diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..b169531c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,22 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{js,ts}] +indent_size = 2 +indent_style = space +block_comment_start = /* +block_comment = * +block_comment_end = */ + +[*.{yml,yaml}] +indent_size = 2 +indent_style = space + +[*.{md,rmd,mkd,mkdn,mdwn,mdown,markdown,litcoffee}] +tab_width = 4 +trim_trailing_whitespace = false diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..04c01ba7 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ \ No newline at end of file diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..1f2c029b --- /dev/null +++ b/.eslintrc @@ -0,0 +1,33 @@ +{ + "extends": "@sapphire", + "rules": { + "no-tabs": "error", + "@typescript-eslint/no-namespace": "off", + "@typescript-eslint/class-literal-property-style": "off", + "padding-line-between-statements": [ + "error", + { "blankLine": "always", "prev": "*", "next": "return" }, + + { "blankLine": "always", "prev": ["const", "let", "var"], "next": "*" }, + { "blankLine": "any", "prev": ["const", "let", "var"], "next": ["const", "let", "var"] }, + + { "blankLine": "always", "prev": "directive", "next": "*" }, + { "blankLine": "any", "prev": "directive", "next": "directive" }, + + { "blankLine": "always", "prev": ["case", "default"], "next": "*" }, + + { "blankLine": "always", "prev": "block", "next": "*" }, + + { "blankLine": "always", "prev": "block-like", "next": "*" }, + + { "blankLine": "always", "prev": "for", "next": "*" }, + { "blankLine": "any", "prev": "for", "next": "for" }, + + { "blankLine": "always", "prev": "if", "next": "*" }, + { "blankLine": "any", "prev": "if", "next": "if" }, + + { "blankLine": "always", "prev": "expression", "next": "*" }, + { "blankLine": "any", "prev": "expression", "next": "expression" } + ] + } +} diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index b6d1f3d0..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,107 +0,0 @@ -{ - "extends": "eslint:recommended", - "env": { - "node": true, - "es6": true - }, - "parserOptions": { - "ecmaVersion": 2020 - }, - "plugins": ["jest"], - "rules": { - "no-extra-parens": ["warn", "all", { - "nestedBinaryExpressions": false - }], - "valid-jsdoc": ["warn", { - "requireReturn": false, - "requireReturnDescription": false, - "preferType": { - "String": "string", - "Number": "number", - "Boolean": "boolean", - "Symbol": "symbol", - "function": "Function", - "object": "Object", - "date": "Date", - "error": "Error" - } - }], - "array-callback-return": "error", - "complexity": "off", - "curly": ["error", "multi-line", "consistent"], - "dot-location": ["error", "property"], - "dot-notation": "error", - "eqeqeq": "off", - "no-console": ["off", { "allow": ["warn", "error"] }], - "no-multi-spaces": "error", - "callback-return": "error", - "handle-callback-err": "error", - "no-path-concat": "error", - "array-bracket-spacing": "error", - "block-spacing": "error", - "brace-style": ["error", "1tbs", { "allowSingleLine": true }], - "camelcase": "error", - "comma-dangle": ["error", "always-multiline"], - "comma-spacing": "error", - "comma-style": "error", - "computed-property-spacing": "error", - "eol-last": "error", - "func-style": ["error", "declaration", { "allowArrowFunctions": true }], - "id-length": ["off"], - "indent": ["error", 2], - "key-spacing": "error", - "keyword-spacing": ["error", { - "overrides": { - "if": { "after": true }, - "for": { "after": true }, - "while": { "after": true }, - "catch": { "after": true }, - "switch": { "after": true } - } - }], - "max-depth": "error", - "max-len": ["error", 200, 2], - "max-nested-callbacks": ["error", { "max": 4 }], - "new-cap": "error", - "newline-per-chained-call": ["error", { "ignoreChainWithDepth": 6 }], - "no-lonely-if": "error", - "no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }], - "no-trailing-spaces": "error", - "no-whitespace-before-property": "error", - "object-curly-newline": "error", - "object-curly-spacing": ["error", "always"], - "operator-assignment": "error", - "operator-linebreak": ["error", "after"], - "quote-props": ["error", "as-needed"], - "quotes": ["error", "single", { "avoidEscape": true, "allowTemplateLiterals": true }], - "semi": "error", - "space-before-blocks": "error", - "space-before-function-paren": ["error", {"anonymous": "always", "named": "never", "asyncArrow": "always"}], - "space-in-parens": "error", - "space-infix-ops": "error", - "unicode-bom": "error", - - "arrow-body-style": ["error", "as-needed"], - "arrow-spacing": "error", - "no-duplicate-imports": "error", - "prefer-arrow-callback": "warn", - "prefer-const": "error", - "prefer-destructuring": ["error", { - "VariableDeclarator": { - "array": false, - "object": true - }, - "AssignmentExpression": { - "array": true, - "object": true - } - }, { - "enforceForRenamedProperties": false - }], - "prefer-rest-params": "error", - "prefer-spread": "error", - "prefer-template": "error", - "rest-spread-spacing": "error", - "template-curly-spacing": "error" - } -} \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..74caf416 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,6 @@ +/.github/ @RealShadowNova +/.husky/ @RealShadowNova +/.vscode/ @RealShadowNova +/docs/ @RealShadowNova +/src/ @RealShadowNova +/tests/ @RealShadowNova diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000..e07e7bae --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,25 @@ +# Contributing + +## Workflow + +1. Fork and clone this repository. +2. Create a new branch in your fork based off the **main** branch. +3. Make your changes, and push them. +4. Submit a Pull Request [here](https://github.com/josh-development/core/pulls)! + +## Contributing to the code + +**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.gg/N7ZKH3P)**. + +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. + +**_Before committing and pushing your changes, please ensure that you do not have any linting errors by running `yarn lint`!_** + +### Josh Concept Guidelines + +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._ + +- 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. +- 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. +- 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. +- 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. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..7440a1df --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: 'github-actions' + directory: '/' + schedule: + interval: 'weekly' + day: 'sunday' + time: '12:00' + labels: + - 'Meta: Dependencies' diff --git a/.github/labels.yml b/.github/labels.yml new file mode 100644 index 00000000..697ad6d4 --- /dev/null +++ b/.github/labels.yml @@ -0,0 +1,177 @@ +- name: 'Bug: Already Fixed' + description: Issues that report already fixed bugs. + color: B3E88C +- name: 'Bug: Cannot Reproduce' + description: Issues that report bugs that cannot be reproduced. + color: f9d0c4 +- name: 'Bug: Confirmed' + description: Issues that report confirmed bugs. + color: f9d0c4 +- name: 'Bug: Fixed' + description: Issues that report bugs and have been fixed. + color: 00fa9a +- name: 'Bug: Fixed in main' + description: Issues that report bugs from stable, but that are fixed in main. + color: b3e88c +- name: 'Bug: Pending Response' + description: Issues that report bugs and are pending of a response from the author. + color: f9d0c4 +- name: 'Bug: Unverified' + description: Issues that report unverified bugs. Pending for inspection. + color: f9d0c4 +- name: 'hacktoberfest-accepted' + description: Issues and PRs related to Hacktoberfest. + color: EE4700 +- name: 'Meta: Bike-Shedding' + description: Issues and PRs that nit pick + color: f9253a +- name: 'Meta: BugFix' + description: PRs that fix bugs or issues. + color: 4cce4a +- name: 'Meta: Cleanup' + description: Issues and PRs related to code cleanup. + color: ffff00 +- name: 'Meta: Dependencies' + description: Issues and PRs related to dependencies. + color: ffff00 +- name: 'Meta: Documentation' + description: Issues and PRs related to documentation. + color: ffff00 +- name: 'Meta: Error Handling' + description: Issues and PRs related to error handling. + color: ffff00 +- name: 'Meta: Examples' + description: Issues and PRs related to examples. + color: ffff00 +- name: 'Meta: Feature' + description: Issues and PRs related to new features. + color: ffff00 +- name: 'Meta: GitHub' + description: Issues and PRs related to GitHub. + color: ffff00 +- name: 'Meta: Refactor' + description: Issues and PRs related to refactors. + color: ffff00 +- name: 'Meta: Typings' + description: Issues and PRs related to typings. + color: ffff00 +- name: 'Meta: V8' + description: Issues and PRs related to the V8 engine. + color: ffff00 +- name: 'Priority: Critical' + description: Issues that must be fixed or PRs that must be finished and merged with + maximum priority. + color: b60205 +- name: 'Priority: High' + description: Issues that must be fixed or PRs that must be finished and merged with + high priority. + color: d93f0b +- name: 'Priority: Medium' + description: Issues that must be fixed or PRs that must be finished and merged with + medium priority. + color: fbca04 +- name: 'Priority: Low' + description: Issues that must be fixed or PRs that must be finished and merged with + low priority. + color: 0e8a16 +- name: 'Priority: Kirito Level' + description: Issues/PR's that have a priority so low that even a quantum computer + couldn't calculate it. + color: '818181' +- name: 'SEM: Major' + description: PRs that contain breaking changes and should be released in the next + major version. + color: 9a35be +- name: 'SEM: Minor' + description: PRs that contain new features and should be released in the next minor + version. + color: d38dfa +- name: 'SEM: Patch' + description: PRs that contain bugfixes and should be released in the next patch + version. + color: 66d8eb +- name: 'SEM: N/A' + description: PRs that only contain documentation changes and do not change the api's + interface. + color: a7f9f3 +- name: 'Status: Blocked' + description: PRs that are blocked by other issues/PRs. + color: d81a0d +- name: 'Status: Denied' + description: Issues or PRs that have been denied by the team. + color: bc1202 +- name: 'Status: Duplicate' + description: Issues/PRs that are duplicated. + color: d93f0b +- name: 'Status: Help Wanted' + description: Issues that need assistance from volunteers or PRs that need help to + proceed. + color: 128A0C +- name: 'Status: Invalid' + description: Issues that will not be fixed or PRs that will not merge, e.g. due + to backwards compatibility. + color: 671df0 +- name: 'Status: Needs Docs' + description: PRs that need documentation + color: d4c5f9 +- name: 'Status: Needs Testing' + description: PRs that need testing from the author or volunteers. + color: d93f0b +- name: 'Status: Needs Typings' + description: PRs that need typings + color: d4c5f9 +- name: 'Status: Ready To Merge' + description: PRs that are ready to merge. + color: 05F541 +- name: 'Status: Review Ready' + description: PRs that are ready for author review. + color: 05F541 +- name: 'Status: Stalled' + description: Issues and PRs that are being set aside for now for rethinking + color: fbca04 +- name: 'Status: WIP' + description: Issues and PRs that are still a work in progress. + color: b60205 +- name: 'Type: Caching' + description: Issues and PRs related to caching. + color: 1d76db +- name: 'Type: Consistency' + description: Issues and PRs related to code consistency. + color: 1d76db +- name: 'Type: Dependencies' + description: Pull requests that update a dependency file + color: 0025ff +- name: 'Type: Discussion' + description: Issues that discuss a new feature, implementation, or a api's interface. + color: ffffff +- name: 'Type: Enhancement' + description: Issues and PRs related to feature enhancement. + color: 1d76db +- name: 'Type: Maintenance' + description: Issues and PRs related to the maintenance of a module. + color: 1d76db +- name: 'Type: Performance' + description: Issues and PRs related to the performance of the api's interface. + color: 1d76db +- name: 'Type: Provider' + description: Issues and PRs related to providers. + color: 1d76db +- name: 'Type: Proposal' + description: Issues that request a new feature or a change in the api's interface. + color: ffffff +- name: 'Type: Question' + description: Issues that do not belong to the Issue Tracker and should be asked + in the official Discord Server. + color: cc317c +- name: 'Type: Security' + description: Pull requests that address a security vulnerability + color: ee0701 +- name: 'Type: Sharding' + description: Issues and PRs related to sharding. + color: 1d76db +- name: 'Type: Typo' + description: Issues and PRs related to typos in the documentation or code. + color: c5def5 +- name: 'Type: Needs Rebase & Merge' + description: PRs that should be merged through the "Rebase and merge" strategy + color: 24853c diff --git a/.github/problemMatchers/eslint.json b/.github/problemMatchers/eslint.json new file mode 100644 index 00000000..eba0bf60 --- /dev/null +++ b/.github/problemMatchers/eslint.json @@ -0,0 +1,22 @@ +{ + "problemMatcher": [ + { + "owner": "eslint-stylish", + "pattern": [ + { + "regexp": "^([^\\s].*)$", + "file": 1 + }, + { + "regexp": "^\\s+(\\d+):(\\d+)\\s+(error|warning|info)\\s+(.*)\\s\\s+(.*)$", + "line": 1, + "column": 2, + "severity": 3, + "message": 4, + "code": 5, + "loop": true + } + ] + } + ] +} diff --git a/.github/problemMatchers/tsc.json b/.github/problemMatchers/tsc.json new file mode 100644 index 00000000..bbd30793 --- /dev/null +++ b/.github/problemMatchers/tsc.json @@ -0,0 +1,17 @@ +{ + "problemMatcher": [ + { + "owner": "tsc", + "pattern": [ + { + "regexp": "^(?:\\s+\\d+\\>)?([^\\s].*)\\((\\d+|\\d+,\\d+|\\d+,\\d+,\\d+,\\d+)\\)\\s*:\\s+(error|warning|info)\\s+(\\w{1,2}\\d+)\\s*:\\s*(.*)$", + "file": 1, + "location": 2, + "severity": 3, + "code": 4, + "message": 5 + } + ] + } + ] +} diff --git a/.github/workflows/continuous-delivery.yml b/.github/workflows/continuous-delivery.yml new file mode 100644 index 00000000..d0cb1954 --- /dev/null +++ b/.github/workflows/continuous-delivery.yml @@ -0,0 +1,84 @@ +name: Continuous Delivery + +on: + push: + branches: + - main + +jobs: + pre_ci: + name: Prepare CI environment + runs-on: ubuntu-latest + steps: + - name: Checkout Project + uses: actions/checkout@v2 + with: + fetch-depth: 2 + - name: '[Push] Get commit message' + if: github.event_name == 'push' + id: push_get_commit_message + run: echo ::set-output name=push_commit_message::$(git log --format=%B -n 1 HEAD) + - name: '[Pull Request] Get commit message' + if: github.event_name == 'pull_request' + id: pr_get_commit_message + run: echo ::set-output name=pr_commit_message::$(git log --format=%B -n 1 HEAD^2) + - name: Add problem matchers + run: | + echo "::add-matcher::.github/problemMatchers/tsc.json" + echo "::add-matcher::.github/problemMatchers/eslint.json" + outputs: + commit_message: $( [ -z "${{ steps.pr_get_commit_message.outputs.pr_commit_message }}" ] && echo "${{ steps.push_get_commit_message.outputs.push_commit_message }}" || echo "${{ steps.pr_get_commit_message.outputs.pr_commit_message }}" ) + + Build: + name: Publish Build + runs-on: ubuntu-latest + if: "!contains(needs.pre_ci.outputs.commit_message, '[skip ci]')" + needs: pre_ci + steps: + - name: Checkout Project + uses: actions/checkout@v2 + - name: Use Node.js 16 + uses: actions/setup-node@v2 + with: + node-version: 16 + - name: Restore CI Cache + uses: actions/cache@v2.1.7 + with: + path: node_modules + key: ${{ runner.os }}-16-${{ hashFiles('**/yarn.lock') }} + - name: Install Dependencies + run: yarn --ignore-scripts --frozen-lockfile + - name: Build Code + run: yarn prepublishOnly + - name: Push Code + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + run: | + REPO="https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" + + echo -e "\n# Checkout the repo in the target branch" + TARGET_BRANCH="build" + git clone $REPO out -b $TARGET_BRANCH + + echo -e "\n# Remove any old files in the dist folder" + rm -rfv out/dist/* + + echo -e "\n# Move the generated files to the newly-checked-out repo, to be committed and pushed" + rsync -vaI package.json out/ + rsync -vaI LICENSE out/ + rsync -vaI README.md out/ + rsync -vaI dist/ out/dist + + echo -e "\n# Removing TSC incremental file" + rm -rfv out/dist/**/*.tsbuildinfo + + echo -e "\n# Commit and push" + cd out + git add --all . + git config user.name "${GITHUB_ACTOR}" + git config user.email "${GITHUB_EMAIL}" + git commit -m "build: tsc build for ${GITHUB_SHA}" || true + git push origin $TARGET_BRANCH + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_ACTOR: 'github-actions[bot]' + GITHUB_EMAIL: '41898282+github-actions[bot]@users.noreply.github.com' diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml new file mode 100644 index 00000000..1f12250f --- /dev/null +++ b/.github/workflows/continuous-integration.yml @@ -0,0 +1,78 @@ +name: Continuous Integration + +on: + push: + branches: + - main + pull_request: + +jobs: + Linting: + name: Linting + runs-on: ubuntu-latest + steps: + - name: Checkout Project + uses: actions/checkout@v2 + - name: Add problem matcher + run: echo "::add-matcher::.github/problemMatchers/eslint.json" + - name: Use Node.js v16 + uses: actions/setup-node@v2 + with: + node-version: 16 + - name: Restore CI Cache + uses: actions/cache@v2.1.7 + id: cache-restore + with: + path: node_modules + key: ${{ runner.os }}-16-${{ hashFiles('**/yarn.lock') }} + - name: Install Dependencies if Cache Miss + if: ${{ !steps.cache-restore.outputs.cache-hit }} + run: yarn --frozen-lockfile + - name: Run ESLint + run: yarn lint --fix=false + + Testing: + name: Unit Tests + runs-on: ubuntu-latest + steps: + - name: Checkout Project + uses: actions/checkout@v2 + - name: Use Node.js v16 + uses: actions/setup-node@v2 + with: + node-version: 16 + - name: Restore CI Cache + uses: actions/cache@v2.1.7 + id: cache-restore + with: + path: node_modules + key: ${{ runner.os }}-16-${{ hashFiles('**/yarn.lock') }} + - name: Install Dependencies if Cache Miss + if: ${{ !steps.cache-restore.outputs.cache-hit }} + run: yarn --frozen-lockfile + - name: Run tests + run: yarn test --coverage + + Building: + name: Compile Source Code + runs-on: ubuntu-latest + steps: + - name: Checkout Project + uses: actions/checkout@v2 + - name: Add problem matcher + run: echo "::add-matcher::.github/problemMatchers/tsc.json" + - name: Use Node.js v16 + uses: actions/setup-node@v2 + with: + node-version: 16 + - name: Restore CI Cache + uses: actions/cache@v2.1.7 + id: cache-restore + with: + path: node_modules + key: ${{ runner.os }}-16-${{ hashFiles('**/yarn.lock') }} + - name: Install Dependencies if Cache Miss + if: ${{ !steps.cache-restore.outputs.cache-hit }} + run: yarn --frozen-lockfile + - name: Build Code + run: yarn build diff --git a/.github/workflows/labelssync.yml b/.github/workflows/labelssync.yml new file mode 100644 index 00000000..ff84aff8 --- /dev/null +++ b/.github/workflows/labelssync.yml @@ -0,0 +1,19 @@ +name: Automatic Label Sync + +on: + schedule: + - cron: '0 0 * * *' + workflow_dispatch: + +jobs: + label_sync: + name: Automatic Label Synchronization + runs-on: ubuntu-latest + steps: + - name: Checkout Project + uses: actions/checkout@v2 + - name: Run Label Sync + uses: crazy-max/ghaction-github-labeler@v3 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + yaml-file: .github/labels.yml diff --git a/.gitignore b/.gitignore index 2694f582..e95ccbc4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,29 @@ -.hgignore -node_modules -.c9 -.DS_Store -Thumbs.db -data/ -config.json -.vscode -data/ -out/ -config-*.json \ No newline at end of file +# Ignore a blachole and the folder for development +node_modules/ +.vs/ + +# Ignore tsc dist folder +dist/ + +# Ignore JavaScript files +**/*.js +**/*.mjs +**/*.js.map +**/*.d.ts +!src/**/*.d.ts +**/*.tsbuildinfo +!jest.config.ts +!scripts/*.mjs + +# Ignore heapsnapshot and log files +*.heapsnapshot +*.log + +# Ignore package locks +package-lock.json + +# Ignore the GH cli downloaded by workflows +gh + +# Ignore auto-generated documentation +docs/api.json diff --git a/.husky/.gitignore b/.husky/.gitignore new file mode 100644 index 00000000..31354ec1 --- /dev/null +++ b/.husky/.gitignore @@ -0,0 +1 @@ +_ diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 00000000..d71a03b9 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +yarn commitlint --edit $1 diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 00000000..209199df --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +yarn pretty-quick --staged && yarn lint-staged diff --git a/.prettierrc b/.prettierrc index 921da85e..69cdf455 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,7 +1,20 @@ { - "tabWidth": 2, + "$schema": "http://json.schemastore.org/prettierrc", + "endOfLine": "lf", + "overrides": [ + { + "files": "*.yml", + "options": { + "tabWidth": 2, + "useTabs": false + } + } + ], + "printWidth": 150, + "quoteProps": "as-needed", "semi": true, "singleQuote": true, - "trailingComma": "all", - "endOfLine": "lf" + "tabWidth": 2, + "trailingComma": "none", + "useTabs": false } diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..395e834c --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "streetsidesoftware.code-spell-checker"] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..5baa34a8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "eslint.validate": ["typescript"], + "editor.tabSize": 2, + "editor.useTabStops": false, + "editor.insertSpaces": true, + "editor.detectIndentation": false, + "editor.codeActionsOnSave": { + "source.fixAll": true, + "source.fixAll.eslint": true, + "source.organizeImports": true + }, + "files.eol": "\n", + "typescript.tsdk": "node_modules\\typescript\\lib", + "cSpell.maxNumberOfProblems": 8, + "cSpell.numSuggestions": 24, + "cSpell.words": ["joshdb", "Évelyne", "Lachance", "Hendry", "typedoc", "realware", "commitlint", "favware", "tsbuildinfo"] +} diff --git a/README.md b/README.md index 320ae695..50656b3a 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,49 @@ -# JOSH - Javascript Object Storage Helper -
-+![Josh Logo](https://evie.codes/josh-light.png) + +# JOSH + +**JavaScript Object Storage Helper** + +[![GitHub](https://img.shields.io/github/license/josh-development/core)](https://github.com/josh-development/core/blob/main/LICENSE.md) +[![npm](https://img.shields.io/npm/v/@joshdb/core?color=crimson&logo=npm&style=flat-square)](https://www.npmjs.com/package/@joshdb/core) + +[![Support Server](https://discord.com/api/guilds/298508738623438848/embed.png?style=banner2)](https://discord.gg/N7ZKH3P) +
Promise.<Array.String>
- * [.values](#Josh+values) ⇒ Promise.<Array>
- * [.size](#Josh+size) ⇒ Promise.<number>
- * [.get(keyOrPath)](#Josh+get) ⇒ Promise.<\*>
- * [.getMany(keys)](#Josh+getMany) ⇒ Promise.<Object>
- * [.random(count)](#Josh+random) ⇒ Promise.<Object>
- * [.randomKey(count)](#Josh+randomKey) ⇒ Promise.<Array.<string>>
- * [.has(keyOrPath)](#Josh+has) ⇒ Promise.<boolean>
- * [.set(keyOrPath, value)](#Josh+set) ⇒ [Promise.<Josh>
](#Josh)
- * [.setMany(data, overwrite)](#Josh+setMany) ⇒ [Promise.<Josh>
](#Josh)
- * [.update(keyOrPath, input)](#Josh+update) ⇒ Promise.<Object>
- * [.ensure(keyOrPath, defaultValue)](#Josh+ensure) ⇒ Promise.<\*>
- * [.delete(keyOrPath)](#Josh+delete) ⇒ [Promise.<Josh>
](#Josh)
- * [.push(keyOrPath, value, allowDupes)](#Josh+push) ⇒ [Promise.<Josh>
](#Josh)
- * [.remove(keyOrPath, value)](#Josh+remove) ⇒ [Promise.<Josh>
](#Josh)
- * [.inc(keyOrPath)](#Josh+inc) ⇒ [Promise.<Josh>
](#Josh)
- * [.dec(keyOrPath)](#Josh+dec) ⇒ [Promise.<Josh>
](#Josh)
- * [.find(pathOrFn, predicate)](#Josh+find) ⇒ Promise.<Object>
- * [.filter(pathOrFn, predicate)](#Josh+filter) ⇒ Promise.<Object>
- * [.map(pathOrFn)](#Josh+map) ⇒ Promise.<Array.<\*>>
- * [.includes(keyOrPath, value)](#Josh+includes) ⇒ boolean
- * [.some(pathOrFn, value)](#Josh+some) ⇒ boolean
- * [.every(pathOrFn, value)](#Josh+every) ⇒ boolean
- * [.math(keyOrPath, operation, operand, path)](#Josh+math) ⇒ [Promise.<Josh>
](#Josh)
- * [.autoId()](#Josh+autoId) ⇒ Promise.<string>
- * [.import(data, overwrite, clear)](#Josh+import) ⇒ [Promise.<Josh>
](#Josh)
- * [.export()](#Josh+export) ⇒ Promise.<string>
- * _static_
- * [.multi(names, options)](#Josh.multi) ⇒ Array.<Map>
-
-
-
-### new Josh([options])
-Initializes a new Josh, with options.
-
-
-| Param | Type | Description |
-| --- | --- | --- |
-| [options] | Object
| Additional options an configurations. |
-| [options.name] | string
| Required. The name of the table in which to save the data. |
-| [options.provider] | string
| Required. A string with the name of the provider to use. Should not be already required, as Josh takes care of doing that for you. *Must* be a valid provider that complies with the Provider API. The provider needs to be installed separately with yarn or npm. See https://josh.evie.dev/providers for details. |
-| [options.ensureProps] | boolean
| defaults to `true`. If enabled and an inserted value is an object, using ensure() will also ensure that every property present in the default object will be added to the value, if it's absent. |
-| [options.autoEnsure] | \*
| default is disabled. When provided a value, essentially runs ensure(key, autoEnsure) automatically so you don't have to. This is especially useful on get(), but will also apply on set(), and any array and object methods that interact with the database. |
-| [options.serializer] | function
| Optional. If a function is provided, it will execute on the data when it is written to the database. This is generally used to convert the value into a format that can be saved in the database, such as converting a complete class instance to just its ID. This function may return the value to be saved, or a promise that resolves to that value (in other words, can be an async function). |
-| [options.deserializer] | function
| Optional. If a function is provided, it will execute on the data when it is read from the database. This is generally used to convert the value from a stored ID into a more complex object. This function may return a value, or a promise that resolves to that value (in other words, can be an async function). |
-
-**Example**
-```js
-const Josh = require("@joshdb/core");
-const provider = require("@joshdb/sqlite");
-
-// sqlite-based database, with default options
-const sqliteDB = new Josh({
- name: 'mydatabase',
- provider,
-});
-```
-
-
-### josh.keys ⇒ Promise.<Array.String>
-Get all the keys in the database.
-
-**Kind**: instance property of [Josh
](#josh)
-**Returns**: Promise.<Array.String>
- An array of all the keys as string values.
-
-
-### josh.values ⇒ Promise.<Array>
-Get all the values in the database.
-
-**Kind**: instance property of [Josh
](#josh)
-**Returns**: Promise.<Array>
- An array of all the values stored in the database.
-
-
-### josh.size ⇒ Promise.<number>
-Get the amount of rows inside the database.
-
-**Kind**: instance property of [Josh
](#josh)
-**Returns**: Promise.<number>
- An integer equal to the amount of stored key/value pairs.
-
-
-### josh.get(keyOrPath) ⇒ Promise.<\*>
-Retrieves (fetches) a value from the database. If a simple key is provided, returns the value.
-If a path is provided, will only return the value at that path, if it exists.
-
-**Kind**: instance method of [Josh
](#josh)
-**Returns**: Promise.<\*>
- Returns the value for the key or the value found at the specified path.
-
-| Param | Type | Description |
-| --- | --- | --- |
-| keyOrPath | string
| Either a key, or full path, of the value you want to get. For more information on how path works, see https://josh.evie.dev/path |
-
-
-
-### josh.getMany(keys) ⇒ Promise.<Object>
-Retrieve many values from the database.
-If you provide `josh.all` as a value (josh being your variable for the database), the entire data set is returned.
-
-**Kind**: instance method of [Josh
](#josh)
-**Returns**: Promise.<Object>
- An object with one or many key/value pairs where the property name is the key and the property value is the database value.
-
-| Param | Type | Description |
-| --- | --- | --- |
-| keys | Array.<string>
\| symbol
| An array of keys to return, or `db.all` to retrieve them all. |
-
-
-
-### josh.random(count) ⇒ Promise.<Object>
-Returns one or more random values from the database.
-
-**Kind**: instance method of [Josh
](#josh)
-**Returns**: Promise.<Object>
- An array of key/value pairs each in their own array.
-The array of values should never contain duplicates. If the requested count is higher than the number
-of rows in the database, only the available number of rows will be returned, in randomized order.
-Each array element is comprised of the key and value: // TODO : FIX [['a', 1], ['b', 2], ['c', 3]]
-
-| Param | Type | Description |
-| --- | --- | --- |
-| count | number
| Defaults to 1. The number of random key/value pairs to get. |
-
-
-
-### josh.randomKey(count) ⇒ Promise.<Array.<string>>
-Returns one or more random keys from the database.
-
-**Kind**: instance method of [Josh
](#josh)
-**Returns**: Promise.<Array.<string>>
- An array of string keys in a randomized order.
-The array of keys should never contain duplicates. If the requested count is higher than the number
-of rows in the database, only the available number of rows will be returned.
-
-| Param | Type | Description |
-| --- | --- | --- |
-| count | number
| Defaults to 1. The number of random key/value pairs to get. |
-
-
-
-### josh.has(keyOrPath) ⇒ Promise.<boolean>
-Verifies whether a key, or a specific property of an object, exists at all.
-
-**Kind**: instance method of [Josh
](#josh)
-**Returns**: Promise.<boolean>
- Whether the key, or property specified in the path, exists.
-
-| Param | Type | Description |
-| --- | --- | --- |
-| keyOrPath | string
| Either a key, or full path, of the value you want to get. For more information on how path works, see https://josh.evie.dev/path |
-
-
-
-### josh.set(keyOrPath, value) ⇒ [Promise.<Josh>
](#Josh)
-Store a value in the database. If a simple key is provided, creates or overwrites the entire value with the new one provide.
-If a path is provided, and the stored value is an object, only the value at the path will be overwritten.
-
-**Kind**: instance method of [Josh
](#josh)
-**Returns**: [Promise.<Josh>
](#Josh) - This database wrapper, useful if you want to chain more instructions for Josh.
-
-| Param | Type | Description |
-| --- | --- | --- |
-| keyOrPath | string
| Either a key, or a full path, where you want to store the value. For more information on how path works, see https://josh.evie.dev/path |
-| value | \*
| The value to store for the key, or in the path, specified. All values MUST be "simple" javascript values: Numbers, Booleans, Strings, Arrays, Objects. If you want to store a "complex" thing such as an instance of a class, please use a Serializer to convert it to a storable value. |
-
-
-
-### josh.setMany(data, overwrite) ⇒ [Promise.<Josh>
](#Josh)
-Store many values at once in the database. DOES NOT SUPPORT PATHS. Or autoId.
-
-**Kind**: instance method of [Josh
](#josh)
-**Returns**: [Promise.<Josh>
](#Josh) - This database wrapper, useful if you want to chain more instructions for Josh.
-
-| Param | Type | Description |
-| --- | --- | --- |
-| data | Object
| The data to insert. Must be an object as key/value pairs. |
-| overwrite | boolean
| Whether to overwrite existing keys. Since this method does not support paths, existin data will be lost. |
-
-**Example**
-```js
-josh.setMany({
- "thinga": "majig",
- "foo": "bar",
- "isCool": true
-});
-```
-
-
-### josh.update(keyOrPath, input) ⇒ Promise.<Object>
-Update an object in the database with modified values. Similar to set() except it does not overwrite the entire object.
-Instead, the data is *merged* with the existing object. Object properties not included in your data are not touched.
-
-**Kind**: instance method of [Josh
](#josh)
-**Returns**: Promise.<Object>
- The merged object that will be stored in the database.
-
-| Param | Type | Description |
-| --- | --- | --- |
-| keyOrPath | string
| Either a key, or full path, of the value you want to update. |
-| input | Object
\| function
| Either the object, or a function. If a function is provided, it will receive the *current* value as an argument. You are expected to return a modified object that will be stored in the database. |
-
-**Example**
-```js
-josh.set('thing', {
- a: 1,
- b: 2,
- c: 3
-});
-josh.update('thing', {
- a: 'one',
- d: 4
-});
-// value is now {a: 'one', b: 2, c: 3, d: 4}
-
-josh.update('thing', (previousValue) => {
- ...previousValue,
- b: 'two',
- e: 5,
-});
-// value is now {a: 'one', b: 'two', c: 3, d: 4, e: 5}
-```
-
-
-### josh.ensure(keyOrPath, defaultValue) ⇒ Promise.<\*>
-Returns the key's value, or the default given, ensuring that the data is there.
-This is a shortcut to "if josh doesn't have key, set it, then get it" which is a very common pattern.
-
-**Kind**: instance method of [Josh
](#josh)
-**Returns**: Promise.<\*>
- The value from the database for the key, or the default value provided for a new key.
-
-| Param | Type | Description |
-| --- | --- | --- |
-| keyOrPath | string
| Either a key, or full path, of the value you want to ensure. |
-| defaultValue | \*
| Required. The value you want to save in the database and return as default. |
-
-**Example**
-```js
-// Simply ensure the data exists (for using property methods):
-josh.ensure("mykey", {some: "value", here: "as an example"});
-josh.has("mykey"); // always returns true
-josh.get("mykey", "here") // returns "as an example";
-
-// Get the default value back in a variable:
-const settings = mySettings.ensure("1234567890", defaultSettings);
-console.log(settings) // josh's value for "1234567890" if it exists, otherwise the defaultSettings value.
-```
-
-
-### josh.delete(keyOrPath) ⇒ [Promise.<Josh>
](#Josh)
-Remove a key/value pair, or the property and value at a specific path, or clear the database.
-
-**Kind**: instance method of [Josh
](#josh)
-**Returns**: [Promise.<Josh>
](#Josh) - This database wrapper, useful if you want to chain more instructions for Josh.
-
-| Param | Type | Description |
-| --- | --- | --- |
-| keyOrPath | string
\| symbol
\| Array.<string>
| Either a key, or full path, of the value you want to delete. If providing a path, only the value located at the path is deleted. If providing an array, will delete all keys in that array (does not support paths) Alternatively: josh.delete(josh.all) will clear the database of all data. |
-
-
-
-### josh.push(keyOrPath, value, allowDupes) ⇒ [Promise.<Josh>
](#Josh)
-Add a new value to an array.
-
-**Kind**: instance method of [Josh
](#josh)
-**Returns**: [Promise.<Josh>
](#Josh) - This database wrapper, useful if you want to chain more instructions for Josh.
-
-| Param | Type | Default | Description |
-| --- | --- | --- | --- |
-| keyOrPath | string
| | Either a key, or full path, where the array where you want to add a value. |
-| value | \*
| | The value to add to the array. |
-| allowDupes | boolean
| true
| Whether to allow duplicate values to be added. Note that if you're pushing objects or arrays, duplicates can occur no matter what, as detecting duplicate objects is CPU-intensive. |
-
-
-
-### josh.remove(keyOrPath, value) ⇒ [Promise.<Josh>
](#Josh)
-Remove a value from an array, by value (simple values like strings and numbers) or function (complex values like arrays or objects).
-
-**Kind**: instance method of [Josh
](#josh)
-**Returns**: [Promise.<Josh>
](#Josh) - This database wrapper, useful if you want to chain more instructions for Josh.
-
-| Param | Type | Description |
-| --- | --- | --- |
-| keyOrPath | \*
| Either a key, or full path, where the array where you want to remove from, is stored. |
-| value | \*
\| function
| Required. The value to remove from the array. OR a function to match a value stored in the array. If using a function, the function provides the value and must return a boolean that's true for the value you want to remove. |
-
-**Example**
-```js
-// Assuming
-josh.set('array', [1, 2, 3])
-josh.set('objectarray', [{ a: 1, b: 2, c: 3 }, { d: 4, e: 5, f: 6 }])
-
-josh.remove('array', 1); // value is now [2, 3]
-josh.remove('objectarray', (value) => value.e === 5); // value is now [{ a: 1, b: 2, c: 3 }]
-```
-
-
-### josh.inc(keyOrPath) ⇒ [Promise.<Josh>
](#Josh)
-Increments (adds 1 to the number) the stored value.
-
-**Kind**: instance method of [Josh
](#josh)
-**Returns**: [Promise.<Josh>
](#Josh) - This database wrapper, useful if you want to chain more instructions for Josh.
-
-| Param | Type | Description |
-| --- | --- | --- |
-| keyOrPath | \*
| Either a key, or full path, to the value you want to increment. The value must be a number. |
-
-
-
-### josh.dec(keyOrPath) ⇒ [Promise.<Josh>
](#Josh)
-Decrements (remove 1 from the number) the stored value.
-
-**Kind**: instance method of [Josh
](#josh)
-**Returns**: [Promise.<Josh>
](#Josh) - This database wrapper, useful if you want to chain more instructions for Josh.
-
-| Param | Type | Description |
-| --- | --- | --- |
-| keyOrPath | \*
| Either a key, or full path, to the value you want to decrement. The value must be a number. |
-
-
-
-### josh.find(pathOrFn, predicate) ⇒ Promise.<Object>
-Finds a value within the database, either through an exact value match, or a function.
-Useful for Objects and Array values, will not work on "simple" values like strings.
-Returns the first found match - if you need more than one result, use filter() instead.
-Either a function OR a value **must** be provided.
-Note that using functions here currently is very inefficient, so it's suggested to use paths whenever necesary.
-
-**Kind**: instance method of [Josh
](#josh)
-**Returns**: Promise.<Object>
- Returns an array composed of the full value (NOT the one at the path!), and the key.
-
-| Param | Type | Description |
-| --- | --- | --- |
-| pathOrFn | function
\| string
| Mandatory. Either a function, or the path in which to find the value. If using a function: it will run on either the stored value, OR the value at the path given if it's provided. - The function receives the value (or value at the path) as well the the key currently being checked. - The function must return a boolean or truthy/falsey value! Oh and the function can be async, too ;) If using a path: - A "value" predicate is mandatory when checking by path. - The value must be simple: string, boolean, integer. It cannot be an object or array. |
-| predicate | string
| Optional on functions, Mandatory on path finds. If provided, the function or value acts on what's at that path. |
-
-**Example**
-```js
-// Assuming:
-josh.set("john.shmidt", {
- fullName: "John Jacob Jingleheimer Schmidt",
- id: 12345,
- user: {
- username: "john.shmidt",
- firstName: "john",
- lastName: "shmidt",
- password: "somerandombcryptstringthingy",
- lastAccess: -22063545000,
- isActive: false,
- avatar: null,
- }
-});
-
-// Regular string find:
-josh.find("user.firstName", "john")
-
-// Simple function find:
-josh.find(value => value.user.firstName === "john");
-
-// Function find with a path:
-josh.find(value => value === "john", "user.firstName");
-
-// The return of all the above if the same:
-{
- "john.shmidt": {
- fullName: "John Jacob Jingleheimer Schmidt",
- id: 12345,
- user: {
- username: "john.shmidt",
- firstName: "john",
- lastName: "shmidt",
- password: "somerandombcryptstringthingy",
- lastAccess: -22063545000,
- isActive: false,
- avatar: null,
- }
- }
-}
-```
-
-
-### josh.filter(pathOrFn, predicate) ⇒ Promise.<Object>
-Filters for values within the database, either through an exact value match, or a function.
-Useful for Objects and Array values, will not work on "simple" values like strings.
-Returns all matches found - if you need a single value, use find() instead.
-Either a function OR a value **must** be provided.
-Note that using functions here currently is very inefficient, so it's suggested to use paths whenever necesary.
-
-**Kind**: instance method of [Josh
](#josh)
-**Returns**: Promise.<Object>
- Returns an array of key/value pair(s) that successfully passes the provided function.
-
-| Param | Type | Description |
-| --- | --- | --- |
-| pathOrFn | function
\| string
| Mandatory. Either a function, or the path in which to find the value. If using a function: it will run on either the stored value, OR the value at the path given if it's provided. - The function receives the value (or value at the path) as well the the key currently being checked. - The function must return a boolean or truthy/falsey value! Oh and the function can be async, too ;) If using a path: - A "value" predicate is mandatory when checking by path. - The value must be simple: string, boolean, integer. It cannot be an object or array. |
-| predicate | string
| Optional on functions, Mandatory on path finds. If provided, the function or value acts on what's at that path. |
-
-
-
-### josh.map(pathOrFn) ⇒ Promise.<Array.<\*>>
-Maps data from each value in your data. Works similarly to Array.map(), but can use both async functions, as well as paths.
-Note that using functions here currently is very inefficient, so it's suggested to use paths whenever necesary.
-
-**Kind**: instance method of [Josh
](#josh)
-**Returns**: Promise.<Array.<\*>>
- An array of values mapped from the data.
-
-| Param | Type | Description |
-| --- | --- | --- |
-| pathOrFn | function
\| string
| Mandatory. Either a function, or the path where to get the value from. If using a path, the value at the path will be returned, or null. If using a function, the function is run on the entire value (no path is used). The function is given the `key` and `value` as arguments, and the value returned will be accessible in the return array. |
-
-
-
-### josh.includes(keyOrPath, value) ⇒ boolean
-Performs Array.includes() on a certain value. Works similarly to
-[Array.includes()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes).
-
-**Kind**: instance method of [Josh
](#josh)
-**Returns**: boolean
- Whether the value is included in the array.
-
-| Param | Type | Description |
-| --- | --- | --- |
-| keyOrPath | string
| Either a key, or full path, to the array you want to check for the value. The value must be an array. |
-| value | \*
| Either the value to check in the array, or a function to determine the presence of the value. If using a value, note that this won't work if the value you're checking for is an array or object - use a function for that. If using a function, the function takes in the value and index, and must return a boolean true when the value is the one you want. |
-
-**Example**
-```js
-josh.set('arr', ['a', 'b', 1, 2, { foo: "bar"}]);
-
-josh.includes('arr', 'a'); // true
-josh.includes('arr', 1) // true
-josh.includes('arr', val => val.foo === 'bar'); // true
-```
-
-
-### josh.some(pathOrFn, value) ⇒ boolean
-Checks whether *at least one key* contains the expected value. The loop stops once the value is found.
-
-**Kind**: instance method of [Josh
](#josh)
-**Returns**: boolean
- Whether the value was found or not (if one of the rows in the database match the value at path, or the function has returned true)
-
-| Param | Type | Description |
-| --- | --- | --- |
-| pathOrFn | string
| Either a function, or the full path to the value to check against the provided value. If using a path, the value at he path will be compared to the value provided as a second argument. If using a function, the function is given the *full* value for each key, along with the key itself, for each row in the database. It should return `true` if your match is found. |
-| value | string
\| number
\| boolean
\| null
| The value to be checked at each path. Cannot be an object or array (use a function for those). Ignored if a function is provided. |
-
-
-
-### josh.every(pathOrFn, value) ⇒ boolean
-Checks whether *every single key* contains the expected value. Identical to josh.some() except all must match except just one.
-
-**Kind**: instance method of [Josh
](#josh)
-**Returns**: boolean
- Whether the value was found or not, on ever single row.
-
-| Param | Type | Description |
-| --- | --- | --- |
-| pathOrFn | \*
| Either a function, or the full path to the value to check against the provided value. If using a path, the value at he path will be compared to the value provided as a second argument. If using a function, the function is given the *full* value for each key, along with the key itself, for each row in the database. It should return `true` if your match is found. |
-| value | string
\| number
\| boolean
\| null
| The value to be checked at each path. Cannot be an object or array (use a function for those). |
-
-
-
-### josh.math(keyOrPath, operation, operand, path) ⇒ [Promise.<Josh>
](#Josh)
-Executes a mathematical operation on a value and saves the result in the database.
-
-**Kind**: instance method of [Josh
](#josh)
-**Returns**: [Promise.<Josh>
](#Josh) - This database wrapper, useful if you want to chain more instructions for Josh.
-
-| Param | Type | Description |
-| --- | --- | --- |
-| keyOrPath | string
| Either a key, or full path, to the numerical value you want to exceute math on. Must be an Number value. |
-| operation | string
| Which mathematical operation to execute. Supports most math ops: =, -, *, /, %, ^, and english spelling of those operations. |
-| operand | number
| The right operand of the operation. |
-| path | string
| Optional. The property path to execute the operation on, if the value is an object or array. |
-
-**Example**
-```js
-// Assuming
-josh.set("number", 42);
-josh.set("numberInObject", {sub: { anInt: 5 }});
-
-josh.math("number", "/", 2); // 21
-josh.math("number", "add", 5); // 26
-josh.math("number", "modulo", 3); // 2
-josh.math("numberInObject.sub.anInt", "+", 10); // 15
-```
-
-
-### josh.autoId() ⇒ Promise.<string>
-Get an automatic ID for insertion of a new record.
-
-**Kind**: instance method of [Josh
](#josh)
-**Returns**: Promise.<string>
- A unique ID to insert data.
-**Example**
-```js
-const Josh = require("@joshdb/core");
-const provider = require("@joshdb/sqlite");
-
-
-const sqliteDB = new Josh({
- name: 'mydatabase',
- provider,
-});
-(async() => {
- const newId = await sqliteDB.autoId();
- console.log("Inserting new row with ID: ", newID);
- sqliteDB.set(newId, "This is a new test value");
-})();
-```
-
-
-### josh.import(data, overwrite, clear) ⇒ [Promise.<Josh>
](#Josh)
-Import an existing json export from josh or enmap. This data must have been exported from josh or enmap,
-and must be from a version that's equivalent or lower than where you're importing it.
-
-**Kind**: instance method of [Josh
](#josh)
-**Returns**: [Promise.<Josh>
](#Josh) - This database wrapper, useful if you want to chain more instructions for Josh.
-
-| Param | Type | Default | Description |
-| --- | --- | --- | --- |
-| data | string
| | The data to import to Josh. Must contain all the required fields provided by export() |
-| overwrite | boolean
| true
| Defaults to `true`. Whether to overwrite existing key/value data with incoming imported data |
-| clear | boolean
| false
| Defaults to `false`. Whether to clear the enmap of all data before importing (**__WARNING__**: Any exiting data will be lost! This cannot be undone.) |
-
-
-
-### josh.export() ⇒ Promise.<string>
-Exports your entire database in JSON format. Useable as import data for both Josh and Enmap.
-***WARNING: This currently requires loading the entire database in memory to write to JSON and might fail on large datasets (more than 1Gb)***
-
-**Kind**: instance method of [Josh
](#josh)
-**Returns**: Promise.<string>
- A JSON string that can be saved wherever you need it.
-**Example**
-```js
-const fs = require("fs");
-josh.export().then(data => fs.writeFileSync("./export.json"), data));
-```
-
-
-### Josh.multi(names, options) ⇒ Array.<Map>
-Initialize multiple Josh instances easily. Used to simplify the creation of many tables
-
-**Kind**: static method of [Josh
](#josh)
-**Returns**: Array.<Map>
- An array of initialized Josh instances.
-
-| Param | Type | Description |
-| --- | --- | --- |
-| names | Array.<string>
| Array of strings. Each array entry will create a separate josh with that name. |
-| options | Object
| Options object to pass to each josh, excluding the name.. |
-
-**Example**
-```js
-// Using local variables.
-const Josh = require('josh');
-const provider = require("@joshdb/sqlite");
-const { settings, tags, blacklist } = Josh.multi(['settings', 'tags', 'blacklist'], { provider });
-
-// Attaching to an existing object (for instance some API's client)
-const Josh = require("@joshdb/core");
-const provider = require("@joshdb/sqlite");
-Object.assign(client, Josh.multi(["settings", "tags", "blacklist"], { provider }));
-```
diff --git a/docs/build.js b/docs/build.js
deleted file mode 100644
index 7c71b403..00000000
--- a/docs/build.js
+++ /dev/null
@@ -1,52 +0,0 @@
-const jsdoc2md = require('jsdoc-to-markdown');
-const fs = require('fs');
-const slug = require('limax');
-
-var htmlEntities = {
- nbsp: ' ',
- cent: '¢',
- pound: '£',
- yen: '¥',
- euro: '€',
- copy: '©',
- reg: '®',
- lt: '<',
- gt: '>',
- quot: '"',
- amp: '&',
- apos: '\'',
-};
-
-// eslint-disable-next-line
-const unescapeHTML = str => str.replace(/\&([^;]+);/g, (entity, entityCode) => {
- let match;
-
- if (entityCode in htmlEntities) {
- return htmlEntities[entityCode];
- /* eslint no-cond-assign: 0 */
- } else if (match = entityCode.match(/^#x([\da-fA-F]+)$/)) {
- return String.fromCharCode(parseInt(match[1], 16));
- /* eslint no-cond-assign: 0 */
- } else if (match = entityCode.match(/^#(\d+)$/)) {
- // eslint-disable-next-line
- return String.fromCharCode(~~match[1]);
- } else {
- return entity;
- }
-});
-
-const finalize = str => str
- .replace(/\[Promise\.<Josh><\/code >\](#Josh)/gi, '[Promise.<Josh>
](#josh)')
- .replace(/\[Josh<\/code>\]\(#Josh\)/gi, '[Josh
](#josh)')
- .replace('* [new Josh([options])](#new_Josh_new)', '* [new Josh([options])](#new-josh-options)');
-
-const regexread = /^ {4}\* \[\.(.*?)\]\((.*?)\)(.*?)(\(#.*?\)|)$/gm;
-
-// eslint-disable-next-line
-const parseData = data => finalize(data.replace(regexread, (_, b, __, d) =>
- ` * [.${b}](#${slug(`josh.${b} ${unescapeHTML(d.replace(/<\/?code>/g, ''))}`)})${d}`));
-
-jsdoc2md.render({ files: './src/index.js' }).then(data =>
- fs.writeFile('./docs/api-docs.md',
- parseData(data),
- () => false));
diff --git a/docs/guides/getting-started/GettingStarted.md b/docs/guides/getting-started/GettingStarted.md
new file mode 100644
index 00000000..fea2536b
--- /dev/null
+++ b/docs/guides/getting-started/GettingStarted.md
@@ -0,0 +1,48 @@
+# Getting Started with `@joshdb/core`
+
+## Installing [`@joshdb/core`](https://github.com/josh-development/core/tree/build)
+
+```sh
+# Using NPM
+npm i @joshdb/core@next
+# Or using Yarn
+yarn add @joshdb/core@next
+```
+
+Note: Josh requires you to have Node.js v16.0.0 or higher.
+
+## Basic Usage
+
+You can make use of the **Josh** class exported from `@joshdb/core` by using `import` or `require`. You can then initiate an instance very simply. Below will create an in-memory storage using the [`MapProvider`](https://github.com/josh-development/core/blob/main/src/lib/structures/defaultProvider/MapProvider.ts)
+
+### CommonJS
+
+```javascript
+const { Josh } = require('@joshdb/core');
+
+const josh = new Josh({ name: 'name' });
+```
+
+### ESM
+
+```javascript
+import { Josh } from '@joshdb/core';
+
+const josh = new Josh({ name: 'name' });
+```
+
+### TypeScript
+
+You can use [generics](https://www.typescriptlang.org/docs/handbook/2/generics.html) to define what type your `Josh` instance uses.
+
+```typescript
+import { Josh } from '@joshdb/core';
+
+interface User {
+ name: string;
+
+ email: string;
+}
+
+const josh = new Josh({ name: 'name' });
+```
diff --git a/docs/guides/getting-started/UsingProviders.md b/docs/guides/getting-started/UsingProviders.md
new file mode 100644
index 00000000..3bcd794d
--- /dev/null
+++ b/docs/guides/getting-started/UsingProviders.md
@@ -0,0 +1,68 @@
+# Using Providers
+
+## What is a provider?
+
+A provider is simply a layer between **Josh** and a database (or wherever data is stored). Each provider receives a payload from every method you use in your instance and returns a modified version.
+
+## Basic Usage
+
+Providers can be installed separately from Josh using Yarn or NPM. You can also create your own provider by extending the exported [`JoshProvider`](https://github.com/josh-development/core/blob/main/src/lib/structures/JoshProvider.ts) class.
+
+In The example below we will use the default in-memory provider which using the native [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) class.
+
+### CommonJS
+
+```javascript
+const { Josh, MapProvider } = require('@joshdb/core');
+
+const josh = new Josh({
+ name: 'name',
+ provider: new MapProvider()
+});
+```
+
+### ESM
+
+```javascript
+import { Josh, MapProvider } from '@joshdb/core';
+
+const josh = new Josh({
+ name: 'name',
+ provider: new MapProvider()
+});
+```
+
+### TypeScript
+
+You can use [generics](https://www.typescriptlang.org/docs/handbook/2/generics.html) to define what type your `Josh` instance uses.
+
+```typescript
+import { Josh, MapProvider } from '@joshdb/core';
+
+interface User {
+ name: string;
+
+ email: string;
+}
+
+const josh = new Josh({
+ name: 'name',
+ provider: new MapProvider()
+});
+```
+
+## Provider Options
+
+Some providers may have options that can be passed when initiating them. These will be objects with their appropriate properties.
+
+Below is an example of how that might look.
+
+```javascript
+const josh = new Josh({
+ name: 'name',
+ provider: new Provider({
+ optionOne: 'optionOne',
+ optionTwo: 'optionTwo'
+ })
+});
+```
diff --git a/docs/guides/middleware/CreatingMiddleware.md b/docs/guides/middleware/CreatingMiddleware.md
new file mode 100644
index 00000000..278f3860
--- /dev/null
+++ b/docs/guides/middleware/CreatingMiddleware.md
@@ -0,0 +1,53 @@
+# Creating Middleware
+
+Now that you have the proper file structure and have an understanding of what a middleware is used for, let's get started!
+
+## Basic Usage
+
+You can make use of the **Middleware** class exported from `joshdb/core` by using `import` or `require`.
+
+**When using JavaScript**
+
+```javascript
+const { Middleware } = require('@joshdb/core');
+```
+
+**When using TypeScript**
+
+```typescript
+import { Middleware } from '@joshdb/core';
+```
+
+Each middleware is a class that extends the base `Middleware` class from Josh. You need to extend it.
+
+## Logger Middleware
+
+To create a middleware you need to create a file with name of the middleware, in our case `logger.js`. The path to this file would be `src/middleware/name/logger.js`.
+
+Below is a very simple example of a middleware class file which uses the `run` method. The `run` method runs on every **Josh** method before and after provider interaction, but _does not_ modify the payload. So even if you do modify the payload here, it will not do anything internally.
+
+**When using JavaScript**
+
+```javascript
+const { Middleware } = require('@joshdb/core');
+
+module.exports = class extends Middleware {
+ run(payload) {
+ console.log(payload);
+ }
+};
+```
+
+**When using TypeScript**
+
+```typescript
+import { Middleware, Payload } = from '@joshdb/core';
+
+export default class extends Middleware {
+ public run(payload: Payload) {
+ console.log(payload)
+ }
+}
+```
+
+If everything was done correctly, you should now see the `payload` be logged to the console during every **Josh** method executions.
diff --git a/docs/guides/middleware/GettingStarted.md b/docs/guides/middleware/GettingStarted.md
new file mode 100644
index 00000000..175f956f
--- /dev/null
+++ b/docs/guides/middleware/GettingStarted.md
@@ -0,0 +1,25 @@
+# Getting Started with Middleware
+
+## What is middleware?
+
+Middleware are simply a class that has methods to run before and after providers receive payloads from **Josh**. They make it possible to implement things like [`AutoEnsure`](https://github.com/josh-development/core/blob/main/src/middlewares/CoreAutoEnsure.ts).
+
+## File Structure
+
+Before creating any middleware, you should have a _main_ file, we'll make this `src/main.js`.
+
+Below is an example of what that file could look like.
+
+```javascript
+const { Josh } = require('@joshdb/core');
+
+const josh = new Josh({ name: 'name' });
+```
+
+## Using middleware
+
+To start taking advantage of middlewares you can simply utilize the `Josh#use()` method. This method takes a single parameter being your middleware class. Below is an example.
+
+```javascript
+josh.use(new MyMiddleware());
+```
diff --git a/index.js b/index.js
deleted file mode 100644
index 61ca6575..00000000
--- a/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-const Josh = require('./src/index.js');
-
-module.exports = Josh;
diff --git a/jest.config.ts b/jest.config.ts
new file mode 100644
index 00000000..2f774fa1
--- /dev/null
+++ b/jest.config.ts
@@ -0,0 +1,15 @@
+import type { Config } from '@jest/types';
+
+// eslint-disable-next-line @typescript-eslint/require-await
+export default async (): Promise => ({
+ displayName: 'unit test',
+ preset: 'ts-jest',
+ testEnvironment: 'node',
+ testRunner: 'jest-circus/runner',
+ testMatch: ['/tests/**/*.test.ts'],
+ globals: {
+ 'ts-jest': {
+ tsconfig: '/tests/tsconfig.json'
+ }
+ }
+});
diff --git a/package.json b/package.json
index f2a7af8f..af0bb5c3 100644
--- a/package.json
+++ b/package.json
@@ -1,37 +1,90 @@
{
"name": "@joshdb/core",
- "version": "1.2.7",
- "description": "Javascript Object Storage Helper",
- "main": "src/index.js",
+ "version": "2.0.0",
+ "description": "JavaScript Object Storage Helper",
+ "author": "Évelyne Lachance (https://evie.codes/)",
+ "contributors": [
+ "Hezekiah Hendry ",
+ "DanCodes (https://dancodes.online/)",
+ "Wilson (https://wilson.antti.codes/)"
+ ],
+ "license": "Apache-2.0",
+ "private": false,
+ "main": "dist/index.js",
+ "module": "dist/index.mjs",
+ "browser": "dist/index.umd.js",
+ "unpkg": "dist/index.umd.js",
+ "types": "dist/index.d.ts",
+ "exports": {
+ "import": "./dist/index.mjs",
+ "require": "./dist/index.js"
+ },
"scripts": {
- "test": "jest",
- "docs": "node ./docs/build.js"
+ "clean": "rimraf dist node_modules/.cache",
+ "docs": "typedoc",
+ "lint": "eslint src tests --ext ts --fix",
+ "format": "prettier --write {src,tests}/**/*.ts",
+ "test": "jest --verbose",
+ "update": "yarn upgrade-interactive --latest",
+ "build": "rollup -c rollup.bundle.ts",
+ "watch": "tsc -b src -w",
+ "prepublishOnly": "rollup-type-bundler",
+ "prepare": "husky install"
+ },
+ "dependencies": {
+ "@realware/utilities": "^2.0.2",
+ "@sapphire/utilities": "^3.2.1"
+ },
+ "devDependencies": {
+ "@commitlint/cli": "^16.1.0",
+ "@commitlint/config-conventional": "^16.0.0",
+ "@favware/rollup-type-bundler": "^1.0.7",
+ "@sapphire/eslint-config": "^4.0.11",
+ "@sapphire/ts-config": "^3.1.8",
+ "@types/jest": "^27.0.2",
+ "@types/node": "^17.0.10",
+ "eslint": "~8.6.0",
+ "husky": "^7.0.4",
+ "jest": "^27.4.7",
+ "jest-circus": "^27.4.6",
+ "lint-staged": "^12.3.1",
+ "prettier": "^2.4.1",
+ "pretty-quick": "^3.1.1",
+ "rollup": "^2.66.0",
+ "rollup-plugin-cleaner": "^1.0.0",
+ "rollup-plugin-typescript2": "^0.31.1",
+ "rollup-plugin-version-injector": "^1.3.3",
+ "ts-jest": "^27.1.3",
+ "ts-node": "^10.4.0",
+ "typedoc": "^0.22.11",
+ "typescript": "^4.5.5"
},
"repository": {
"type": "git",
- "url": "git+https://github.com/eslachance/josh.git"
+ "url": "git+https://github.com/josh-development/core.git"
},
- "author": "Evelyne Lachance (https://evie.codes/)",
- "license": "Apache-2.0",
+ "files": [
+ "dist",
+ "!dist/*.tsbuildinfo"
+ ],
+ "engines": {
+ "node": ">=16",
+ "npm": ">=6"
+ },
+ "keywords": [],
"bugs": {
- "url": "https://github.com/eslachance/josh/issues"
+ "url": "https://github.com/josh-development/core/issues"
},
- "homepage": "https://github.com/eslachance/josh#readme",
- "dependencies": {
- "lodash": "^4.17.20",
- "serialize-javascript": "^6.0.0"
+ "homepage": "https://github.com/josh-development/core",
+ "publishConfig": {
+ "access": "public"
},
- "devDependencies": {
- "eslint": "6.8.0",
- "eslint-config-airbnb-base": "^14.2.0",
- "eslint-config-standard": "^14.1.1",
- "eslint-plugin-import": "^2.22.0",
- "eslint-plugin-node": "^11.1.0",
- "eslint-plugin-promise": "^4.2.1",
- "eslint-plugin-standard": "^4.0.1",
- "jest": "^26.2.2",
- "jsdoc-to-markdown": "^6.0.1",
- "limax": "^2.1.0"
- },
- "types": "./typings/index.d.ts"
+ "commitlint": {
+ "extends": [
+ "@commitlint/config-conventional"
+ ]
+ },
+ "lint-staged": {
+ "*.ts": "eslint --fix --ext ts"
+ }
}
diff --git a/rollup.bundle.ts b/rollup.bundle.ts
new file mode 100644
index 00000000..c1cb79f2
--- /dev/null
+++ b/rollup.bundle.ts
@@ -0,0 +1,33 @@
+import { resolve } from 'path';
+import cleaner from 'rollup-plugin-cleaner';
+import typescript from 'rollup-plugin-typescript2';
+import versionInjector from 'rollup-plugin-version-injector';
+
+export default {
+ input: 'src/index.ts',
+ output: [
+ {
+ file: './dist/index.js',
+ format: 'cjs',
+ exports: 'named',
+ sourcemap: true
+ },
+ {
+ file: './dist/index.mjs',
+ format: 'es',
+ exports: 'named',
+ sourcemap: true
+ },
+ {
+ file: './dist/index.umd.js',
+ format: 'umd',
+ name: 'JoshCore',
+ sourcemap: true,
+ globals: {
+ '@sapphire/utilities': 'SapphireUtilities',
+ '@realware/utilities': 'RealwareUtilities'
+ }
+ }
+ ],
+ plugins: [cleaner({ targets: ['./dist'] }), typescript({ tsconfig: resolve(process.cwd(), 'src', 'tsconfig.json') }), versionInjector()]
+};
diff --git a/src/error.js b/src/error.js
deleted file mode 100644
index 7eefc13d..00000000
--- a/src/error.js
+++ /dev/null
@@ -1,12 +0,0 @@
-class CustomError extends Error {
-
- constructor(message, name = null) {
- super();
- Error.captureStackTrace(this, this.constructor);
- this.name = name || 'JoshError';
- this.message = message;
- }
-
-}
-
-module.exports = CustomError;
diff --git a/src/index.js b/src/index.js
deleted file mode 100644
index c8b57c99..00000000
--- a/src/index.js
+++ /dev/null
@@ -1,752 +0,0 @@
-const {
- merge,
- isArray,
- isFunction,
- get: _get,
- isNil,
- isObject,
- cloneDeep,
-} = require('lodash');
-const serialize = require('serialize-javascript');
-
-// Custom error codes with stack support.
-const Err = require('./error.js');
-
-// Package.json
-const pkgdata = require('../package.json');
-
-class Josh {
- /**
- * Initializes a new Josh, with options.
- * @param {Object} [options] Additional options an configurations.
- * @param {string} [options.name] Required. The name of the table in which to save the data.
- * @param {string} [options.provider] Required. A string with the name of the provider to use. Should not be already required,
- * as Josh takes care of doing that for you. *Must* be a valid provider that complies with the Provider API.
- * The provider needs to be installed separately with yarn or npm. See https://josh.evie.dev/providers for details.
- * @param {boolean} [options.ensureProps] defaults to `true`. If enabled and an inserted value is an object, using ensure() will also ensure that
- * every property present in the default object will be added to the value, if it's absent.
- * @param {*} [options.autoEnsure] default is disabled. When provided a value, essentially runs ensure(key, autoEnsure) automatically so you don't have to.
- * This is especially useful on get(), but will also apply on set(), and any array and object methods that interact with the database.
- * @param {Function} [options.serializer] Optional. If a function is provided, it will execute on the data when it is written to the database.
- * This is generally used to convert the value into a format that can be saved in the database, such as converting a complete class instance to just its ID.
- * This function may return the value to be saved, or a promise that resolves to that value (in other words, can be an async function).
- * @param {Function} [options.deserializer] Optional. If a function is provided, it will execute on the data when it is read from the database.
- * This is generally used to convert the value from a stored ID into a more complex object.
- * This function may return a value, or a promise that resolves to that value (in other words, can be an async function).
- * @example
- * const Josh = require("@joshdb/core");
- * const provider = require("@joshdb/sqlite");
- *
- * // sqlite-based database, with default options
- * const sqliteDB = new Josh({
- * name: 'mydatabase',
- * provider,
- * });
- */
- constructor(options = {}) {
- const { provider: Provider, name, providerOptions } = options;
-
- // Just grab the version from package.json
- this.version = pkgdata.version;
-
- // Fail miserably and weep if no provider, or no name, was given during initialization
- if (!Provider || !name) {
- throw new Err(
- 'Josh requires both a "name" and "provider" given in the options.',
- 'JoshOptionsError',
- );
- }
-
- // Verify if the provider given is an object, and is a valid provider for Josh...
- const intializedProvider = new Provider({ name, ...providerOptions });
- if (intializedProvider.constructor.name !== 'JoshProvider') {
- throw new Err(
- `The given Provider does not seem valid. I expected JoshProvider, but this was a ${intializedProvider.constructor.name}!`,
- 'JoshOptionsError',
- );
- }
-
- // Create a function that will be resolved whenever the provider's database is connected.
- this.defer = new Promise((resolve) => {
- this.ready = resolve;
- });
-
- // Configure shit
- this.provider = intializedProvider;
- this.name = name;
-
- this.all = Symbol('_all');
- this.off = Symbol('_off');
-
- this.serializer = options.serializer;
- this.deserializer = options.deserializer;
-
- this.autoEnsure = isNil(options.autoEnsure) ? this.off : options.autoEnsure;
- this.ensureProps = isNil(options.ensureProps) ? true : options.ensureProps;
-
- // Connect the provider to its database.
- this.provider.init().then(() => {
- this.ready();
- this.isReady = true;
- });
-
- // Initialize this property, to prepare for a possible destroy() call.
- // This is completely ignored in all situations except destroying Josh.
- this.isDestroyed = false;
- }
-
- /*
- * Internal Method. Verifies that the database is ready, assuming persistence is used.
- */
- async readyCheck() {
- await this.defer;
- if (this.isDestroyed) {
- throw new Err(
- 'This Josh has been destroyed and can no longer be used without being re-initialized.',
- 'JoshDestroyedError',
- );
- }
- }
-
- /*
- * Internal Method. Splits the key and path
- */
- getKeyAndPath(keyOrPath) {
- if (!keyOrPath) return [];
- const [key, ...path] = keyOrPath.split('.');
- return [key.toString(), path.length ? path.join('.') : null];
- }
-
- /**
- * Retrieves (fetches) a value from the database. If a simple key is provided, returns the value.
- * If a path is provided, will only return the value at that path, if it exists.
- * @param {string} keyOrPath Either a key, or full path, of the value you want to get.
- * For more information on how path works, see https://josh.evie.dev/path
- * @return {Promise<*>} Returns the value for the key or the value found at the specified path.
- */
- async get(keyOrPath) {
- await this.readyCheck();
- const [key, path] = this.getKeyAndPath(keyOrPath);
- if(isNil(key)) return null;
- let value;
- if (!(await this.has(keyOrPath))) {
- if (this.autoEnsure !== this.off) value = this.autoEnsure;
- else return null;
- } else {
- value = await this.provider.get(key);
- }
- value = this.deserializer ? await this.deserializer(value, key, path) : value;
- return !isNil(path) ? _get(value, path) : value;
- }
-
- // Not yet implemented (or implementable)
- // async query(opts) {
- // await this.readyCheck();
- // this.provider.query(opts);
- // }
-
- /**
- * Retrieve many values from the database.
- * If you provide `josh.all` as a value (josh being your variable for the database), the entire data set is returned.
- * @param {string[]|symbol} keys An array of keys to return, or `db.all` to retrieve them all.
- * @return {Promise