From ca771bd74bf0d39f012566d2c19d9af24d030555 Mon Sep 17 00:00:00 2001
From: Coroliov Oleg <1880059+ruscon@users.noreply.github.com>
Date: Fri, 31 Jan 2020 11:52:32 +0200
Subject: [PATCH 01/11] feat(client): add client with retry mechanism
---
.editorconfig | 16 ++
.env.example | 6 +
.gitattributes | 4 +
.github/ISSUE_TEMPLATE/1-bug-report.md | 45 ++++
.github/ISSUE_TEMPLATE/2-feature-request.md | 33 +++
.github/ISSUE_TEMPLATE/3-documentation.md | 27 +++
.github/ISSUE_TEMPLATE/4-question.md | 44 ++++
.github/PULL_REQUEST_TEMPLATE.md | 47 ++++
.github/config.yml | 15 ++
.github/workflows/ci.yml | 63 ++++++
.gitignore | 20 ++
.npmrc | 3 +
.nvmrc | 1 +
.nycrc.json | 8 +
.prettierignore | 6 +
.prettierrc.json | 9 +
.remarkrc | 13 ++
.versionrc.json | 31 +++
CHANGELOG.md | 1 +
CODE_OF_CONDUCT.md | 27 +++
CONTRIBUTING.md | 32 +++
LICENSE | 22 ++
README.md | 117 +++++++++-
bin/prepublish.js | 20 ++
commitlint.config.js | 8 +
package.json | 119 +++++++++++
src/client/index.ts | 2 +
src/client/square-client-factory.ts | 12 ++
src/client/square-client.ts | 201 ++++++++++++++++++
src/constants.ts | 9 +
src/exception/index.ts | 1 +
src/exception/square-exception.ts | 81 +++++++
src/index.ts | 6 +
src/interface/i-square-api-config.ts | 33 +++
src/interface/i-square-api-default-config.ts | 9 +
.../i-square-api-default-retry-config.ts | 6 +
src/interface/i-square-api-merged-config.ts | 3 +
src/interface/index.ts | 4 +
src/logger/i-logger.ts | 6 +
src/logger/index.ts | 2 +
src/logger/null-logger.ts | 19 ++
src/utils/common.utils.ts | 42 ++++
src/utils/index.ts | 2 +
src/utils/retry.utils.ts | 67 ++++++
test/common.opts.ts | 7 +
test/e2e/client/square-client.spec.ts | 116 ++++++++++
test/integration/client/square-client.spec.ts | 60 ++++++
test/mocha.opts | 7 +
.../unit/client/square-client-factory.spec.ts | 127 +++++++++++
test/unit/client/square-client.spec.ts | 190 +++++++++++++++++
test/unit/exception/square-exception.spec.ts | 173 +++++++++++++++
test/unit/index.spec.ts | 1 +
test/unit/logger/null-logger.spec.ts | 77 +++++++
test/unit/utils/common.utils.spec.ts | 9 +
test/unit/utils/retry.utils.spec.ts | 38 ++++
tsconfig.build.json | 10 +
tsconfig.json | 28 +++
tslint.json | 47 ++++
58 files changed, 2130 insertions(+), 2 deletions(-)
create mode 100644 .editorconfig
create mode 100644 .env.example
create mode 100644 .gitattributes
create mode 100644 .github/ISSUE_TEMPLATE/1-bug-report.md
create mode 100644 .github/ISSUE_TEMPLATE/2-feature-request.md
create mode 100644 .github/ISSUE_TEMPLATE/3-documentation.md
create mode 100644 .github/ISSUE_TEMPLATE/4-question.md
create mode 100644 .github/PULL_REQUEST_TEMPLATE.md
create mode 100644 .github/config.yml
create mode 100644 .github/workflows/ci.yml
create mode 100644 .gitignore
create mode 100644 .npmrc
create mode 100644 .nvmrc
create mode 100644 .nycrc.json
create mode 100644 .prettierignore
create mode 100644 .prettierrc.json
create mode 100644 .remarkrc
create mode 100644 .versionrc.json
create mode 100644 CHANGELOG.md
create mode 100644 CODE_OF_CONDUCT.md
create mode 100644 CONTRIBUTING.md
create mode 100644 LICENSE
create mode 100644 bin/prepublish.js
create mode 100644 commitlint.config.js
create mode 100644 package.json
create mode 100644 src/client/index.ts
create mode 100644 src/client/square-client-factory.ts
create mode 100644 src/client/square-client.ts
create mode 100644 src/constants.ts
create mode 100644 src/exception/index.ts
create mode 100644 src/exception/square-exception.ts
create mode 100644 src/index.ts
create mode 100644 src/interface/i-square-api-config.ts
create mode 100644 src/interface/i-square-api-default-config.ts
create mode 100644 src/interface/i-square-api-default-retry-config.ts
create mode 100644 src/interface/i-square-api-merged-config.ts
create mode 100644 src/interface/index.ts
create mode 100644 src/logger/i-logger.ts
create mode 100644 src/logger/index.ts
create mode 100644 src/logger/null-logger.ts
create mode 100644 src/utils/common.utils.ts
create mode 100644 src/utils/index.ts
create mode 100644 src/utils/retry.utils.ts
create mode 100644 test/common.opts.ts
create mode 100644 test/e2e/client/square-client.spec.ts
create mode 100644 test/integration/client/square-client.spec.ts
create mode 100644 test/mocha.opts
create mode 100644 test/unit/client/square-client-factory.spec.ts
create mode 100644 test/unit/client/square-client.spec.ts
create mode 100644 test/unit/exception/square-exception.spec.ts
create mode 100644 test/unit/index.spec.ts
create mode 100644 test/unit/logger/null-logger.spec.ts
create mode 100644 test/unit/utils/common.utils.spec.ts
create mode 100644 test/unit/utils/retry.utils.spec.ts
create mode 100644 tsconfig.build.json
create mode 100644 tsconfig.json
create mode 100644 tslint.json
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..c26529e
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,16 @@
+root = true
+
+[*]
+indent_style = space
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+max_line_length = 160
+indent_size = 4
+
+[*.md]
+trim_trailing_whitespace = false
+
+[*.{yml, yaml}]
+indent_size = 2
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..3541036
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,6 @@
+##################################################
+# These variables are used for integration tests #
+##################################################
+SQUARE_BASE_URL = https://connect.squareup.com
+SQUARE_SANDBOX_BASE_URL = https://connect.squareupsandbox.com
+SQUARE_ACCESS_TOKEN =
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..988cdb9
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,4 @@
+* text=auto
+* text eol=lf
+*.png binary
+*.gif binary
diff --git a/.github/ISSUE_TEMPLATE/1-bug-report.md b/.github/ISSUE_TEMPLATE/1-bug-report.md
new file mode 100644
index 0000000..bff59a2
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/1-bug-report.md
@@ -0,0 +1,45 @@
+---
+name: π Bug Report
+about: Report a reproducible bug
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+
+
+## Describe the bug
+
+
+
+## To Reproduce
+
+
+
+```typescript
+// Example code here
+```
+
+## Expected behavior
+
+
+
+## Environment:
+
+* Square Connect Plus Version: x.y.z
+* Node version: x.y.z
+
+## Additional context/Screenshots
+
+
diff --git a/.github/ISSUE_TEMPLATE/2-feature-request.md b/.github/ISSUE_TEMPLATE/2-feature-request.md
new file mode 100644
index 0000000..3532d51
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/2-feature-request.md
@@ -0,0 +1,33 @@
+---
+name: β¨ Feature Request
+about: Suggest an idea or feature
+title: 'feat: '
+labels: feature
+assignees: ruscon
+
+---
+
+## Issue Number: [N/A]
+
+## Does this PR introduce a breaking change
+
+ [ ] Yes
+ [ ] No
+
+
+
+## Is your feature request related to a problem? Please describe.
+
+
+
+## Describe the solution you'd like
+
+
+
+## Describe alternatives you've considered
+
+
+
+## Additional context
+
+
diff --git a/.github/ISSUE_TEMPLATE/3-documentation.md b/.github/ISSUE_TEMPLATE/3-documentation.md
new file mode 100644
index 0000000..8f3ddf3
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/3-documentation.md
@@ -0,0 +1,27 @@
+---
+name: π Documentation
+about: Found a typo or something that isn't crystal clear in our docs?
+title: 'docs: '
+labels: documentation
+assignees: ruscon
+
+---
+
+
+
+## Section/Content To Improve
+
+
+
+## Suggested Improvement
+
+
+
+## Relevant File(s):
+
+
diff --git a/.github/ISSUE_TEMPLATE/4-question.md b/.github/ISSUE_TEMPLATE/4-question.md
new file mode 100644
index 0000000..25bcc5d
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/4-question.md
@@ -0,0 +1,44 @@
+---
+name: π€ Question
+about: Get help using Square Connect Plus
+title: 'question: '
+labels: question
+assignees: ''
+
+---
+
+
+
+## Environment:
+
+* Square Connect Plus Version: x.y.z
+* Node version: x.y.z
+
+## Describe the issue
+
+
+
+## Example Code
+
+
+
+```typescript
+// Example code here
+```
+
+## Expected behavior, if applicable
+
+
+
+## Additional context/screenshots
+
+
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..53b5c75
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+## Summary
+
+
+
+## Context
+
+
diff --git a/.github/config.yml b/.github/config.yml
new file mode 100644
index 0000000..3015da1
--- /dev/null
+++ b/.github/config.yml
@@ -0,0 +1,15 @@
+updateDocsComment: >
+ Thanks for opening this pull request! The maintainers of this repository would appreciate it if you would update some of our documentation based on your changes.
+
+updateDocsWhiteList:
+ - bug
+ - fix
+ - Backport
+ - dev
+ - Update
+ - WIP
+ - chore
+
+updateDocsTargetFiles:
+ - README
+ - docs/
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..7a46d41
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,63 @@
+name: CI
+
+on: ["push", "pull_request"]
+
+jobs:
+ commitlint:
+ runs-on: ubuntu-latest
+
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ steps:
+ - name: Clone repository
+ uses: actions/checkout@v1
+ with:
+ fetch-depth: 1
+
+ - name: Lints Pull Request commits
+ uses: wagoid/commitlint-github-action@v1.2.2
+
+ build:
+ runs-on: ubuntu-latest
+
+ strategy:
+ fail-fast: false
+ matrix:
+ node-version: [8.x, 10.x, 12.x, 13.x]
+
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ steps:
+ - name: Clone repository
+ uses: actions/checkout@v1
+ with:
+ fetch-depth: 1
+
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v1
+ with:
+ node-version: ${{ matrix.node-version }}
+
+ - run: node --version
+ - run: npm --version
+
+ - name: Install npm dependencies
+ run: npm i
+
+ - name: Lint code
+ run: npm run lint
+
+ - name: Run tests
+ run: npm run coverage:all
+ env:
+ SQUARE_BASE_URL: ${{ secrets.SQUARE_BASE_URL }}
+ SQUARE_SANDBOX_BASE_URL: ${{ secrets.SQUARE_SANDBOX_BASE_URL }}
+ SQUARE_ACCESS_TOKEN: ${{ secrets.SQUARE_ACCESS_TOKEN }}
+
+ - name: Run Coveralls
+ uses: coverallsapp/github-action@master
+ if: startsWith(matrix.node-version, '12.')
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b070ed4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,20 @@
+# dependencies
+dist/
+node_modules/
+
+# IDE
+/.idea
+/.vscode
+
+# misc
+.DS_Store
+
+# tests
+test.ts
+/test-reports
+/coverage
+/.nyc_output
+/log
+!/bin
+.env
+package-lock.json
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..df3c14a
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,3 @@
+save-exact=false
+scripts-prepend-node-path=true
+package-lock=false
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 0000000..dae199a
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+v12
diff --git a/.nycrc.json b/.nycrc.json
new file mode 100644
index 0000000..7d71873
--- /dev/null
+++ b/.nycrc.json
@@ -0,0 +1,8 @@
+{
+ "extension": [".ts"],
+ "include": ["src/**/*.ts"],
+ "exclude": ["**/*.d.ts", "src/index.ts", "src/interface/index.ts", "src/test.ts"],
+ "reporter": ["lcov", "text-summary", "html"],
+ "cache": false,
+ "all": true
+}
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..736cc1e
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,6 @@
+.nyc_output
+coverage
+dist
+node_modules
+package-lock.json
+src/test.ts
diff --git a/.prettierrc.json b/.prettierrc.json
new file mode 100644
index 0000000..b190b4d
--- /dev/null
+++ b/.prettierrc.json
@@ -0,0 +1,9 @@
+{
+ "tabWidth": 4,
+ "useTabs": false,
+ "semi": true,
+ "singleQuote": true,
+ "trailingComma": "all",
+ "arrowParens": "always",
+ "printWidth": 160
+}
diff --git a/.remarkrc b/.remarkrc
new file mode 100644
index 0000000..d1b862a
--- /dev/null
+++ b/.remarkrc
@@ -0,0 +1,13 @@
+{
+ "settings": {
+ "emphasis": "*",
+ "strong": "*",
+ "bullet": "*"
+ },
+ "plugins": [
+ "remark-lint-emphasis-marker",
+ "remark-lint-strong-marker",
+ "remark-github",
+ "remark-frontmatter"
+ ]
+}
diff --git a/.versionrc.json b/.versionrc.json
new file mode 100644
index 0000000..db321f2
--- /dev/null
+++ b/.versionrc.json
@@ -0,0 +1,31 @@
+{
+ "types": [
+ {
+ "type": "feat",
+ "section": "Features"
+ },
+ {
+ "type": "fix",
+ "section": "Bug Fixes"
+ },
+ {
+ "type": "test",
+ "section": "Tests",
+ "hidden": true
+ },
+ {
+ "type": "build",
+ "section": "Build System",
+ "hidden": true
+ },
+ {
+ "type": "chore",
+ "section": "Chore",
+ "hidden": true
+ },
+ {
+ "type": "ci",
+ "hidden": true
+ }
+ ]
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1 @@
+
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..7a099a3
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,27 @@
+## Contributor Code of Conduct
+
+As contributors and maintainers of this project, we pledge to respect all people
+who contribute through reporting issues, posting feature requests, updating
+documentation, submitting pull requests or patches, and other activities.
+
+We are committed to making participation in this project a harassment-free
+experience for everyone, regardless of level of experience, gender, gender
+identity and expression, sexual orientation, disability, personal appearance,
+body size, race, age, or religion.
+
+Examples of unacceptable behavior by participants include the use of sexual
+language or imagery, derogatory comments or personal attacks, trolling, public
+or private harassment, insults, or other unprofessional conduct.
+
+Project maintainers have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct. Project maintainers who do not follow the
+Code of Conduct may be removed from the project team.
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by opening an issue or contacting one or more of the project
+maintainers.
+
+This Code of Conduct is adapted from the [Contributor
+Covenant](http:contributor-covenant.org), version 1.0.0, available at
+
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..06696b1
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,32 @@
+# Contributing
+
+First of all, **thank you** for contributing, **you are awesome**!
+
+Here are a few rules to follow in order to ease code reviews, and discussions before
+maintainers accept and merge your work.
+
+You MUST follow the [TypeScript](https://www.typescriptlang.org/docs/home.html) standards. If you don't know about any of them, you
+should really read the recommendations.
+
+Check your node version `node -v`.
+You MUST have `">=10.13"`, because of https://github.com/okonet/lint-staged#v10.
+
+You MUST run the `npm run coverage` command.
+
+You MUST write (or update) unit tests.
+
+You MUST run the `npm run commit` hook to add a new commit.
+
+You SHOULD write documentation.
+
+Please, write [commit messages that make
+sense](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html),
+and [rebase your branch](http://git-scm.com/book/en/Git-Branching-Rebasing)
+before submitting your Pull Request.
+
+One may ask you to [squash your
+commits](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html)
+too. This is used to "clean" your Pull Request before merging it (we don't want
+commits such as `fix tests`, `fix 2`, `fix 3`, etc.).
+
+Thank you!
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..be7581f
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+(The MIT License)
+
+Copyright (c) 2017 Kamil MyΕliwiec
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
index 04332de..ecf6068 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,115 @@
-# square-connect-plus
-Typescript library which extends the official Square Connect APIs library with additional functionality
+[](https://github.com/goparrot/square-connect-plus/actions?query=branch%3Amaster+event%3Apush+workflow%3ACI)
+[](https://coveralls.io/github/goparrot/square-connect-plus?branch=master)
+[](https://www.npmjs.com/package/@goparrot/square-connect-plus)
+[](https://greenkeeper.io/)
+[](http://commitizen.github.io/cz-cli/)
+[](https://conventionalcommits.org)
+
+# Square Connect Plus
+
+**Square Connect Plus** is a Typescript library which extends the official Square Connect APIs library with additional functionality.
+The library does not modify request and response payload.
+
+* [Installation](#installation)
+* [Usage](#usage)
+* [Versioning](#versioning)
+* [Contributing](#contributing)
+* [Unit Tests](#unit-tests)
+* [Background](#background)
+* [License](#license)
+
+## Installation
+
+ $ npm i @goparrot/square-connect-plus square-connect
+
+## Usage
+
+### Simple example
+
+```typescript
+import { SquareClient } from '@goparrot/square-connect-plus';
+import { ListLocationsResponse } from 'square-connect';
+
+const accessToken: string = `${process.env.SQUARE_ACCESS_TOKEN}`;
+const squareClient: SquareClient = new SquareClient(accessToken);
+
+(async () => {
+ try {
+ const listLocationsResponse: ListLocationsResponse = await squareClient.getLocationsApi().listLocations();
+ if (listLocationsResponse.errors) {
+ throw new Error(`cant fetch locations`);
+ }
+
+ console.info('locations', listLocationsResponse.locations);
+ } catch (error) {
+ console.error(error);
+ // or error as string with stack + request and response payload
+ // console.error(`${error.stack}\npayload: ${error.toString()}`);
+ }
+})();
+```
+
+### Advanced example
+
+```typescript
+import { SquareClient, exponentialDelay, retryCondition } from '@goparrot/square-connect-plus';
+
+const accessToken: string = `${process.env.SQUARE_ACCESS_TOKEN}`;
+const squareClient: SquareClient = new SquareClient(accessToken, {
+ retry: {
+ maxRetries: 10,
+ },
+ originClient: {
+ timeout: 10000,
+ },
+ logger: console,
+});
+```
+
+## Available Options
+
+### `retry` Options
+
+| Name | Type | Default | Description |
+| -------------- | ---------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| maxRetries | `Number` | `6` | The number of times to retry before failing. |
+| retryCondition | `Function` | `retryCondition` | A callback to further control if a request should be retried. By default, the built-in `retryCondition` function is used. |
+| retryDelay | `Function` | `exponentialDelay` | A callback to further control the delay between retried requests. By default, the built-in `exponentialDelay` function is used ([Exponential Backoff](https://developers.google.com/analytics/devguides/reporting/core/v3/errors#backoff)). |
+
+### `originClient` Options
+
+A set of possible settings for the original library.
+
+\| --- \| --- \| --- \| --- \|
+| basePath | `String` \| `https://connect.squareup.com` | The base URL against which to resolve every API call's (relative) path. |
+| defaultHeaders | `Array` \| `{ 'User-Agent': 'Square-Connect-Javascript/2.20191217.0' }` | The default HTTP headers to be included for all API calls. |
+| timeout | `Number` \| `15000` | The default HTTP timeout for all API calls. |
+| cache | `Boolean` \| `true` | If set to false an additional timestamp parameter is added to all API GET calls to prevent browser caching. |
+| enableCookies | `Boolean` \| `false` | If set to true, the client will save the cookies from each server response, and return them in the next request. |
+
+### `logger` Option
+
+By default, the built-in `NullLogger` class is used.
+You can use any logger that fits the built-in `ILogger` interface
+
+## Versioning
+
+Square Connect Plus follows [Semantic Versioning](http://semver.org/).
+
+## Contributing
+
+See [`CONTRIBUTING`](https://github.com/goparrot/square-connect-plus/blob/master/CONTRIBUTING.md#contributing) file.
+
+## Unit Tests
+
+In order to run the test suite, install the development dependencies:
+
+ $ npm i
+
+Then, run the following command:
+
+ $ npm run coverage
+
+## License
+
+Square Connect Plus is [MIT licensed](LICENSE).
diff --git a/bin/prepublish.js b/bin/prepublish.js
new file mode 100644
index 0000000..fe2d2c1
--- /dev/null
+++ b/bin/prepublish.js
@@ -0,0 +1,20 @@
+const fs = require('fs');
+
+const originalPackage = require('../package.json');
+originalPackage.module = './index.js';
+originalPackage.main = './index.js';
+originalPackage.types = './index.d.ts';
+delete originalPackage.scripts;
+delete originalPackage.devDependencies;
+delete originalPackage.config;
+delete originalPackage.husky;
+delete originalPackage.files;
+delete originalPackage.directories;
+delete originalPackage['lint-staged'];
+
+fs.writeFileSync('./dist/package.json', JSON.stringify(originalPackage, null, ' '));
+
+const copyFiles = ['README.md'];
+for (const file of copyFiles) {
+ fs.copyFileSync(`./${file}`, `./dist/${file}`);
+}
diff --git a/commitlint.config.js b/commitlint.config.js
new file mode 100644
index 0000000..4da806b
--- /dev/null
+++ b/commitlint.config.js
@@ -0,0 +1,8 @@
+module.exports = {
+ extends: ['@commitlint/config-conventional'],
+ scopes: [{ name: 'client' }, { name: 'tutorial' }],
+ scopeOverrides: {
+ fix: [{ name: 'style' }, { name: 'unit' }, { name: 'e2e' }, { name: 'integration' }],
+ },
+ allowCustomScopes: true,
+};
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..d3c7b7d
--- /dev/null
+++ b/package.json
@@ -0,0 +1,119 @@
+{
+ "name": "@goparrot/square-connect-plus",
+ "description": "Extends the official Square Connect APIs Javascript library with additional functionality",
+ "version": "0.0.0",
+ "author": "Coroliov Oleg",
+ "license": "MIT",
+ "private": false,
+ "bugs": {
+ "url": "https://github.com/goparrot/square-connect-plus/issues"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/goparrot/square-connect-plus.git"
+ },
+ "keywords": [
+ "node",
+ "typescript",
+ "square-connect",
+ "retry"
+ ],
+ "engines": {
+ "node": ">=8.9.0"
+ },
+ "main": "src/index.ts",
+ "husky": {
+ "hooks": {
+ "commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
+ "pre-commit": "npm run pre-commit",
+ "post-commit": "git update-index --again"
+ }
+ },
+ "lint-staged": {
+ "*.{ts,json}": [
+ "npm run format"
+ ]
+ },
+ "config": {
+ "commitizen": {
+ "path": "cz-conventional-changelog"
+ }
+ },
+ "scripts": {
+ "commit": "git-cz",
+ "test": "mocha 'test/unit/**/*.spec.ts' 'test/e2e/**/*.spec.ts'",
+ "test:fast": "TS_NODE_TRANSPILE_ONLY=true npm run test",
+ "test:unit": "mocha 'test/unit/**/*.spec.ts'",
+ "test:e2e": "mocha 'test/e2e/**/*.spec.ts'",
+ "test:integration": "mocha --timeout 15000 'test/integration/**/*.spec.ts'",
+ "test:integration:fast": "mocha --timeout 15000 'test/integration/**/*.spec.ts'",
+ "test:all": "mocha --timeout 15000 'test/**/*.spec.ts'",
+ "test:all:fast": "TS_NODE_TRANSPILE_ONLY=true mocha --timeout 15000 'test/**/*.spec.ts'",
+ "coverage": "nyc npm test",
+ "coverage:fast": "TS_NODE_TRANSPILE_ONLY=true nyc npm run test:fast",
+ "coverage:all": "nyc npm run test:all",
+ "coverage:all:fast": "TS_NODE_TRANSPILE_ONLY=true nyc npm run test:all:fast",
+ "format": "prettier \"**/*.{ts,js,json}\" --write",
+ "format:staged": "lint-staged",
+ "lint": "npm run lint:config:check && tslint -c tslint.json -p tsconfig.json --format stylish",
+ "lint:config:check": "tslint-config-prettier-check ./tslint.json",
+ "build": "rimraf dist && tsc -p tsconfig.build.json",
+ "remark": "remark README.md CHANGELOG.md CONTRIBUTING.md CODE_OF_CONDUCT.md .github/ -o -f -q && git add .",
+ "pre-commit": "git add . && npm run format:staged && npm run remark && npm run lint && npm run coverage:all && npm run build",
+ "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0 && remark CHANGELOG.md -o -f -q && git add CHANGELOG.md",
+ "prepublishOnly": "echo \"use 'npm run publish'\" && exit 1",
+ "publish": "npm run build && node bin/prepublish.js && npm publish dist",
+ "publish:dev": "npm run publish --tag dev",
+ "publish:dev:dry": "npm run publish:dev --dry-run",
+ "version": "echo \"use 'npm run release'\" && exit 1",
+ "release": "standard-version && git push && git push --tags && npm run publish && npm run github-release",
+ "release:dry": "npm run publish:dev:dry && standard-version --dry-run",
+ "github-release": "conventional-github-releaser -p angular"
+ },
+ "peerDependencies": {
+ "square-connect": "^2.20190814.0"
+ },
+ "dependencies": {},
+ "devDependencies": {
+ "@commitlint/cli": "^8.2.0",
+ "@commitlint/config-conventional": "^8.2.0",
+ "@commitlint/travis-cli": "^8.2.0",
+ "@types/chai": "^4.2.8",
+ "@types/chai-as-promised": "^7.1.2",
+ "@types/mocha": "^7.0.1",
+ "@types/node": "^13.5.3",
+ "@types/sinon": "^7.5.0",
+ "@types/square-connect": "^2.20190814.3",
+ "@types/superagent": "^4.1.4",
+ "chai": "^4.2.0",
+ "chai-as-promised": "^7.1.1",
+ "commitizen": "^4.0.0",
+ "conventional-changelog-cli": "^2.0.27",
+ "conventional-github-releaser": "^3.1.3",
+ "cz-conventional-changelog": "^3.1.0",
+ "dotenv-safe": "^8.2.0",
+ "husky": "^4.2.1",
+ "lint-staged": "^10.0.7",
+ "mocha": "^7.0.1",
+ "mocha-junit-reporter": "^1.23.0",
+ "nock": "^11.7.2",
+ "nyc": "^15.0.0",
+ "prettier": "^1.18.2",
+ "remark-cli": "^7.0.0",
+ "remark-frontmatter": "^1.3.2",
+ "remark-github": "^8.0.0",
+ "remark-lint-emphasis-marker": "^1.0.3",
+ "remark-lint-strong-marker": "^1.0.3",
+ "rimraf": "^3.0.1",
+ "sinon": "^8.1.1",
+ "source-map-support": "^0.5.16",
+ "square-connect": "^2.20200122.0",
+ "standard-version": "^7.0.0",
+ "superagent": "^5.2.1",
+ "ts-node": "^8.4.1",
+ "tsconfig-paths": "^3.9.0",
+ "tslint": "^6.0.0",
+ "tslint-config-prettier": "^1.18.0",
+ "typescript": "^3.7.5"
+ }
+}
diff --git a/src/client/index.ts b/src/client/index.ts
new file mode 100644
index 0000000..5adabda
--- /dev/null
+++ b/src/client/index.ts
@@ -0,0 +1,2 @@
+export * from './square-client';
+export * from './square-client-factory';
diff --git a/src/client/square-client-factory.ts b/src/client/square-client-factory.ts
new file mode 100644
index 0000000..d6972e3
--- /dev/null
+++ b/src/client/square-client-factory.ts
@@ -0,0 +1,12 @@
+import { ISquareClientConfig } from '../interface';
+import { SquareClient } from './square-client';
+
+export class SquareClientFactory {
+ static create(accessToken: string, config: ISquareClientConfig = {}): SquareClient {
+ return new SquareClient(accessToken, config);
+ }
+
+ create(accessToken: string, config: ISquareClientConfig = {}): SquareClient {
+ return SquareClientFactory.create(accessToken, config);
+ }
+}
diff --git a/src/client/square-client.ts b/src/client/square-client.ts
new file mode 100644
index 0000000..0920b44
--- /dev/null
+++ b/src/client/square-client.ts
@@ -0,0 +1,201 @@
+import {
+ ApiClient,
+ ApplePayApi,
+ CatalogApi,
+ CheckoutApi,
+ CustomersApi,
+ EmployeesApi,
+ InventoryApi,
+ LaborApi,
+ LocationsApi,
+ MobileAuthorizationApi,
+ OAuthApi,
+ OrdersApi,
+ PaymentsApi,
+ RefundsApi,
+ TransactionsApi,
+} from 'square-connect';
+import { ISquareClientConfig, ISquareClientDefaultConfig, ISquareClientMergedConfig } from '../interface';
+import { ILogger, NullLogger } from '../logger';
+import { exponentialDelay, makeRetryable, mergeDeepProps, retryCondition } from '../utils';
+
+export class SquareClient {
+ private originApiClient: ApiClient;
+ private readonly config: ISquareClientMergedConfig;
+ private readonly defaultConfig: ISquareClientDefaultConfig = {
+ retry: {
+ maxRetries: 6,
+ retryDelay: exponentialDelay,
+ },
+ originClient: {
+ timeout: 15000,
+ },
+ };
+
+ constructor(private readonly accessToken: string, config: ISquareClientConfig = {}) {
+ this.config = mergeDeepProps(this.defaultConfig, config);
+ this.config.logger = config.logger;
+ }
+
+ /**
+ * Generate unique idempotency key (format: reference-timestampWithMilliseconds)
+ */
+ static generateIdempotencyKey(reference: string): string {
+ return [reference, Date.now().toString()].join('-');
+ }
+
+ getConfig(): ISquareClientMergedConfig {
+ return this.config;
+ }
+
+ getOriginApiClient(): ApiClient {
+ return this.originApiClient ?? (this.originApiClient = this.createOriginApiClient(this.accessToken, this.config));
+ }
+
+ getApplePayApi(): ApplePayApi {
+ const retryableMethods: string[] = [];
+
+ return this.proxify(new ApplePayApi(this.getOriginApiClient()), retryableMethods);
+ }
+
+ getCatalogApi(): CatalogApi {
+ const retryableMethods: string[] = ['batchRetrieveCatalogObjects', 'catalogInfo', 'listCatalog', 'retrieveCatalogObject', 'searchCatalogObjects'];
+
+ return this.proxify(new CatalogApi(this.getOriginApiClient()), retryableMethods);
+ }
+
+ getCheckoutApi(): CheckoutApi {
+ const retryableMethods: string[] = [];
+
+ return this.proxify(new CheckoutApi(this.getOriginApiClient()), retryableMethods);
+ }
+
+ getCustomersApi(): CustomersApi {
+ // createCustomerCard should not be retryable (#GP-2400)
+ const retryableMethods: string[] = ['listCustomers', 'retrieveCustomer', 'searchCustomers', 'deleteCustomerCard'];
+
+ return this.proxify(new CustomersApi(this.getOriginApiClient()), retryableMethods);
+ }
+
+ getEmployeesApi(): EmployeesApi {
+ const retryableMethods: string[] = ['listEmployees', 'retrieveEmployee'];
+
+ return this.proxify(new EmployeesApi(this.getOriginApiClient()), retryableMethods);
+ }
+
+ getInventoryApi(): InventoryApi {
+ const retryableMethods: string[] = [
+ 'batchRetrieveInventoryChanges',
+ 'batchRetrieveInventoryCounts',
+ 'retrieveInventoryAdjustment',
+ 'retrieveInventoryChanges',
+ 'retrieveInventoryCount',
+ 'retrieveInventoryPhysicalCount',
+ ];
+
+ return this.proxify(new InventoryApi(this.getOriginApiClient()), retryableMethods);
+ }
+
+ getLaborApi(): LaborApi {
+ const retryableMethods: string[] = [
+ 'getBreakType',
+ 'getEmployeeWage',
+ 'getShift',
+ 'listBreakTypes',
+ 'listEmployeeWages',
+ 'listWorkweekConfigs',
+ 'searchShifts',
+ ];
+
+ return this.proxify(new LaborApi(this.getOriginApiClient()), retryableMethods);
+ }
+
+ getLocationsApi(): LocationsApi {
+ const retryableMethods: string[] = ['listLocations'];
+
+ return this.proxify(new LocationsApi(this.getOriginApiClient()), retryableMethods);
+ }
+
+ getMobileAuthorizationApi(): MobileAuthorizationApi {
+ const retryableMethods: string[] = [];
+
+ return this.proxify(new MobileAuthorizationApi(this.getOriginApiClient()), retryableMethods);
+ }
+
+ getOAuthApi(): OAuthApi {
+ const retryableMethods: string[] = [];
+
+ return this.proxify(new OAuthApi(this.getOriginApiClient()), retryableMethods);
+ }
+
+ getOrdersApi(): OrdersApi {
+ const retryableMethods: string[] = ['batchRetrieveOrders', 'searchOrders', 'createOrder', 'payOrder'];
+
+ return this.proxify(new OrdersApi(this.getOriginApiClient()), retryableMethods);
+ }
+
+ getPaymentsApi(): PaymentsApi {
+ const retryableMethods: string[] = ['getPayment', 'listPayments', 'createPayment', 'cancelPayment'];
+
+ return this.proxify(new PaymentsApi(this.getOriginApiClient()), retryableMethods);
+ }
+
+ getRefundsApi(): RefundsApi {
+ const retryableMethods: string[] = ['getPaymentRefund', 'listPaymentRefunds', 'refundPayment'];
+
+ return this.proxify(new RefundsApi(this.getOriginApiClient()), retryableMethods);
+ }
+
+ getTransactionsApi(): TransactionsApi {
+ const retryableMethods: string[] = ['listRefunds', 'listTransactions', 'retrieveTransaction'];
+
+ return this.proxify(new TransactionsApi(this.getOriginApiClient()), retryableMethods);
+ }
+
+ private createOriginApiClient(accessToken: string, config: ISquareClientConfig): ApiClient {
+ const apiClient: ApiClient = new ApiClient();
+ apiClient.authentications.oauth2.accessToken = accessToken;
+
+ return mergeDeepProps(apiClient, config.originClient);
+ }
+
+ private getLogger(): ILogger {
+ return this.config.logger ?? (this.config.logger = new NullLogger());
+ }
+
+ private proxify(api: T, retryableMethods: string[]): T {
+ let globalStack: string = '';
+ try {
+ throw new Error();
+ } catch (err) {
+ globalStack += err.stack.slice(6); // remove "Error:"
+ }
+
+ const handler: ProxyHandler = {
+ get: (target: T, apiMethodName: string): any => {
+ if (!retryableMethods.includes(apiMethodName)) {
+ return target[apiMethodName];
+ }
+
+ return async (...args: any[]): Promise => {
+ const requestFn: () => any = target[apiMethodName].bind(target, ...args);
+
+ this.getLogger().debug(`Square api request: ${JSON.stringify({ apiMethodName, args })}`);
+
+ try {
+ return await makeRetryable(requestFn, {
+ maxRetries: this.config.retry.maxRetries,
+ retryDelay: this.config.retry.retryDelay,
+ retryCondition: this.config.retry.retryCondition ?? retryCondition,
+ });
+ } catch (err) {
+ err.stack += globalStack;
+ throw err;
+ }
+ };
+ },
+ };
+
+ return new Proxy(api, handler);
+ }
+}
diff --git a/src/constants.ts b/src/constants.ts
new file mode 100644
index 0000000..4234712
--- /dev/null
+++ b/src/constants.ts
@@ -0,0 +1,9 @@
+import { ErrorCodeType } from 'square-connect';
+
+export const retryableErrorCodes: ReadonlyArray = Object.freeze([
+ 'RATE_LIMITED',
+ 'REQUEST_TIMEOUT',
+ 'GATEWAY_TIMEOUT',
+ 'SERVICE_UNAVAILABLE',
+ 'INTERNAL_SERVER_ERROR',
+]);
diff --git a/src/exception/index.ts b/src/exception/index.ts
new file mode 100644
index 0000000..718fa79
--- /dev/null
+++ b/src/exception/index.ts
@@ -0,0 +1 @@
+export * from './square-exception';
diff --git a/src/exception/square-exception.ts b/src/exception/square-exception.ts
new file mode 100644
index 0000000..a2c82bd
--- /dev/null
+++ b/src/exception/square-exception.ts
@@ -0,0 +1,81 @@
+import { ModelError } from 'square-connect';
+import { Response, SuperAgentRequest } from 'superagent';
+
+export class SquareException extends Error {
+ retries?: number;
+ url?: string;
+ method?: string;
+ statusCode: number;
+ apiError: ModelError;
+ requestArgs?: any;
+
+ constructor(
+ data?: { retries?: number; url?: string; method?: string; statusCode?: number; apiError?: ModelError; requestArgs?: any },
+ originError?: Error,
+ ) {
+ super();
+
+ this.name = this.constructor.name;
+
+ this.retries = data?.retries || 0;
+ this.url = data?.url;
+ this.method = data?.method?.toUpperCase();
+ this.statusCode = data?.statusCode || 500;
+ this.apiError = data?.apiError || { category: 'API_ERROR', code: 'SERVICE_UNAVAILABLE', detail: 'Square API error' };
+ this.message = this.apiError?.detail ?? this.apiError.code ?? originError?.message ?? 'Square API error';
+ this.requestArgs = data?.requestArgs;
+
+ // Error.captureStackTrace(this);
+ }
+
+ static createFromSuperAgentError(
+ error: Error & { response?: Response & { request?: SuperAgentRequest }; code?: string },
+ retries: number,
+ ): SquareException {
+ const response: (Response & { request?: SuperAgentRequest }) | undefined = error?.response;
+ const request: SuperAgentRequest | undefined = response?.request;
+
+ if (response) {
+ // console.dir(request, {depth: undefined});
+ // console.log(JSON.parse(JSON.stringify(response)));
+ return new SquareException(
+ {
+ retries,
+ url: request?.url,
+ statusCode: response?.status,
+ method: request?.method,
+ apiError: response?.body?.errors?.[0],
+ // @ts-ignore
+ requestArgs: request?._data,
+ },
+ error,
+ );
+ } else if ('ECONNABORTED' === error.code) {
+ return new SquareException({
+ retries,
+ statusCode: 500,
+ apiError: { category: 'API_ERROR', code: 'GATEWAY_TIMEOUT', detail: 'Square API timeout' },
+ });
+ }
+
+ return new SquareException(
+ {
+ retries,
+ statusCode: 500,
+ apiError: { category: 'API_ERROR', code: 'SERVICE_UNAVAILABLE', detail: 'Square API error' },
+ },
+ error,
+ );
+ }
+
+ toString(): string {
+ return JSON.stringify({
+ retries: this.retries,
+ url: this.url,
+ method: this.method,
+ statusCode: this.statusCode,
+ requestArgs: this.requestArgs,
+ apiError: this.apiError,
+ });
+ }
+}
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 0000000..650c8fc
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,6 @@
+export * from './exception';
+export * from './interface';
+export * from './logger';
+export * from './utils';
+export * from './client';
+export * from './constants';
diff --git a/src/interface/i-square-api-config.ts b/src/interface/i-square-api-config.ts
new file mode 100644
index 0000000..27d5afa
--- /dev/null
+++ b/src/interface/i-square-api-config.ts
@@ -0,0 +1,33 @@
+import { ILogger } from '../logger';
+import { IRetriesOptions } from '../utils';
+
+export interface ISquareClientConfig {
+ retry?: Partial;
+ originClient?: {
+ /**
+ * The base URL against which to resolve every API call's (relative) path.
+ */
+ basePath?: string;
+
+ /**
+ * The default HTTP headers to be included for all API calls.
+ */
+ defaultHeaders?: { [key: string]: string };
+
+ /**
+ * The default HTTP timeout for all API calls.
+ */
+ timeout?: number;
+
+ /**
+ * If set to false an additional timestamp parameter is added to all API GET calls to prevent browser caching.
+ */
+ cache?: boolean;
+
+ /**
+ * If set to true, the client will save the cookies from each server response, and return them in the next request.
+ */
+ enableCookies?: boolean;
+ };
+ logger?: ILogger;
+}
diff --git a/src/interface/i-square-api-default-config.ts b/src/interface/i-square-api-default-config.ts
new file mode 100644
index 0000000..43e8b91
--- /dev/null
+++ b/src/interface/i-square-api-default-config.ts
@@ -0,0 +1,9 @@
+import { ISquareClientConfig } from './i-square-api-config';
+import { ISquareClientDefaultRetryConfig } from './i-square-api-default-retry-config';
+
+export interface ISquareClientDefaultConfig extends ISquareClientConfig {
+ retry: ISquareClientDefaultRetryConfig;
+ originClient: {
+ timeout: number;
+ };
+}
diff --git a/src/interface/i-square-api-default-retry-config.ts b/src/interface/i-square-api-default-retry-config.ts
new file mode 100644
index 0000000..8aee8fc
--- /dev/null
+++ b/src/interface/i-square-api-default-retry-config.ts
@@ -0,0 +1,6 @@
+import { IRetriesOptions } from '../utils';
+
+export interface ISquareClientDefaultRetryConfig extends Partial {
+ maxRetries: number;
+ retryDelay: (retryCount: number) => number;
+}
diff --git a/src/interface/i-square-api-merged-config.ts b/src/interface/i-square-api-merged-config.ts
new file mode 100644
index 0000000..b0c5405
--- /dev/null
+++ b/src/interface/i-square-api-merged-config.ts
@@ -0,0 +1,3 @@
+import { ISquareClientDefaultConfig } from './i-square-api-default-config';
+
+export interface ISquareClientMergedConfig extends ISquareClientDefaultConfig {}
diff --git a/src/interface/index.ts b/src/interface/index.ts
new file mode 100644
index 0000000..6d69679
--- /dev/null
+++ b/src/interface/index.ts
@@ -0,0 +1,4 @@
+export * from './i-square-api-config';
+export * from './i-square-api-default-retry-config';
+export * from './i-square-api-default-config';
+export * from './i-square-api-merged-config';
diff --git a/src/logger/i-logger.ts b/src/logger/i-logger.ts
new file mode 100644
index 0000000..52b7c47
--- /dev/null
+++ b/src/logger/i-logger.ts
@@ -0,0 +1,6 @@
+export interface ILogger {
+ debug(message: string, meta?: any): any;
+ info(message: string, meta?: any): any;
+ warn(message: string, meta?: any): any;
+ error(message: string, meta?: any): any;
+}
diff --git a/src/logger/index.ts b/src/logger/index.ts
new file mode 100644
index 0000000..6d95c9c
--- /dev/null
+++ b/src/logger/index.ts
@@ -0,0 +1,2 @@
+export * from './null-logger';
+export * from './i-logger';
diff --git a/src/logger/null-logger.ts b/src/logger/null-logger.ts
new file mode 100644
index 0000000..06bf610
--- /dev/null
+++ b/src/logger/null-logger.ts
@@ -0,0 +1,19 @@
+import { ILogger } from './i-logger';
+
+export class NullLogger implements ILogger {
+ debug(): any {
+ //
+ }
+
+ info(): any {
+ //
+ }
+
+ warn(): any {
+ //
+ }
+
+ error(): any {
+ //
+ }
+}
diff --git a/src/utils/common.utils.ts b/src/utils/common.utils.ts
new file mode 100644
index 0000000..d7e2b95
--- /dev/null
+++ b/src/utils/common.utils.ts
@@ -0,0 +1,42 @@
+export async function sleep(timeout: number): Promise {
+ return new Promise((resolve: any): any => setTimeout(resolve, timeout));
+}
+
+/**
+ * Simple object check.
+ * @returns {boolean}
+ */
+export function isObject(item?: T): item is T {
+ return !!item && 'object' === typeof item && !Array.isArray(item);
+}
+
+/**
+ * @link {https://stackoverflow.com/a/34749873/3408246}
+ * Deep merge props of two objects
+ */
+export function mergeDeepProps(target: T, ...sources: S[]): T {
+ if (!sources.length) return target;
+ const source: S | undefined = sources.shift();
+
+ if (isObject(target) && isObject(source)) {
+ for (const key in source) {
+ if (isObject(source[key])) {
+ // @ts-ignore
+ if (!target[key]) {
+ Object.assign(target, { [key]: {} });
+ }
+
+ mergeDeepProps(
+ // @ts-ignore
+ target[key],
+ // @ts-ignore
+ source[key],
+ );
+ } else {
+ Object.assign(target, { [key]: source[key] });
+ }
+ }
+ }
+
+ return mergeDeepProps(target, ...sources);
+}
diff --git a/src/utils/index.ts b/src/utils/index.ts
new file mode 100644
index 0000000..d5f66ed
--- /dev/null
+++ b/src/utils/index.ts
@@ -0,0 +1,2 @@
+export * from './common.utils';
+export * from './retry.utils';
diff --git a/src/utils/retry.utils.ts b/src/utils/retry.utils.ts
new file mode 100644
index 0000000..35b1c7f
--- /dev/null
+++ b/src/utils/retry.utils.ts
@@ -0,0 +1,67 @@
+import { retryableErrorCodes } from '../constants';
+import { SquareException } from '../exception';
+import { sleep } from './common.utils';
+
+export interface IRetriesOptions {
+ /** @default 3 */
+ maxRetries: number;
+ /** @default exponentialDelay */
+ retryDelay: (retryCount: number) => number;
+ retryCondition: (error: SquareException, maxRetries: number, retries: number) => Promise;
+}
+
+/**
+ * @return {number} - delay in milliseconds
+ */
+export function exponentialDelay(retryNumber: number): number {
+ const delay: number = Math.pow(2, retryNumber) * 100;
+ const randomSum: number = delay * 0.2 * Math.random(); // 0-20% of the delay
+
+ return delay + randomSum;
+}
+
+/**
+ * add the ability to retry the request
+ */
+export async function makeRetryable(promiseFn: (...arg: any[]) => Promise, params: IRetriesOptions): Promise {
+ let retries: number = 0;
+
+ async function retry(): Promise {
+ try {
+ return await promiseFn();
+ } catch (error) {
+ const squareException: SquareException = SquareException.createFromSuperAgentError(error, retries);
+
+ if (await params.retryCondition(squareException, params.maxRetries, retries)) {
+ retries++;
+ const delay: number = exponentialDelay(retries);
+ await sleep(delay);
+
+ return retry();
+ }
+
+ throw squareException;
+ }
+ }
+
+ return retry();
+}
+
+export function isRetryableException(error: Error): boolean {
+ if (!(error instanceof SquareException)) {
+ return false;
+ }
+
+ const isRetryableResponseStatusCode: boolean = 429 === error.statusCode || (error.statusCode >= 500 && 501 !== error.statusCode);
+ const isIdempotentRequestMethod: boolean = ['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE'].includes(error.method?.toUpperCase() || '');
+
+ return isRetryableResponseStatusCode && (isIdempotentRequestMethod || retryableErrorCodes.includes(error.apiError.code));
+}
+
+export async function retryCondition(error: SquareException, maxRetries: number, retries: number): Promise {
+ if (isRetryableException(error) && maxRetries > retries) {
+ return true;
+ }
+
+ throw error;
+}
diff --git a/test/common.opts.ts b/test/common.opts.ts
new file mode 100644
index 0000000..59bfe15
--- /dev/null
+++ b/test/common.opts.ts
@@ -0,0 +1,7 @@
+import * as chai from 'chai';
+// @ts-ignore
+import chaiAsPromised from 'chai-as-promised';
+
+chai.use(chaiAsPromised);
+// @ts-ignore
+global.should = chai.should();
diff --git a/test/e2e/client/square-client.spec.ts b/test/e2e/client/square-client.spec.ts
new file mode 100644
index 0000000..20d3ba2
--- /dev/null
+++ b/test/e2e/client/square-client.spec.ts
@@ -0,0 +1,116 @@
+import nock from 'nock';
+import { SquareClient } from '../../../src/client';
+import { SquareException } from '../../../src/exception';
+import { ISquareClientConfig } from '../../../src/interface';
+import { exponentialDelay } from '../../../src/utils';
+
+describe('SquareClient (e2e)', (): void => {
+ const accessToken: string = 'test';
+ const basePath: string = `${process.env.SQUARE_SANDBOX_BASE_URL}`;
+
+ const config: ISquareClientConfig = {
+ retry: {
+ maxRetries: 2,
+ retryDelay: exponentialDelay,
+ },
+ originClient: {
+ timeout: 100,
+ cache: false,
+ enableCookies: false,
+ basePath,
+ },
+ };
+
+ afterEach((): void => {
+ nock.cleanAll();
+ });
+
+ it('should NOT retry 501 http status', async (): Promise => {
+ nock(basePath)
+ .get(/.*/)
+ .times(1000)
+ .reply(501);
+
+ return new SquareClient(accessToken, config)
+ .getLocationsApi()
+ .listLocations()
+ .should.eventually.be.rejectedWith(SquareException)
+ .and.have.property('retries', 0);
+ });
+
+ it('should NOT retry 400 http status', async (): Promise => {
+ nock(basePath)
+ .get(/.*/)
+ .times(1000)
+ .reply(400);
+
+ return new SquareClient(accessToken, config)
+ .getLocationsApi()
+ .listLocations()
+ .should.eventually.be.rejectedWith(SquareException)
+ .and.have.property('retries', 0);
+ });
+
+ it('should retry 429 http status', async (): Promise => {
+ nock(basePath)
+ .get(/.*/)
+ .times(1000)
+ .reply(429, {
+ errors: [
+ {
+ category: 'RATE_LIMIT_ERROR',
+ code: 'RATE_LIMITED',
+ detail: 'fake 429 error',
+ },
+ ],
+ });
+
+ return new SquareClient(accessToken, config)
+ .getLocationsApi()
+ .listLocations()
+ .should.eventually.be.rejectedWith(SquareException, 'fake 429 error')
+ .and.have.property('retries', config.retry?.maxRetries);
+ });
+
+ it('should retry 500 http status', async (): Promise => {
+ nock(basePath)
+ .get(/.*/)
+ .times(1000)
+ .reply(500);
+
+ return new SquareClient(accessToken, config)
+ .getLocationsApi()
+ .listLocations()
+ .should.eventually.be.rejectedWith(SquareException)
+ .and.have.property('retries', config.retry?.maxRetries);
+ });
+
+ it('should retry 503 http status', async (): Promise => {
+ nock(basePath)
+ .get(/.*/)
+ .times(1000)
+ .reply(503);
+
+ return new SquareClient(accessToken, config)
+ .getLocationsApi()
+ .listLocations()
+ .should.eventually.be.rejectedWith(SquareException)
+ .and.have.property('retries', config.retry?.maxRetries);
+ });
+
+ it('should retry if request timeout', async (): Promise => {
+ nock(basePath)
+ .get(/.*/)
+ .times(1000)
+ .delay({
+ head: 3000,
+ })
+ .reply(200, 'OK');
+
+ return new SquareClient(accessToken, config)
+ .getLocationsApi()
+ .listLocations()
+ .should.eventually.be.rejectedWith(SquareException, 'Square API timeout')
+ .and.have.property('retries', config.retry?.maxRetries);
+ });
+});
diff --git a/test/integration/client/square-client.spec.ts b/test/integration/client/square-client.spec.ts
new file mode 100644
index 0000000..793c3c1
--- /dev/null
+++ b/test/integration/client/square-client.spec.ts
@@ -0,0 +1,60 @@
+import { SearchCustomersRequest } from 'square-connect';
+import { SquareClient } from '../../../src/client';
+import { SquareException } from '../../../src/exception';
+import { ISquareClientConfig } from '../../../src/interface';
+
+describe('SquareClient (integration)', (): void => {
+ const accessToken: string = `${process.env.SQUARE_ACCESS_TOKEN}`;
+ const basePath: string = `${process.env.SQUARE_BASE_URL}`;
+
+ const config: ISquareClientConfig = {
+ retry: {
+ maxRetries: 1,
+ },
+ originClient: {
+ timeout: 10000,
+ basePath,
+ },
+ };
+
+ describe('#getLocationsApi', (): void => {
+ it('should retry by timeout', async (): Promise => {
+ return new SquareClient(accessToken, {
+ ...config,
+ ...{
+ originClient: {
+ timeout: 1,
+ },
+ },
+ })
+ .getLocationsApi()
+ .listLocations()
+ .should.eventually.be.rejectedWith(SquareException, 'Square API timeout');
+ });
+
+ it('should retrieve data', async (): Promise => {
+ return new SquareClient(accessToken, config)
+ .getLocationsApi()
+ .listLocations()
+ .should.eventually.be.fulfilled.and.have.property('locations');
+ });
+ });
+
+ describe('#getCustomersApi', (): void => {
+ it('should be rejected with ModelError', async (): Promise => {
+ const query: SearchCustomersRequest = {
+ limit: 1,
+ query: {
+ sort: {
+ order: 'WRONG_VALUE' as any,
+ },
+ },
+ };
+
+ return new SquareClient(accessToken, config)
+ .getCustomersApi()
+ .searchCustomers(query)
+ .should.eventually.be.rejectedWith(Error, '`WRONG_VALUE` is not a valid enum value for `query.sort.order`.');
+ });
+ });
+});
diff --git a/test/mocha.opts b/test/mocha.opts
new file mode 100644
index 0000000..204a418
--- /dev/null
+++ b/test/mocha.opts
@@ -0,0 +1,7 @@
+--ui bdd
+-r ts-node/register
+-r source-map-support/register
+-r tsconfig-paths/register
+-r dotenv-safe/config
+-r ./test/common.opts.ts
+--project tsconfig.spec.json
diff --git a/test/unit/client/square-client-factory.spec.ts b/test/unit/client/square-client-factory.spec.ts
new file mode 100644
index 0000000..7e586a4
--- /dev/null
+++ b/test/unit/client/square-client-factory.spec.ts
@@ -0,0 +1,127 @@
+import { ApiClient, CustomersApi, LocationsApi, OrdersApi, PaymentsApi, RefundsApi } from 'square-connect';
+import { SquareClient, SquareClientFactory } from '../../../src/client';
+import { ISquareClientConfig } from '../../../src/interface';
+import { exponentialDelay } from '../../../src/utils';
+
+describe('SquareClientFactory (unit)', (): void => {
+ const accessToken: string = 'test';
+
+ const config: ISquareClientConfig = {
+ retry: {
+ maxRetries: 1,
+ retryDelay: exponentialDelay,
+ },
+ originClient: {
+ timeout: 1000,
+ cache: false,
+ enableCookies: true,
+ basePath: 'https://connect.squareupsandbox.com',
+ },
+ logger: console,
+ };
+
+ describe('#create', (): void => {
+ it('should be statically init with accessToken only (default config)', async (): Promise => {
+ return SquareClientFactory.create(accessToken).should.be.instanceOf(SquareClient);
+ });
+
+ it('should be init with accessToken only (default config)', async (): Promise => {
+ return new SquareClientFactory().create(accessToken).should.be.instanceOf(SquareClient);
+ });
+
+ it('should be statically init with accessToken and config', async (): Promise => {
+ return SquareClientFactory.create(accessToken, config).should.be.instanceOf(SquareClient);
+ });
+
+ it('should be init with accessToken and config', async (): Promise => {
+ return new SquareClientFactory().create(accessToken, config).should.be.instanceOf(SquareClient);
+ });
+ });
+
+ describe('#generateIdempotencyKey', (): void => {
+ it('should return string', async (): Promise => {
+ const reference: string = 'test';
+ return SquareClient.generateIdempotencyKey(reference)
+ .should.be.match(new RegExp(`^${reference}-.*`))
+ .and.lengthOf(18);
+ });
+ });
+
+ describe('#getConfig', (): void => {
+ it('should return default configuration', async (): Promise => {
+ return new SquareClient(accessToken).getConfig().should.be.deep.eq({
+ retry: {
+ maxRetries: 6,
+ retryDelay: exponentialDelay,
+ },
+ originClient: {
+ timeout: 15000,
+ },
+ logger: undefined,
+ });
+ });
+
+ it('should return custom configuration', async (): Promise => {
+ return new SquareClient(accessToken, config).getConfig().should.be.deep.eq(config);
+ });
+ });
+
+ describe('#getOriginApiClient', (): void => {
+ it('should return ApiClient with default configuration', async (): Promise => {
+ const apiClient: ApiClient = new SquareClient(accessToken).getOriginApiClient();
+
+ apiClient.should.be.instanceOf(ApiClient);
+ apiClient.should.have.property('timeout', 15000);
+ apiClient.should.have.property('cache', true);
+ apiClient.should.have.property('enableCookies', false);
+ apiClient.should.have.property('defaultHeaders').and.should.be.a('object');
+ return apiClient.should.and.have.property('basePath', 'https://connect.squareup.com');
+ });
+
+ it('should return ApiClient with custom configuration', async (): Promise => {
+ const apiClient: ApiClient = new SquareClient(accessToken, config).getOriginApiClient();
+
+ apiClient.should.be.instanceOf(ApiClient);
+ apiClient.should.have.property('timeout', config.originClient?.timeout);
+ apiClient.should.have.property('cache', config.originClient?.cache);
+ apiClient.should.have.property('enableCookies', config.originClient?.enableCookies);
+ apiClient.should.have.property('defaultHeaders').and.should.be.a('object');
+ return apiClient.should.and.have.property('basePath', config.originClient?.basePath);
+ });
+
+ it('should return the same object on second call', async (): Promise => {
+ const squareClient: SquareClient = new SquareClient(accessToken);
+ return squareClient.getOriginApiClient().should.be.deep.eq(squareClient.getOriginApiClient());
+ });
+ });
+
+ describe('#getLocationsApi', (): void => {
+ it('should return LocationsApi', async (): Promise => {
+ return new SquareClient(accessToken).getLocationsApi().should.be.instanceOf(LocationsApi);
+ });
+ });
+
+ describe('#getCustomersApi', (): void => {
+ it('should return CustomersApi', async (): Promise => {
+ return new SquareClient(accessToken).getCustomersApi().should.be.instanceOf(CustomersApi);
+ });
+ });
+
+ describe('#getPaymentsApi', (): void => {
+ it('should return PaymentsApi', async (): Promise => {
+ return new SquareClient(accessToken).getPaymentsApi().should.be.instanceOf(PaymentsApi);
+ });
+ });
+
+ describe('#getRefundsApi', (): void => {
+ it('should return RefundsApi', async (): Promise => {
+ return new SquareClient(accessToken).getRefundsApi().should.be.instanceOf(RefundsApi);
+ });
+ });
+
+ describe('#getOrdersApi', (): void => {
+ it('should return OrdersApi', async (): Promise => {
+ return new SquareClient(accessToken).getOrdersApi().should.be.instanceOf(OrdersApi);
+ });
+ });
+});
diff --git a/test/unit/client/square-client.spec.ts b/test/unit/client/square-client.spec.ts
new file mode 100644
index 0000000..f59cd23
--- /dev/null
+++ b/test/unit/client/square-client.spec.ts
@@ -0,0 +1,190 @@
+import {
+ ApiClient,
+ ApplePayApi,
+ CatalogApi,
+ CheckoutApi,
+ CustomersApi,
+ EmployeesApi,
+ InventoryApi,
+ LaborApi,
+ LocationsApi,
+ MobileAuthorizationApi,
+ OAuthApi,
+ OrdersApi,
+ PaymentsApi,
+ RefundsApi,
+ TransactionsApi,
+} from 'square-connect';
+import { SquareClient } from '../../../src/client';
+import { ISquareClientConfig } from '../../../src/interface';
+import { exponentialDelay } from '../../../src/utils';
+
+describe('SquareClient (unit)', (): void => {
+ const accessToken: string = 'test';
+ const basePath: string = `${process.env.SQUARE_SANDBOX_BASE_URL}`;
+
+ const config: ISquareClientConfig = {
+ retry: {
+ maxRetries: 1,
+ retryDelay: exponentialDelay,
+ },
+ originClient: {
+ timeout: 1000,
+ cache: false,
+ enableCookies: false,
+ basePath,
+ },
+ logger: console,
+ };
+
+ describe('#constructor', (): void => {
+ it('should be init with accessToken only (default config)', async (): Promise => {
+ return new SquareClient(accessToken).should.be.instanceOf(SquareClient);
+ });
+
+ it('should be init with accessToken and config', async (): Promise => {
+ return new SquareClient(accessToken, config).should.be.instanceOf(SquareClient);
+ });
+ });
+
+ describe('#generateIdempotencyKey', (): void => {
+ it('should return string', async (): Promise => {
+ const reference: string = 'test';
+ return SquareClient.generateIdempotencyKey(reference)
+ .should.be.match(new RegExp(`^${reference}-.*`))
+ .and.lengthOf(18);
+ });
+ });
+
+ describe('#getConfig', (): void => {
+ it('should return default configuration', async (): Promise => {
+ return new SquareClient(accessToken).getConfig().should.be.deep.eq({
+ retry: {
+ maxRetries: 6,
+ retryDelay: exponentialDelay,
+ },
+ originClient: {
+ timeout: 15000,
+ },
+ logger: undefined,
+ });
+ });
+
+ it('should return custom configuration', async (): Promise => {
+ return new SquareClient(accessToken, config).getConfig().should.be.deep.eq(config);
+ });
+ });
+
+ describe('#getOriginApiClient', (): void => {
+ it('should return ApiClient with default configuration', async (): Promise => {
+ const apiClient: ApiClient = new SquareClient(accessToken).getOriginApiClient();
+
+ apiClient.should.be.instanceOf(ApiClient);
+ apiClient.should.have.property('timeout', 15000);
+ apiClient.should.have.property('cache', true);
+ apiClient.should.have.property('enableCookies', false);
+ apiClient.should.have.property('defaultHeaders').and.should.be.a('object');
+ return apiClient.should.and.have.property('basePath', 'https://connect.squareup.com');
+ });
+
+ it('should return ApiClient with custom configuration', async (): Promise => {
+ const apiClient: ApiClient = new SquareClient(accessToken, config).getOriginApiClient();
+
+ apiClient.should.be.instanceOf(ApiClient);
+ apiClient.should.have.property('timeout', config.originClient?.timeout);
+ apiClient.should.have.property('cache', config.originClient?.cache);
+ apiClient.should.have.property('enableCookies', config.originClient?.enableCookies);
+ apiClient.should.have.property('defaultHeaders').and.should.be.a('object');
+ return apiClient.should.and.have.property('basePath', config.originClient?.basePath);
+ });
+
+ it('should return the same object on second call', async (): Promise => {
+ const squareClient: SquareClient = new SquareClient(accessToken);
+ return squareClient.getOriginApiClient().should.be.deep.eq(squareClient.getOriginApiClient());
+ });
+ });
+
+ describe('#getApplePayApi', (): void => {
+ it('should return ApplePayApi', async (): Promise => {
+ return new SquareClient(accessToken).getApplePayApi().should.be.instanceOf(ApplePayApi);
+ });
+ });
+
+ describe('#getCatalogApi', (): void => {
+ it('should return CatalogApi', async (): Promise => {
+ return new SquareClient(accessToken).getCatalogApi().should.be.instanceOf(CatalogApi);
+ });
+ });
+
+ describe('#getCheckoutApi', (): void => {
+ it('should return CheckoutApi', async (): Promise => {
+ return new SquareClient(accessToken).getCheckoutApi().should.be.instanceOf(CheckoutApi);
+ });
+ });
+
+ describe('#getCustomersApi', (): void => {
+ it('should return CustomersApi', async (): Promise => {
+ return new SquareClient(accessToken).getCustomersApi().should.be.instanceOf(CustomersApi);
+ });
+ });
+
+ describe('#getEmployeesApi', (): void => {
+ it('should return EmployeesApi', async (): Promise => {
+ return new SquareClient(accessToken).getEmployeesApi().should.be.instanceOf(EmployeesApi);
+ });
+ });
+
+ describe('#getInventoryApi', (): void => {
+ it('should return InventoryApi', async (): Promise => {
+ return new SquareClient(accessToken).getInventoryApi().should.be.instanceOf(InventoryApi);
+ });
+ });
+
+ describe('#getLaborApi', (): void => {
+ it('should return LaborApi', async (): Promise => {
+ return new SquareClient(accessToken).getLaborApi().should.be.instanceOf(LaborApi);
+ });
+ });
+
+ describe('#getLocationsApi', (): void => {
+ it('should return LocationsApi', async (): Promise => {
+ return new SquareClient(accessToken).getLocationsApi().should.be.instanceOf(LocationsApi);
+ });
+ });
+
+ describe('#getMobileAuthorizationApi', (): void => {
+ it('should return MobileAuthorizationApi', async (): Promise => {
+ return new SquareClient(accessToken).getMobileAuthorizationApi().should.be.instanceOf(MobileAuthorizationApi);
+ });
+ });
+
+ describe('#getOAuthApi', (): void => {
+ it('should return OAuthApi', async (): Promise => {
+ return new SquareClient(accessToken).getOAuthApi().should.be.instanceOf(OAuthApi);
+ });
+ });
+
+ describe('#getOrdersApi', (): void => {
+ it('should return OrdersApi', async (): Promise => {
+ return new SquareClient(accessToken).getOrdersApi().should.be.instanceOf(OrdersApi);
+ });
+ });
+
+ describe('#getPaymentsApi', (): void => {
+ it('should return PaymentsApi', async (): Promise => {
+ return new SquareClient(accessToken).getPaymentsApi().should.be.instanceOf(PaymentsApi);
+ });
+ });
+
+ describe('#getRefundsApi', (): void => {
+ it('should return RefundsApi', async (): Promise => {
+ return new SquareClient(accessToken).getRefundsApi().should.be.instanceOf(RefundsApi);
+ });
+ });
+
+ describe('#getTransactionsApi', (): void => {
+ it('should return TransactionsApi', async (): Promise => {
+ return new SquareClient(accessToken).getTransactionsApi().should.be.instanceOf(TransactionsApi);
+ });
+ });
+});
diff --git a/test/unit/exception/square-exception.spec.ts b/test/unit/exception/square-exception.spec.ts
new file mode 100644
index 0000000..a0a1fe1
--- /dev/null
+++ b/test/unit/exception/square-exception.spec.ts
@@ -0,0 +1,173 @@
+import { SquareException } from '../../../src/exception';
+
+class SuperAgentError extends Error {
+ constructor(message: string, readonly response?: any, readonly code?: string) {
+ super(message);
+ }
+}
+
+describe('SquareException (unit)', (): void => {
+ describe('#createFromSuperAgentError', (): void => {
+ it('should be ok without args', async (): Promise => {
+ const squareException: SquareException = new SquareException();
+
+ squareException.should.be.instanceOf(SquareException);
+ squareException.should.have.property('message', 'Square API error');
+ squareException.should.have.property('retries', 0);
+ squareException.should.have.property('statusCode', 500);
+ return squareException.should.have.property('apiError').and.eql({ category: 'API_ERROR', code: 'SERVICE_UNAVAILABLE', detail: 'Square API error' });
+ });
+ });
+
+ describe('#createFromSuperAgentError', (): void => {
+ it('should be ok with Error', async (): Promise => {
+ const error: Error = new Error('test message');
+ const squareException: SquareException = SquareException.createFromSuperAgentError(error, 0);
+
+ squareException.should.be.instanceOf(SquareException);
+ squareException.should.have.property('message', 'Square API error');
+ squareException.should.have.property('retries', 0);
+ squareException.should.have.property('statusCode', 500);
+ return squareException.should.have.property('apiError').and.eql({ category: 'API_ERROR', code: 'SERVICE_UNAVAILABLE', detail: 'Square API error' });
+ });
+
+ it('should be ok with Error with ECONNABORTED error code', async (): Promise => {
+ const error: SuperAgentError = new SuperAgentError('test message', undefined, 'ECONNABORTED');
+
+ const squareException: SquareException = SquareException.createFromSuperAgentError(error, 1);
+
+ squareException.should.be.instanceOf(SquareException);
+ squareException.should.have.property('message', 'Square API timeout');
+ squareException.should.have.property('retries', 1);
+ squareException.should.have.property('statusCode', 500);
+ squareException.should.have.property('url', undefined);
+ squareException.should.have.property('method', undefined);
+ return squareException.should.have.property('apiError').and.eql({
+ category: 'API_ERROR',
+ code: 'GATEWAY_TIMEOUT',
+ detail: 'Square API timeout',
+ });
+ });
+
+ it('should be ok with Error with empty response object', async (): Promise => {
+ const error: SuperAgentError = new SuperAgentError('test message', {});
+
+ const squareException: SquareException = SquareException.createFromSuperAgentError(error, 1);
+
+ squareException.should.be.instanceOf(SquareException);
+ squareException.should.have.property('message', 'Square API error');
+ squareException.should.have.property('retries', 1);
+ squareException.should.have.property('statusCode', 500);
+ squareException.should.have.property('url', undefined);
+ squareException.should.have.property('method', undefined);
+ return squareException.should.have.property('apiError').and.eql({
+ category: 'API_ERROR',
+ code: 'SERVICE_UNAVAILABLE',
+ detail: 'Square API error',
+ });
+ });
+
+ it('should be ok with Error with response status', async (): Promise => {
+ const error: SuperAgentError = new SuperAgentError('test message', {
+ status: 429,
+ });
+
+ const squareException: SquareException = SquareException.createFromSuperAgentError(error, 1);
+
+ squareException.should.be.instanceOf(SquareException);
+ squareException.should.have.property('message', 'Square API error');
+ squareException.should.have.property('retries', 1);
+ squareException.should.have.property('statusCode', 429);
+ squareException.should.have.property('url', undefined);
+ squareException.should.have.property('method', undefined);
+ return squareException.should.have.property('apiError').and.eql({
+ category: 'API_ERROR',
+ code: 'SERVICE_UNAVAILABLE',
+ detail: 'Square API error',
+ });
+ });
+
+ it('should be ok with Error with response body', async (): Promise => {
+ const error: SuperAgentError = new SuperAgentError('test message', {
+ status: 429,
+ body: {},
+ });
+
+ const squareException: SquareException = SquareException.createFromSuperAgentError(error, 1);
+
+ squareException.should.be.instanceOf(SquareException);
+ squareException.should.have.property('message', 'Square API error');
+ squareException.should.have.property('retries', 1);
+ squareException.should.have.property('statusCode', 429);
+ squareException.should.have.property('url', undefined);
+ squareException.should.have.property('method', undefined);
+ return squareException.should.have.property('apiError').and.eql({
+ category: 'API_ERROR',
+ code: 'SERVICE_UNAVAILABLE',
+ detail: 'Square API error',
+ });
+ });
+
+ it('should be ok with Error with response body.errors', async (): Promise => {
+ const error: SuperAgentError = new SuperAgentError('test message', {
+ status: 429,
+ body: {
+ errors: [
+ {
+ category: 'RATE_LIMIT_ERROR',
+ code: 'RATE_LIMITED',
+ detail: 'fake 429 error',
+ },
+ ],
+ },
+ });
+
+ const squareException: SquareException = SquareException.createFromSuperAgentError(error, 1);
+
+ squareException.should.be.instanceOf(SquareException);
+ squareException.should.have.property('message', 'fake 429 error');
+ squareException.should.have.property('retries', 1);
+ squareException.should.have.property('statusCode', 429);
+ squareException.should.have.property('url', undefined);
+ squareException.should.have.property('method', undefined);
+ return squareException.should.have.property('apiError').and.eql({
+ category: 'RATE_LIMIT_ERROR',
+ code: 'RATE_LIMITED',
+ detail: 'fake 429 error',
+ });
+ });
+
+ it('should be ok with Error + response and request properties ', async (): Promise => {
+ const error: SuperAgentError = new SuperAgentError('test message', {
+ status: 429,
+ body: {
+ errors: [
+ {
+ category: 'RATE_LIMIT_ERROR',
+ code: 'RATE_LIMITED',
+ detail: 'fake 429 error',
+ },
+ ],
+ },
+ request: {
+ url: 'url',
+ method: 'get',
+ },
+ });
+
+ const squareException: SquareException = SquareException.createFromSuperAgentError(error, 1);
+
+ squareException.should.be.instanceOf(SquareException);
+ squareException.should.have.property('message', 'fake 429 error');
+ squareException.should.have.property('retries', 1);
+ squareException.should.have.property('statusCode', 429);
+ squareException.should.have.property('url', 'url');
+ squareException.should.have.property('method', 'GET');
+ return squareException.should.have.property('apiError').and.eql({
+ category: 'RATE_LIMIT_ERROR',
+ code: 'RATE_LIMITED',
+ detail: 'fake 429 error',
+ });
+ });
+ });
+});
diff --git a/test/unit/index.spec.ts b/test/unit/index.spec.ts
new file mode 100644
index 0000000..6faa584
--- /dev/null
+++ b/test/unit/index.spec.ts
@@ -0,0 +1 @@
+export * from '../../src';
diff --git a/test/unit/logger/null-logger.spec.ts b/test/unit/logger/null-logger.spec.ts
new file mode 100644
index 0000000..2ac01b2
--- /dev/null
+++ b/test/unit/logger/null-logger.spec.ts
@@ -0,0 +1,77 @@
+import { ILogger, NullLogger } from '../../../src/logger';
+
+describe('NullLogger (unit)', (): void => {
+ describe('#constructor', (): void => {
+ it('should be init without args', async (): Promise => {
+ return new NullLogger().should.be.instanceOf(NullLogger);
+ });
+ });
+
+ describe('#debug', (): void => {
+ it('should have debug method', async (): Promise => {
+ const logger: ILogger = new NullLogger();
+ return logger.debug.should.be.a('function');
+ });
+
+ it('should be ok', async (): Promise => {
+ const logger: ILogger = new NullLogger();
+ try {
+ logger.debug('test');
+ return true.should.be.true;
+ } catch (e) {
+ return true.should.be.false;
+ }
+ });
+ });
+
+ describe('#info', (): void => {
+ it('should have debug method', async (): Promise => {
+ const logger: ILogger = new NullLogger();
+ return logger.info.should.be.a('function');
+ });
+
+ it('should be ok', async (): Promise => {
+ const logger: ILogger = new NullLogger();
+ try {
+ logger.info('test');
+ return true.should.be.true;
+ } catch (e) {
+ return true.should.be.false;
+ }
+ });
+ });
+
+ describe('#warn', (): void => {
+ it('should have warn method', async (): Promise => {
+ const logger: ILogger = new NullLogger();
+ return logger.warn.should.be.a('function');
+ });
+
+ it('should be ok', async (): Promise => {
+ const logger: ILogger = new NullLogger();
+ try {
+ logger.warn('test');
+ return true.should.be.true;
+ } catch (e) {
+ return true.should.be.false;
+ }
+ });
+ });
+
+ describe('#error', (): void => {
+ it('should have error method', async (): Promise => {
+ const logger: ILogger = new NullLogger();
+ return logger.error.should.be.a('function');
+ });
+
+ it('should be ok', async (): Promise => {
+ const logger: ILogger = new NullLogger();
+ try {
+ logger.error('test');
+ return true.should.be.true;
+ } catch (e) {
+ return true.should.be.false;
+ }
+ });
+ });
+});
diff --git a/test/unit/utils/common.utils.spec.ts b/test/unit/utils/common.utils.spec.ts
new file mode 100644
index 0000000..825d19b
--- /dev/null
+++ b/test/unit/utils/common.utils.spec.ts
@@ -0,0 +1,9 @@
+import { mergeDeepProps } from '../../../src/utils';
+
+describe('common.utils (unit)', (): void => {
+ describe('#mergeDeepProps', (): void => {
+ it('should merge object props', async (): Promise => {
+ return mergeDeepProps({ a: { b: 1 }, c: 1, d: 1 }, { a: { b: 2 }, c: 2, e: 2 }).should.be.deep.eq({ a: { b: 2 }, c: 2, d: 1, e: 2 });
+ });
+ });
+});
diff --git a/test/unit/utils/retry.utils.spec.ts b/test/unit/utils/retry.utils.spec.ts
new file mode 100644
index 0000000..9d0a780
--- /dev/null
+++ b/test/unit/utils/retry.utils.spec.ts
@@ -0,0 +1,38 @@
+import { exponentialDelay, makeRetryable } from '../../../src/utils';
+
+describe('retry.utils (unit)', (): void => {
+ describe('#exponentialDelay', (): void => {
+ it('should return >= 200 and <= 250', async (): Promise => {
+ return exponentialDelay(1)
+ .should.be.gte(200)
+ .and.lte(240);
+ });
+
+ it('should return >= 400 and <= 500', async (): Promise => {
+ return exponentialDelay(2)
+ .should.be.gte(400)
+ .and.lte(480);
+ });
+
+ it('should return >= 800 and <= 1200', async (): Promise => {
+ return exponentialDelay(3)
+ .should.be.gte(800)
+ .and.lte(1000);
+ });
+ });
+
+ describe('#makeRetryable', (): void => {
+ it('should throw SquareException', async (): Promise => {
+ return makeRetryable(
+ async (): Promise => {
+ throw new Error('test error');
+ },
+ {
+ maxRetries: 1,
+ retryDelay: exponentialDelay,
+ retryCondition: async (_error: Error, maxRetries: number, retries: number): Promise => maxRetries > retries,
+ },
+ ).should.be.rejectedWith(Error, 'Square API error');
+ });
+ });
+});
diff --git a/tsconfig.build.json b/tsconfig.build.json
new file mode 100644
index 0000000..2653248
--- /dev/null
+++ b/tsconfig.build.json
@@ -0,0 +1,10 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "declaration": true,
+ "sourceMap": true,
+ "incremental": false
+ },
+ "include": ["src"],
+ "exclude": ["src/test.ts"]
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..89fdc51
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,28 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "esModuleInterop": true,
+ "target": "es6",
+ "strict": true,
+ "strictPropertyInitialization": false,
+ "strictNullChecks": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "skipLibCheck": false,
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "sourceMap": false,
+ "declaration": false,
+ "noImplicitAny": false,
+ "removeComments": false,
+ "noLib": false,
+ "allowSyntheticDefaultImports": true,
+ "outDir": "dist",
+ "baseUrl": "src",
+ "lib": ["es2017"]
+ },
+ "include": ["src", "test"]
+}
diff --git a/tslint.json b/tslint.json
new file mode 100644
index 0000000..5988326
--- /dev/null
+++ b/tslint.json
@@ -0,0 +1,47 @@
+{
+ "defaultSeverity": "error",
+ // make sure "tslint-config-prettier" is at the end
+ "extends": ["tslint:latest", "tslint-config-prettier"],
+ "rules": {
+ "deprecation": true,
+ "no-implicit-dependencies": [true, "dev", "optional"],
+ "no-submodule-imports": false,
+ "eofline": false,
+ "indent": false,
+ "member-access": [true, "no-public"],
+ "ordered-imports": [true],
+ "max-line-length": [160],
+ "member-ordering": [false],
+ "curly": false,
+ "interface-name": [false],
+ "array-type": [false],
+ "no-empty-interface": false,
+ "prefer-conditional-expression": false,
+ "no-empty": true,
+ "arrow-parens": false,
+ "object-literal-sort-keys": false,
+ "no-unused-expression": false,
+ "max-classes-per-file": false,
+ "variable-name": [false],
+ "one-line": [false],
+ "one-variable-per-declaration": [false],
+ "promise-function-async": true,
+ "no-null-keyword": true,
+ "no-return-await": true,
+ "match-default-export-name": true,
+ "prefer-readonly": true,
+ "typedef": [
+ true,
+ "call-signature",
+ "arrow-call-signature",
+ "parameter",
+ "arrow-parameter",
+ "property-declaration",
+ "variable-declaration",
+ "member-variable-declaration",
+ "object-destructuring",
+ "array-destructuring"
+ ]
+ },
+ "rulesDirectory": []
+}
From 9f20b18c79f773a5a61070a5123950a28143bc33 Mon Sep 17 00:00:00 2001
From: Coroliov Oleg <1880059+ruscon@users.noreply.github.com>
Date: Fri, 31 Jan 2020 12:31:22 +0200
Subject: [PATCH 02/11] chore(release): 0.0.1
---
CHANGELOG.md | 8 ++++++++
CONTRIBUTING.md | 2 +-
package.json | 2 +-
3 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8b13789..6d35cab 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1 +1,9 @@
+# Changelog
+All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
+
+### 0.0.1 (2020-01-31)
+
+### Features
+
+* **client:** add client with retry mechanism ([ca771bd](https://github.com/goparrot/square-connect-plus/commit/ca771bd74bf0d39f012566d2c19d9af24d030555))
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 06696b1..27af91f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -9,7 +9,7 @@ You MUST follow the [TypeScript](https://www.typescriptlang.org/docs/home.html)
should really read the recommendations.
Check your node version `node -v`.
-You MUST have `">=10.13"`, because of https://github.com/okonet/lint-staged#v10.
+You MUST have `">=10.13"`, because of .
You MUST run the `npm run coverage` command.
diff --git a/package.json b/package.json
index d3c7b7d..3810a2a 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "@goparrot/square-connect-plus",
"description": "Extends the official Square Connect APIs Javascript library with additional functionality",
- "version": "0.0.0",
+ "version": "0.0.1",
"author": "Coroliov Oleg",
"license": "MIT",
"private": false,
From 969dd83bdc7a403dd0f85e069cc22bf28edc7116 Mon Sep 17 00:00:00 2001
From: Coroliov Oleg <1880059+ruscon@users.noreply.github.com>
Date: Fri, 31 Jan 2020 12:46:01 +0200
Subject: [PATCH 03/11] build(npm): fix npm github-release command
---
package.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 3810a2a..68bbb34 100644
--- a/package.json
+++ b/package.json
@@ -68,7 +68,7 @@
"version": "echo \"use 'npm run release'\" && exit 1",
"release": "standard-version && git push && git push --tags && npm run publish && npm run github-release",
"release:dry": "npm run publish:dev:dry && standard-version --dry-run",
- "github-release": "conventional-github-releaser -p angular"
+ "github-release": "env-cmd conventional-github-releaser -p angular"
},
"peerDependencies": {
"square-connect": "^2.20190814.0"
@@ -92,6 +92,7 @@
"conventional-github-releaser": "^3.1.3",
"cz-conventional-changelog": "^3.1.0",
"dotenv-safe": "^8.2.0",
+ "env-cmd": "^10.0.1",
"husky": "^4.2.1",
"lint-staged": "^10.0.7",
"mocha": "^7.0.1",
From a0b0e520fffca0e4e2687388255fb55170a3ea7c Mon Sep 17 00:00:00 2001
From: Coroliov Oleg <1880059+ruscon@users.noreply.github.com>
Date: Fri, 31 Jan 2020 12:51:15 +0200
Subject: [PATCH 04/11] docs(readme): fix `originClient` options table
---
README.md | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/README.md b/README.md
index ecf6068..829a342 100644
--- a/README.md
+++ b/README.md
@@ -80,12 +80,13 @@ const squareClient: SquareClient = new SquareClient(accessToken, {
A set of possible settings for the original library.
-\| --- \| --- \| --- \| --- \|
-| basePath | `String` \| `https://connect.squareup.com` | The base URL against which to resolve every API call's (relative) path. |
-| defaultHeaders | `Array` \| `{ 'User-Agent': 'Square-Connect-Javascript/2.20191217.0' }` | The default HTTP headers to be included for all API calls. |
-| timeout | `Number` \| `15000` | The default HTTP timeout for all API calls. |
-| cache | `Boolean` \| `true` | If set to false an additional timestamp parameter is added to all API GET calls to prevent browser caching. |
-| enableCookies | `Boolean` \| `false` | If set to true, the client will save the cookies from each server response, and return them in the next request. |
+| Name | Type | Default | Description |
+| -------------- | --------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- |
+| basePath | `String` | `https://connect.squareup.com` | The base URL against which to resolve every API call's (relative) path. |
+| defaultHeaders | `Array` | `{ 'User-Agent': 'Square-Connect-Javascript/2.20191217.0' }` | The default HTTP headers to be included for all API calls. |
+| timeout | `Number` | `15000` | The default HTTP timeout for all API calls. |
+| cache | `Boolean` | `true` | If set to false an additional timestamp parameter is added to all API GET calls to prevent browser caching. |
+| enableCookies | `Boolean` | `false` | If set to true, the client will save the cookies from each server response, and return them in the next request. |
### `logger` Option
From d6e194dc664b6c94b67f0e3c0d40645b71cf1bd4 Mon Sep 17 00:00:00 2001
From: Coroliov Oleg <1880059+ruscon@users.noreply.github.com>
Date: Fri, 31 Jan 2020 21:20:23 +0200
Subject: [PATCH 05/11] feat: add SquareException.toObject() method
---
.mocharc.json | 5 +++
src/client/square-client.ts | 2 +-
src/exception/i-square-exception.ts | 12 +++++++
src/exception/index.ts | 1 +
src/exception/square-exception.ts | 21 ++++++++----
src/utils/common.utils.ts | 2 +-
test/mocha.opts | 7 ----
test/unit/exception/square-exception.spec.ts | 24 +++++++++++++-
test/unit/utils/common.utils.spec.ts | 34 +++++++++++++++++++-
test/unit/utils/retry.utils.spec.ts | 8 ++++-
10 files changed, 97 insertions(+), 19 deletions(-)
create mode 100644 .mocharc.json
create mode 100644 src/exception/i-square-exception.ts
delete mode 100644 test/mocha.opts
diff --git a/.mocharc.json b/.mocharc.json
new file mode 100644
index 0000000..ec6bc47
--- /dev/null
+++ b/.mocharc.json
@@ -0,0 +1,5 @@
+{
+ "ui": "bdd",
+ "project": "tsconfig.spec.json",
+ "require": ["ts-node/register", "source-map-support/register", "tsconfig-paths/register", "dotenv-safe/config", "./test/common.opts.ts"]
+}
diff --git a/src/client/square-client.ts b/src/client/square-client.ts
index 0920b44..c9b1142 100644
--- a/src/client/square-client.ts
+++ b/src/client/square-client.ts
@@ -156,7 +156,7 @@ export class SquareClient {
const apiClient: ApiClient = new ApiClient();
apiClient.authentications.oauth2.accessToken = accessToken;
- return mergeDeepProps(apiClient, config.originClient);
+ return mergeDeepProps(apiClient, config.originClient ?? {});
}
private getLogger(): ILogger {
diff --git a/src/exception/i-square-exception.ts b/src/exception/i-square-exception.ts
new file mode 100644
index 0000000..4391a9e
--- /dev/null
+++ b/src/exception/i-square-exception.ts
@@ -0,0 +1,12 @@
+import { ModelError } from 'square-connect';
+
+export interface ISquareException {
+ name: string;
+ statusCode: number;
+ message: string;
+ apiError: ModelError;
+ retries?: number;
+ url?: string;
+ method?: string;
+ requestArgs?: any;
+}
diff --git a/src/exception/index.ts b/src/exception/index.ts
index 718fa79..b2883bc 100644
--- a/src/exception/index.ts
+++ b/src/exception/index.ts
@@ -1 +1,2 @@
+export * from './i-square-exception';
export * from './square-exception';
diff --git a/src/exception/square-exception.ts b/src/exception/square-exception.ts
index a2c82bd..a4b6ba2 100644
--- a/src/exception/square-exception.ts
+++ b/src/exception/square-exception.ts
@@ -1,12 +1,13 @@
import { ModelError } from 'square-connect';
import { Response, SuperAgentRequest } from 'superagent';
+import { ISquareException } from './i-square-exception';
-export class SquareException extends Error {
+export class SquareException extends Error implements ISquareException {
+ statusCode: number;
+ apiError: ModelError;
retries?: number;
url?: string;
method?: string;
- statusCode: number;
- apiError: ModelError;
requestArgs?: any;
constructor(
@@ -22,7 +23,7 @@ export class SquareException extends Error {
this.method = data?.method?.toUpperCase();
this.statusCode = data?.statusCode || 500;
this.apiError = data?.apiError || { category: 'API_ERROR', code: 'SERVICE_UNAVAILABLE', detail: 'Square API error' };
- this.message = this.apiError?.detail ?? this.apiError.code ?? originError?.message ?? 'Square API error';
+ this.message = this.apiError.detail ?? this.apiError.code ?? originError?.message ?? 'Square API error';
this.requestArgs = data?.requestArgs;
// Error.captureStackTrace(this);
@@ -68,14 +69,20 @@ export class SquareException extends Error {
);
}
- toString(): string {
- return JSON.stringify({
+ toObject(): ISquareException {
+ return {
+ name: this.name,
+ message: this.message,
retries: this.retries,
url: this.url,
method: this.method,
statusCode: this.statusCode,
requestArgs: this.requestArgs,
apiError: this.apiError,
- });
+ };
+ }
+
+ toString(): string {
+ return JSON.stringify(this.toObject());
}
}
diff --git a/src/utils/common.utils.ts b/src/utils/common.utils.ts
index d7e2b95..e1ed6d7 100644
--- a/src/utils/common.utils.ts
+++ b/src/utils/common.utils.ts
@@ -14,7 +14,7 @@ export function isObject(item?: T): item is T {
* @link {https://stackoverflow.com/a/34749873/3408246}
* Deep merge props of two objects
*/
-export function mergeDeepProps(target: T, ...sources: S[]): T {
+export function mergeDeepProps(target: T, ...sources: S[]): T {
if (!sources.length) return target;
const source: S | undefined = sources.shift();
diff --git a/test/mocha.opts b/test/mocha.opts
deleted file mode 100644
index 204a418..0000000
--- a/test/mocha.opts
+++ /dev/null
@@ -1,7 +0,0 @@
---ui bdd
--r ts-node/register
--r source-map-support/register
--r tsconfig-paths/register
--r dotenv-safe/config
--r ./test/common.opts.ts
---project tsconfig.spec.json
diff --git a/test/unit/exception/square-exception.spec.ts b/test/unit/exception/square-exception.spec.ts
index a0a1fe1..c87088f 100644
--- a/test/unit/exception/square-exception.spec.ts
+++ b/test/unit/exception/square-exception.spec.ts
@@ -7,7 +7,7 @@ class SuperAgentError extends Error {
}
describe('SquareException (unit)', (): void => {
- describe('#createFromSuperAgentError', (): void => {
+ describe('#constructor', (): void => {
it('should be ok without args', async (): Promise => {
const squareException: SquareException = new SquareException();
@@ -17,6 +17,28 @@ describe('SquareException (unit)', (): void => {
squareException.should.have.property('statusCode', 500);
return squareException.should.have.property('apiError').and.eql({ category: 'API_ERROR', code: 'SERVICE_UNAVAILABLE', detail: 'Square API error' });
});
+
+ it('should be ok with empty data.originError object', async (): Promise => {
+ // @ts-ignore
+ const squareException: SquareException = new SquareException({ apiError: {} });
+
+ squareException.should.be.instanceOf(SquareException);
+ squareException.should.have.property('message', 'Square API error');
+ squareException.should.have.property('retries', 0);
+ squareException.should.have.property('statusCode', 500);
+ return squareException.should.have.property('apiError').and.eql({});
+ });
+
+ it('should be ok with empty data.originError object and originError', async (): Promise => {
+ // @ts-ignore
+ const squareException: SquareException = new SquareException({ apiError: {} }, new Error('error message'));
+
+ squareException.should.be.instanceOf(SquareException);
+ squareException.should.have.property('message', 'error message');
+ squareException.should.have.property('retries', 0);
+ squareException.should.have.property('statusCode', 500);
+ return squareException.should.have.property('apiError').and.eql({});
+ });
});
describe('#createFromSuperAgentError', (): void => {
diff --git a/test/unit/utils/common.utils.spec.ts b/test/unit/utils/common.utils.spec.ts
index 825d19b..7d44f83 100644
--- a/test/unit/utils/common.utils.spec.ts
+++ b/test/unit/utils/common.utils.spec.ts
@@ -2,8 +2,40 @@ import { mergeDeepProps } from '../../../src/utils';
describe('common.utils (unit)', (): void => {
describe('#mergeDeepProps', (): void => {
- it('should merge object props', async (): Promise => {
+ it('should correctly merge simple object with undefined', async (): Promise => {
+ return mergeDeepProps({ a: 1 }, undefined as any).should.be.deep.eq({ a: 1 });
+ });
+
+ it('should correctly merge class props with undefined', async (): Promise => {
+ const error: Error = new Error();
+ return mergeDeepProps(error, undefined as any)
+ .should.be.instanceOf(Error)
+ .and.deep.eq(error)
+ .and.have.property('name', 'Error');
+ });
+
+ it('should correctly merge 2 empty objects', async (): Promise => {
+ return mergeDeepProps({}, {}).should.be.deep.eq({});
+ });
+
+ it('should correctly merge 2 simple object props', async (): Promise => {
+ return mergeDeepProps({ a: 1, b: 1, c: 1 }, { b: 2, c: 2, e: 2 }).should.be.deep.eq({ a: 1, b: 2, c: 2, e: 2 });
+ });
+
+ it('should correctly merge 3 simple objects props', async (): Promise => {
+ return mergeDeepProps({ a: 1, b: 1, c: 1 }, { b: 2, c: 2, e: 2 }, { b: 3, c: 3, d: 3 }).should.be.deep.eq({ a: 1, b: 3, c: 3, e: 2, d: 3 });
+ });
+
+ it('should correctly merge 2 complex object props', async (): Promise => {
return mergeDeepProps({ a: { b: 1 }, c: 1, d: 1 }, { a: { b: 2 }, c: 2, e: 2 }).should.be.deep.eq({ a: { b: 2 }, c: 2, d: 1, e: 2 });
});
+
+ it('should correctly merge class props with simple object', async (): Promise => {
+ const error: Error = new Error();
+ return mergeDeepProps(error, { name: 'x' })
+ .should.be.instanceOf(Error)
+ .and.deep.eq(error)
+ .and.have.property('name', 'x');
+ });
});
});
diff --git a/test/unit/utils/retry.utils.spec.ts b/test/unit/utils/retry.utils.spec.ts
index 9d0a780..585d431 100644
--- a/test/unit/utils/retry.utils.spec.ts
+++ b/test/unit/utils/retry.utils.spec.ts
@@ -1,4 +1,4 @@
-import { exponentialDelay, makeRetryable } from '../../../src/utils';
+import { exponentialDelay, isRetryableException, makeRetryable } from '../../../src/utils';
describe('retry.utils (unit)', (): void => {
describe('#exponentialDelay', (): void => {
@@ -35,4 +35,10 @@ describe('retry.utils (unit)', (): void => {
).should.be.rejectedWith(Error, 'Square API error');
});
});
+
+ describe('#isRetryableException', (): void => {
+ it('should return false if Error is not instanceof SquareException', async (): Promise => {
+ return isRetryableException(new Error()).should.be.eq(false);
+ });
+ });
});
From 8b50c794d663dbe97feb608f39bf5e4fbe35a9e2 Mon Sep 17 00:00:00 2001
From: Coroliov Oleg <1880059+ruscon@users.noreply.github.com>
Date: Fri, 31 Jan 2020 21:28:07 +0200
Subject: [PATCH 06/11] chore(release): 0.0.2
---
CHANGELOG.md | 6 ++++++
package.json | 2 +-
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6d35cab..39f6810 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,12 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
+### [0.0.2](https://github.com/goparrot/square-connect-plus/compare/v0.0.1...v0.0.2) (2020-01-31)
+
+### Features
+
+* add SquareException.toObject() method ([d6e194d](https://github.com/goparrot/square-connect-plus/commit/d6e194dc664b6c94b67f0e3c0d40645b71cf1bd4))
+
### 0.0.1 (2020-01-31)
### Features
diff --git a/package.json b/package.json
index 68bbb34..d8098fa 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "@goparrot/square-connect-plus",
"description": "Extends the official Square Connect APIs Javascript library with additional functionality",
- "version": "0.0.1",
+ "version": "0.0.2",
"author": "Coroliov Oleg",
"license": "MIT",
"private": false,
From 71948202ded58be2fdadd751a27ccd5e4cebdeaf Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" <23040076+greenkeeper[bot]@users.noreply.github.com>
Date: Sun, 16 Feb 2020 21:06:29 +0000
Subject: [PATCH 07/11] chore(package): update nock to version 12.0.0
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index d8098fa..935f5ef 100644
--- a/package.json
+++ b/package.json
@@ -97,7 +97,7 @@
"lint-staged": "^10.0.7",
"mocha": "^7.0.1",
"mocha-junit-reporter": "^1.23.0",
- "nock": "^11.7.2",
+ "nock": "^12.0.0",
"nyc": "^15.0.0",
"prettier": "^1.18.2",
"remark-cli": "^7.0.0",
From 42aa855d433f20af32fd1f9ae1c013fc6b20d108 Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" <23040076+greenkeeper[bot]@users.noreply.github.com>
Date: Wed, 19 Feb 2020 11:47:30 +0000
Subject: [PATCH 08/11] chore(package): update sinon to version 9.0.0
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 935f5ef..e81cf11 100644
--- a/package.json
+++ b/package.json
@@ -106,7 +106,7 @@
"remark-lint-emphasis-marker": "^1.0.3",
"remark-lint-strong-marker": "^1.0.3",
"rimraf": "^3.0.1",
- "sinon": "^8.1.1",
+ "sinon": "^9.0.0",
"source-map-support": "^0.5.16",
"square-connect": "^2.20200122.0",
"standard-version": "^7.0.0",
From edd172b467f82f531cdf737259afa0f26731c9a0 Mon Sep 17 00:00:00 2001
From: Coroliov Oleg <1880059+ruscon@users.noreply.github.com>
Date: Tue, 24 Mar 2020 16:29:52 +0200
Subject: [PATCH 09/11] chore(package): update all dependencies
---
commitlint.config.js => .commitlintrc.js | 0
.eslintrc.js | 94 +++++++++++++++++++
.github/workflows/ci.yml | 8 +-
.mocharc.json | 14 ++-
README.md | 2 +-
bin/prepublish.js | 20 ----
bin/prepublish.ts | 21 +++++
package.json | 81 ++++++++--------
test/e2e/client/square-client.spec.ts | 20 +---
test/integration/client/square-client.spec.ts | 5 +-
test/unit/utils/common.utils.spec.ts | 5 +-
test/unit/utils/retry.utils.spec.ts | 12 +--
tsconfig.eslint.json | 4 +
tslint.json | 47 ----------
14 files changed, 186 insertions(+), 147 deletions(-)
rename commitlint.config.js => .commitlintrc.js (100%)
create mode 100644 .eslintrc.js
delete mode 100644 bin/prepublish.js
create mode 100644 bin/prepublish.ts
create mode 100644 tsconfig.eslint.json
delete mode 100644 tslint.json
diff --git a/commitlint.config.js b/.commitlintrc.js
similarity index 100%
rename from commitlint.config.js
rename to .commitlintrc.js
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..724588f
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,94 @@
+/**
+ * How to install and use
+ * @link {https://www.arden.nl/setting-up-a-gatsby-js-starter-with-type-script-es-lint-prettier-and-pre-commit-hooks}
+ *
+ * Rule documentation
+ * @link {https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-var-requires.md}
+ */
+module.exports = {
+ root: true,
+ env: {
+ browser: true,
+ node: true,
+ es6: true,
+ mocha: true,
+ },
+ parser: '@typescript-eslint/parser',
+ extends: [
+ 'eslint:recommended',
+ 'plugin:@typescript-eslint/recommended',
+ 'plugin:@typescript-eslint/recommended-requiring-type-checking',
+ 'plugin:prettier/recommended',
+ 'plugin:import/errors',
+ 'plugin:import/warnings',
+ 'plugin:import/typescript',
+ 'prettier',
+ 'prettier/@typescript-eslint',
+ ],
+ settings: {
+ 'import/parsers': {
+ '@typescript-eslint/parser': ['.ts', '.tsx'],
+ },
+ },
+ parserOptions: {
+ project: './tsconfig.eslint.json',
+ },
+ plugins: ['@typescript-eslint', 'import', 'prettier'],
+ rules: {
+ 'prettier/prettier': 'error',
+ 'import/no-deprecated': ['error'],
+ 'import/order': ['error', { groups: ['builtin', 'external', 'parent', 'sibling', 'index'] }],
+ '@typescript-eslint/explicit-member-accessibility': ['error', { accessibility: 'no-public' }],
+ '@typescript-eslint/explicit-function-return-type': ['error'],
+ '@typescript-eslint/interface-name-prefix': 'off',
+ '@typescript-eslint/no-explicit-any': 'off',
+ '@typescript-eslint/no-inferrable-types': 'off',
+ '@typescript-eslint/no-non-null-assertion': 'off',
+ '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
+ '@typescript-eslint/no-empty-interface': 'off',
+ '@typescript-eslint/no-require-imports': ['error'],
+ '@typescript-eslint/no-use-before-define': ['error'],
+ '@typescript-eslint/camelcase': 'off',
+ '@typescript-eslint/ban-ts-ignore': 'off',
+ '@typescript-eslint/require-await': 'off',
+ '@typescript-eslint/promise-function-async': [
+ 'error',
+ {
+ allowAny: true,
+ },
+ ],
+ '@typescript-eslint/ban-types': [
+ 'error',
+ {
+ types: {
+ Number: {
+ message: 'Use number instead',
+ fixWith: 'number',
+ },
+ Function: {
+ message: 'Use () => void instead',
+ fixWith: '() => void',
+ },
+ Object: {
+ message: 'Use object instead',
+ fixWith: 'object',
+ },
+ String: {
+ message: 'Use string instead',
+ fixWith: 'string',
+ },
+ },
+ },
+ ],
+ },
+ overrides: [
+ {
+ files: ['*.js', '*.jsx'],
+ rules: {
+ '@typescript-eslint/no-require-imports': 'off',
+ '@typescript-eslint/no-var-requires': 'off',
+ '@typescript-eslint/explicit-function-return-type': 'off',
+ },
+ },
+ ],
+};
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7a46d41..c391a8a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -11,12 +11,12 @@ jobs:
steps:
- name: Clone repository
- uses: actions/checkout@v1
+ uses: actions/checkout@v2
with:
- fetch-depth: 1
+ fetch-depth: 0
- name: Lints Pull Request commits
- uses: wagoid/commitlint-github-action@v1.2.2
+ uses: wagoid/commitlint-github-action@v1
build:
runs-on: ubuntu-latest
@@ -24,7 +24,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- node-version: [8.x, 10.x, 12.x, 13.x]
+ node-version: [10.x, 12.x, 13.x]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.mocharc.json b/.mocharc.json
index ec6bc47..9b65af0 100644
--- a/.mocharc.json
+++ b/.mocharc.json
@@ -1,5 +1,15 @@
{
"ui": "bdd",
- "project": "tsconfig.spec.json",
- "require": ["ts-node/register", "source-map-support/register", "tsconfig-paths/register", "dotenv-safe/config", "./test/common.opts.ts"]
+ "project": "tsconfig.json",
+ "check-leaks": true,
+ "full-trace": true,
+ "recursive": true,
+ "exit": true,
+ "require": [
+ "ts-node/register/transpile-only",
+ "source-map-support/register",
+ "tsconfig-paths/register",
+ "dotenv-safe/config",
+ "./test/common.opts.ts"
+ ]
}
diff --git a/README.md b/README.md
index 829a342..05c4d1e 100644
--- a/README.md
+++ b/README.md
@@ -83,7 +83,7 @@ A set of possible settings for the original library.
| Name | Type | Default | Description |
| -------------- | --------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- |
| basePath | `String` | `https://connect.squareup.com` | The base URL against which to resolve every API call's (relative) path. |
-| defaultHeaders | `Array` | `{ 'User-Agent': 'Square-Connect-Javascript/2.20191217.0' }` | The default HTTP headers to be included for all API calls. |
+| defaultHeaders | `Array` | `{ 'User-Agent': 'Square-Connect-Javascript/3.20200226.0' }` | The default HTTP headers to be included for all API calls. |
| timeout | `Number` | `15000` | The default HTTP timeout for all API calls. |
| cache | `Boolean` | `true` | If set to false an additional timestamp parameter is added to all API GET calls to prevent browser caching. |
| enableCookies | `Boolean` | `false` | If set to true, the client will save the cookies from each server response, and return them in the next request. |
diff --git a/bin/prepublish.js b/bin/prepublish.js
deleted file mode 100644
index fe2d2c1..0000000
--- a/bin/prepublish.js
+++ /dev/null
@@ -1,20 +0,0 @@
-const fs = require('fs');
-
-const originalPackage = require('../package.json');
-originalPackage.module = './index.js';
-originalPackage.main = './index.js';
-originalPackage.types = './index.d.ts';
-delete originalPackage.scripts;
-delete originalPackage.devDependencies;
-delete originalPackage.config;
-delete originalPackage.husky;
-delete originalPackage.files;
-delete originalPackage.directories;
-delete originalPackage['lint-staged'];
-
-fs.writeFileSync('./dist/package.json', JSON.stringify(originalPackage, null, ' '));
-
-const copyFiles = ['README.md'];
-for (const file of copyFiles) {
- fs.copyFileSync(`./${file}`, `./dist/${file}`);
-}
diff --git a/bin/prepublish.ts b/bin/prepublish.ts
new file mode 100644
index 0000000..b22a384
--- /dev/null
+++ b/bin/prepublish.ts
@@ -0,0 +1,21 @@
+import { writeFileSync, copyFileSync } from 'fs';
+import originPackage from '../package.json';
+
+const distPackage: Record = originPackage;
+distPackage.module = './index.js';
+distPackage.main = './index.js';
+distPackage.types = './index.d.ts';
+delete distPackage.scripts;
+delete distPackage.devDependencies;
+delete distPackage.config;
+delete distPackage.husky;
+delete distPackage.files;
+delete distPackage.directories;
+delete distPackage['lint-staged'];
+
+writeFileSync('./dist/package.json', JSON.stringify(distPackage, null, ' '));
+
+const copyFiles = ['README.md'];
+for (const file of copyFiles) {
+ copyFileSync(`./${file}`, `./dist/${file}`);
+}
diff --git a/package.json b/package.json
index e81cf11..8d83c64 100644
--- a/package.json
+++ b/package.json
@@ -19,19 +19,18 @@
"retry"
],
"engines": {
- "node": ">=8.9.0"
+ "node": ">=10"
},
"main": "src/index.ts",
"husky": {
"hooks": {
- "commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
- "pre-commit": "npm run pre-commit",
+ "commit-msg": "commitlint -E HUSKY_GIT_PARAMS && npm run pre-commit",
"post-commit": "git update-index --again"
}
},
"lint-staged": {
- "*.{ts,json}": [
- "npm run format"
+ "*.{ts,tsx,json,js,jsx}": [
+ "npm run format:base"
]
},
"config": {
@@ -42,27 +41,24 @@
"scripts": {
"commit": "git-cz",
"test": "mocha 'test/unit/**/*.spec.ts' 'test/e2e/**/*.spec.ts'",
- "test:fast": "TS_NODE_TRANSPILE_ONLY=true npm run test",
+ "test:all": "mocha --timeout 15000 'test/**/*.spec.ts'",
"test:unit": "mocha 'test/unit/**/*.spec.ts'",
"test:e2e": "mocha 'test/e2e/**/*.spec.ts'",
"test:integration": "mocha --timeout 15000 'test/integration/**/*.spec.ts'",
- "test:integration:fast": "mocha --timeout 15000 'test/integration/**/*.spec.ts'",
- "test:all": "mocha --timeout 15000 'test/**/*.spec.ts'",
- "test:all:fast": "TS_NODE_TRANSPILE_ONLY=true mocha --timeout 15000 'test/**/*.spec.ts'",
"coverage": "nyc npm test",
- "coverage:fast": "TS_NODE_TRANSPILE_ONLY=true nyc npm run test:fast",
"coverage:all": "nyc npm run test:all",
- "coverage:all:fast": "TS_NODE_TRANSPILE_ONLY=true nyc npm run test:all:fast",
- "format": "prettier \"**/*.{ts,js,json}\" --write",
- "format:staged": "lint-staged",
- "lint": "npm run lint:config:check && tslint -c tslint.json -p tsconfig.json --format stylish",
- "lint:config:check": "tslint-config-prettier-check ./tslint.json",
+ "lint": "npm run lint:base -- '.'",
+ "lint:base": "npm run lint:config:check && eslint --ignore-path .gitignore --ext .ts,.tsx,.json,.js,.jsx",
+ "lint:config:check": "eslint --print-config src/index.ts | eslint-config-prettier-check",
+ "format": "npm run format:base -- '.'",
+ "format:base": "npm run lint:base -- --fix",
+ "format:staged": "git add . && lint-staged",
"build": "rimraf dist && tsc -p tsconfig.build.json",
"remark": "remark README.md CHANGELOG.md CONTRIBUTING.md CODE_OF_CONDUCT.md .github/ -o -f -q && git add .",
"pre-commit": "git add . && npm run format:staged && npm run remark && npm run lint && npm run coverage:all && npm run build",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0 && remark CHANGELOG.md -o -f -q && git add CHANGELOG.md",
"prepublishOnly": "echo \"use 'npm run publish'\" && exit 1",
- "publish": "npm run build && node bin/prepublish.js && npm publish dist",
+ "publish": "npm run build && ts-node -T bin/prepublish.ts && npm publish dist",
"publish:dev": "npm run publish --tag dev",
"publish:dev:dry": "npm run publish:dev --dry-run",
"version": "echo \"use 'npm run release'\" && exit 1",
@@ -71,20 +67,22 @@
"github-release": "env-cmd conventional-github-releaser -p angular"
},
"peerDependencies": {
- "square-connect": "^2.20190814.0"
+ "square-connect": "^3.20200226.0"
},
"dependencies": {},
"devDependencies": {
- "@commitlint/cli": "^8.2.0",
- "@commitlint/config-conventional": "^8.2.0",
+ "@commitlint/cli": "^8.3.5",
+ "@commitlint/config-conventional": "^8.3.4",
"@commitlint/travis-cli": "^8.2.0",
- "@types/chai": "^4.2.8",
+ "@types/chai": "^4.2.11",
"@types/chai-as-promised": "^7.1.2",
- "@types/mocha": "^7.0.1",
- "@types/node": "^13.5.3",
- "@types/sinon": "^7.5.0",
+ "@types/mocha": "^7.0.2",
+ "@types/node": "^13.9.3",
+ "@types/sinon": "^7.5.2",
"@types/square-connect": "^2.20190814.3",
- "@types/superagent": "^4.1.4",
+ "@types/superagent": "^4.1.7",
+ "@typescript-eslint/eslint-plugin": "^2.25.0",
+ "@typescript-eslint/parser": "^2.25.0",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"commitizen": "^4.0.0",
@@ -92,29 +90,32 @@
"conventional-github-releaser": "^3.1.3",
"cz-conventional-changelog": "^3.1.0",
"dotenv-safe": "^8.2.0",
- "env-cmd": "^10.0.1",
- "husky": "^4.2.1",
- "lint-staged": "^10.0.7",
- "mocha": "^7.0.1",
+ "env-cmd": "^10.1.0",
+ "eslint": "^6.8.0",
+ "eslint-config-prettier": "^6.10.1",
+ "eslint-import-resolver-typescript": "^2.0.0",
+ "eslint-plugin-import": "^2.20.1",
+ "eslint-plugin-prettier": "^3.1.2",
+ "husky": "^4.2.3",
+ "lint-staged": "^10.0.9",
+ "mocha": "^7.1.1",
"mocha-junit-reporter": "^1.23.0",
- "nock": "^12.0.0",
+ "nock": "^12.0.3",
"nyc": "^15.0.0",
- "prettier": "^1.18.2",
+ "prettier": "^2.0.2",
"remark-cli": "^7.0.0",
- "remark-frontmatter": "^1.3.2",
- "remark-github": "^8.0.0",
+ "remark-frontmatter": "^1.3.3",
+ "remark-github": "^9.0.0",
"remark-lint-emphasis-marker": "^1.0.3",
"remark-lint-strong-marker": "^1.0.3",
- "rimraf": "^3.0.1",
- "sinon": "^9.0.0",
+ "rimraf": "^3.0.2",
+ "sinon": "^9.0.1",
"source-map-support": "^0.5.16",
- "square-connect": "^2.20200122.0",
+ "square-connect": "^3.20200226.0",
"standard-version": "^7.0.0",
- "superagent": "^5.2.1",
- "ts-node": "^8.4.1",
+ "superagent": "^5.2.2",
+ "ts-node": "^8.8.1",
"tsconfig-paths": "^3.9.0",
- "tslint": "^6.0.0",
- "tslint-config-prettier": "^1.18.0",
- "typescript": "^3.7.5"
+ "typescript": "^3.8.3"
}
}
diff --git a/test/e2e/client/square-client.spec.ts b/test/e2e/client/square-client.spec.ts
index 20d3ba2..967e9af 100644
--- a/test/e2e/client/square-client.spec.ts
+++ b/test/e2e/client/square-client.spec.ts
@@ -26,10 +26,7 @@ describe('SquareClient (e2e)', (): void => {
});
it('should NOT retry 501 http status', async (): Promise => {
- nock(basePath)
- .get(/.*/)
- .times(1000)
- .reply(501);
+ nock(basePath).get(/.*/).times(1000).reply(501);
return new SquareClient(accessToken, config)
.getLocationsApi()
@@ -39,10 +36,7 @@ describe('SquareClient (e2e)', (): void => {
});
it('should NOT retry 400 http status', async (): Promise => {
- nock(basePath)
- .get(/.*/)
- .times(1000)
- .reply(400);
+ nock(basePath).get(/.*/).times(1000).reply(400);
return new SquareClient(accessToken, config)
.getLocationsApi()
@@ -73,10 +67,7 @@ describe('SquareClient (e2e)', (): void => {
});
it('should retry 500 http status', async (): Promise => {
- nock(basePath)
- .get(/.*/)
- .times(1000)
- .reply(500);
+ nock(basePath).get(/.*/).times(1000).reply(500);
return new SquareClient(accessToken, config)
.getLocationsApi()
@@ -86,10 +77,7 @@ describe('SquareClient (e2e)', (): void => {
});
it('should retry 503 http status', async (): Promise => {
- nock(basePath)
- .get(/.*/)
- .times(1000)
- .reply(503);
+ nock(basePath).get(/.*/).times(1000).reply(503);
return new SquareClient(accessToken, config)
.getLocationsApi()
diff --git a/test/integration/client/square-client.spec.ts b/test/integration/client/square-client.spec.ts
index 793c3c1..4d48c8c 100644
--- a/test/integration/client/square-client.spec.ts
+++ b/test/integration/client/square-client.spec.ts
@@ -33,10 +33,7 @@ describe('SquareClient (integration)', (): void => {
});
it('should retrieve data', async (): Promise => {
- return new SquareClient(accessToken, config)
- .getLocationsApi()
- .listLocations()
- .should.eventually.be.fulfilled.and.have.property('locations');
+ return new SquareClient(accessToken, config).getLocationsApi().listLocations().should.eventually.be.fulfilled.and.have.property('locations');
});
});
diff --git a/test/unit/utils/common.utils.spec.ts b/test/unit/utils/common.utils.spec.ts
index 7d44f83..8bfdedb 100644
--- a/test/unit/utils/common.utils.spec.ts
+++ b/test/unit/utils/common.utils.spec.ts
@@ -32,10 +32,7 @@ describe('common.utils (unit)', (): void => {
it('should correctly merge class props with simple object', async (): Promise => {
const error: Error = new Error();
- return mergeDeepProps(error, { name: 'x' })
- .should.be.instanceOf(Error)
- .and.deep.eq(error)
- .and.have.property('name', 'x');
+ return mergeDeepProps(error, { name: 'x' }).should.be.instanceOf(Error).and.deep.eq(error).and.have.property('name', 'x');
});
});
});
diff --git a/test/unit/utils/retry.utils.spec.ts b/test/unit/utils/retry.utils.spec.ts
index 585d431..b1596d8 100644
--- a/test/unit/utils/retry.utils.spec.ts
+++ b/test/unit/utils/retry.utils.spec.ts
@@ -3,21 +3,15 @@ import { exponentialDelay, isRetryableException, makeRetryable } from '../../../
describe('retry.utils (unit)', (): void => {
describe('#exponentialDelay', (): void => {
it('should return >= 200 and <= 250', async (): Promise => {
- return exponentialDelay(1)
- .should.be.gte(200)
- .and.lte(240);
+ return exponentialDelay(1).should.be.gte(200).and.lte(240);
});
it('should return >= 400 and <= 500', async (): Promise => {
- return exponentialDelay(2)
- .should.be.gte(400)
- .and.lte(480);
+ return exponentialDelay(2).should.be.gte(400).and.lte(480);
});
it('should return >= 800 and <= 1200', async (): Promise => {
- return exponentialDelay(3)
- .should.be.gte(800)
- .and.lte(1000);
+ return exponentialDelay(3).should.be.gte(800).and.lte(1000);
});
});
diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json
new file mode 100644
index 0000000..3004417
--- /dev/null
+++ b/tsconfig.eslint.json
@@ -0,0 +1,4 @@
+{
+ "extends": "./tsconfig.json",
+ "include": ["src/**/*", "test/**/*", "bin/**/*", "*.json"]
+}
diff --git a/tslint.json b/tslint.json
deleted file mode 100644
index 5988326..0000000
--- a/tslint.json
+++ /dev/null
@@ -1,47 +0,0 @@
-{
- "defaultSeverity": "error",
- // make sure "tslint-config-prettier" is at the end
- "extends": ["tslint:latest", "tslint-config-prettier"],
- "rules": {
- "deprecation": true,
- "no-implicit-dependencies": [true, "dev", "optional"],
- "no-submodule-imports": false,
- "eofline": false,
- "indent": false,
- "member-access": [true, "no-public"],
- "ordered-imports": [true],
- "max-line-length": [160],
- "member-ordering": [false],
- "curly": false,
- "interface-name": [false],
- "array-type": [false],
- "no-empty-interface": false,
- "prefer-conditional-expression": false,
- "no-empty": true,
- "arrow-parens": false,
- "object-literal-sort-keys": false,
- "no-unused-expression": false,
- "max-classes-per-file": false,
- "variable-name": [false],
- "one-line": [false],
- "one-variable-per-declaration": [false],
- "promise-function-async": true,
- "no-null-keyword": true,
- "no-return-await": true,
- "match-default-export-name": true,
- "prefer-readonly": true,
- "typedef": [
- true,
- "call-signature",
- "arrow-call-signature",
- "parameter",
- "arrow-parameter",
- "property-declaration",
- "variable-declaration",
- "member-variable-declaration",
- "object-destructuring",
- "array-destructuring"
- ]
- },
- "rulesDirectory": []
-}
From 3c827b4e836e9b907717c77d435889dae0c7d591 Mon Sep 17 00:00:00 2001
From: Coroliov Oleg <1880059+ruscon@users.noreply.github.com>
Date: Tue, 24 Mar 2020 16:39:52 +0200
Subject: [PATCH 10/11] chore(release): 0.0.3
---
CHANGELOG.md | 2 ++
package.json | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 39f6810..8e489a7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,8 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
+### [0.0.3](https://github.com/goparrot/square-connect-plus/compare/v0.0.2...v0.0.3) (2020-03-24)
+
### [0.0.2](https://github.com/goparrot/square-connect-plus/compare/v0.0.1...v0.0.2) (2020-01-31)
### Features
diff --git a/package.json b/package.json
index 8d83c64..e5bec38 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "@goparrot/square-connect-plus",
"description": "Extends the official Square Connect APIs Javascript library with additional functionality",
- "version": "0.0.2",
+ "version": "0.0.3",
"author": "Coroliov Oleg",
"license": "MIT",
"private": false,
From af032bbbc4c9cdf458451eb822ce32eb39a5b8e2 Mon Sep 17 00:00:00 2001
From: "greenkeeper[bot]" <23040076+greenkeeper[bot]@users.noreply.github.com>
Date: Thu, 21 May 2020 17:53:28 +0000
Subject: [PATCH 11/11] chore(package): update @typescript-eslint/parser to
version 3.0.0
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index e5bec38..1ef6693 100644
--- a/package.json
+++ b/package.json
@@ -82,7 +82,7 @@
"@types/square-connect": "^2.20190814.3",
"@types/superagent": "^4.1.7",
"@typescript-eslint/eslint-plugin": "^2.25.0",
- "@typescript-eslint/parser": "^2.25.0",
+ "@typescript-eslint/parser": "^3.0.0",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"commitizen": "^4.0.0",