Compare commits
198 Commits
Author | SHA1 | Date | |
---|---|---|---|
bbb57004d9 | |||
f7d2c6de4f | |||
b8f545cdd5 | |||
96820090d4 | |||
6e2c63fe1b | |||
39d3bb4d24 | |||
62db3a9bc5 | |||
a82d9eafe2 | |||
f55ab55365 | |||
9cf8c0b0f3 | |||
49796f89dd | |||
aaeaa23b79 | |||
85199b8839 | |||
33f7df28e2 | |||
29ee46b3a2 | |||
18dd110d4e | |||
fa93f13306 | |||
81694cf58c | |||
fdd1c7cdb3 | |||
40f330791f | |||
16b4d168db | |||
cc017a9bbf | |||
2adf7e9ee2 | |||
2303b6da7e | |||
4487579bfd | |||
8d2bbcae2a | |||
deb25a3068 | |||
0a83d8b476 | |||
8e7c730d86 | |||
fe50adb1ff | |||
cd75f7acd8 | |||
bb0dd6426a | |||
d471376681 | |||
b882922f2b | |||
9a0d35c325 | |||
7b49bba0d2 | |||
6600a23a00 | |||
e2845c9992 | |||
5e6f2c6fbf | |||
d3d0649b73 | |||
fba43df3c4 | |||
c6fa540543 | |||
1891b54389 | |||
fee8443af1 | |||
c48f956ae3 | |||
4a4b64a2c4 | |||
43d4b47782 | |||
6d970cb925 | |||
43710c930e | |||
306dd7c366 | |||
3d69d97891 | |||
a6d52702fd | |||
de31ee6093 | |||
cd2d7b2525 | |||
2d4a75c9cd | |||
557fec0386 | |||
e803f9e48d | |||
76c714a931 | |||
e8669f0420 | |||
d9e6214a7e | |||
7c4227bfc6 | |||
e55a521395 | |||
06fc279caf | |||
e89e317bbc | |||
d182832e47 | |||
92059a50de | |||
db80f2df7e | |||
145505b891 | |||
f4f50c6a94 | |||
d204059313 | |||
00210566d5 | |||
14245b2521 | |||
f0fa91e2db | |||
19a1fe1524 | |||
27770a8ad1 | |||
ab48f11e83 | |||
a0a9e3f824 | |||
c829b06169 | |||
80fa40baf4 | |||
3659b80e1e | |||
770e7d46ea | |||
2a46f2a306 | |||
eae4d09664 | |||
23a2f597fc | |||
c278249c32 | |||
a32c372374 | |||
f98972d9fe | |||
acebe6a381 | |||
7031504852 | |||
3010a1da9a | |||
cdead33be4 | |||
5e23649702 | |||
cc6bd5726a | |||
f487584e80 | |||
443bccd4c9 | |||
f359856b1d | |||
bda04f124b | |||
466187fd52 | |||
d22504317e | |||
6e31d84798 | |||
36472b7306 | |||
e86f14b8d8 | |||
2b9e7f6dd2 | |||
5e4afccf9c | |||
3de7a1a210 | |||
bd2a5eedff | |||
aa18357d75 | |||
9960aff219 | |||
03d884ed59 | |||
9a0ac6fc62 | |||
ad35ea4eb8 | |||
ffb0195f04 | |||
78737c24df | |||
6e276eda00 | |||
021d26a23a | |||
c9c8a1234c | |||
dffabd905f | |||
36f2707141 | |||
b00d674b6f | |||
b09598d465 | |||
acc7b2d46f | |||
16a97a420c | |||
a73c78e54b | |||
1f408b5123 | |||
284f4967f4 | |||
55c80c1403 | |||
7a3e565dbb | |||
6f5d10ccd3 | |||
f1ddab72f6 | |||
376225888c | |||
63e8660f6c | |||
2358b1d48f | |||
9db29bacc2 | |||
5f27b6834c | |||
6717ecf80c | |||
7784f99878 | |||
54a0521f9e | |||
ef25315d59 | |||
74b6bf230f | |||
fe693e6383 | |||
6014e94ee0 | |||
88927fa6f8 | |||
e8133247f7 | |||
4e51ed315e | |||
0ffc44b3ef | |||
a8291febec | |||
c0704eb2d8 | |||
9eeb9c16b6 | |||
52bf520eb9 | |||
c9f6198114 | |||
0a17591eae | |||
3417f09cdb | |||
20dc3c9230 | |||
6f865a356f | |||
71a6ffef96 | |||
189916e62b | |||
33e36b5d44 | |||
dbe999eea7 | |||
31b326cf51 | |||
0c03763281 | |||
21e85062f7 | |||
33670bb4d5 | |||
6893e1f460 | |||
3a1943417b | |||
8e6834da02 | |||
7d78890e14 | |||
5c413947a4 | |||
5de63a1ef3 | |||
ee7fa87b25 | |||
4b65674fb2 | |||
14f48c99d0 | |||
e9f1d5697f | |||
b4a9d1aa0c | |||
d77d2b13da | |||
aaa2394b36 | |||
99efccd827 | |||
b1cfca5f35 | |||
ca4ef6d5c0 | |||
a7516c86e6 | |||
abece86511 | |||
8eca91145b | |||
d7a9d173b8 | |||
fcef5fcb6c | |||
93595a222b | |||
5f8b5f7690 | |||
980ea344e7 | |||
9c69fb6c1c | |||
fe33cfdeaa | |||
f0c74b1568 | |||
a6920b18d4 | |||
85a647cd10 | |||
df48a06d3e | |||
52fa217fda | |||
97bbb8b38b | |||
8c941fe1b7 | |||
b821ca60cc | |||
06c945ad0f | |||
48d839b9b9 |
66
.gitea/workflows/default_nottags.yaml
Normal file
66
.gitea/workflows/default_nottags.yaml
Normal file
@@ -0,0 +1,66 @@
|
||||
name: Default (not tags)
|
||||
|
||||
on:
|
||||
push:
|
||||
tags-ignore:
|
||||
- '**'
|
||||
|
||||
env:
|
||||
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
|
||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
|
||||
|
||||
jobs:
|
||||
security:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install pnpm and npmci
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
|
||||
- name: Run npm prepare
|
||||
run: npmci npm prepare
|
||||
|
||||
- name: Audit production dependencies
|
||||
run: |
|
||||
npmci command npm config set registry https://registry.npmjs.org
|
||||
npmci command pnpm audit --audit-level=high --prod
|
||||
continue-on-error: true
|
||||
|
||||
- name: Audit development dependencies
|
||||
run: |
|
||||
npmci command npm config set registry https://registry.npmjs.org
|
||||
npmci command pnpm audit --audit-level=high --dev
|
||||
continue-on-error: true
|
||||
|
||||
test:
|
||||
if: ${{ always() }}
|
||||
needs: security
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Test stable
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
npmci npm test
|
||||
|
||||
- name: Test build
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
npmci npm build
|
124
.gitea/workflows/default_tags.yaml
Normal file
124
.gitea/workflows/default_tags.yaml
Normal file
@@ -0,0 +1,124 @@
|
||||
name: Default (tags)
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
env:
|
||||
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
|
||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
|
||||
|
||||
jobs:
|
||||
security:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Audit production dependencies
|
||||
run: |
|
||||
npmci command npm config set registry https://registry.npmjs.org
|
||||
npmci command pnpm audit --audit-level=high --prod
|
||||
continue-on-error: true
|
||||
|
||||
- name: Audit development dependencies
|
||||
run: |
|
||||
npmci command npm config set registry https://registry.npmjs.org
|
||||
npmci command pnpm audit --audit-level=high --dev
|
||||
continue-on-error: true
|
||||
|
||||
test:
|
||||
if: ${{ always() }}
|
||||
needs: security
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Test stable
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
npmci npm test
|
||||
|
||||
- name: Test build
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
npmci npm build
|
||||
|
||||
release:
|
||||
needs: test
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Release
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm publish
|
||||
|
||||
metadata:
|
||||
needs: test
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
continue-on-error: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Code quality
|
||||
run: |
|
||||
npmci command npm install -g typescript
|
||||
npmci npm install
|
||||
|
||||
- name: Trigger
|
||||
run: npmci trigger
|
||||
|
||||
- name: Build docs and upload artifacts
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
pnpm install -g @git.zone/tsdoc
|
||||
npmci command tsdoc
|
||||
continue-on-error: true
|
20
.gitignore
vendored
20
.gitignore
vendored
@@ -1,4 +1,20 @@
|
||||
node_modules/
|
||||
.nogit/
|
||||
|
||||
# artifacts
|
||||
coverage/
|
||||
public/
|
||||
pages/
|
||||
pages/
|
||||
|
||||
# installs
|
||||
node_modules/
|
||||
|
||||
# caches
|
||||
.yarn/
|
||||
.cache/
|
||||
.rpt2_cache
|
||||
|
||||
# builds
|
||||
dist/
|
||||
dist_*/
|
||||
|
||||
# custom
|
@@ -1,71 +0,0 @@
|
||||
# gitzone standard
|
||||
image: hosttoday/ht-docker-node:npmci
|
||||
|
||||
cache:
|
||||
paths:
|
||||
- .yarn/
|
||||
key: "$CI_BUILD_STAGE"
|
||||
|
||||
stages:
|
||||
- test
|
||||
- release
|
||||
- trigger
|
||||
- pages
|
||||
|
||||
testLEGACY:
|
||||
stage: test
|
||||
script:
|
||||
- npmci test legacy
|
||||
coverage: /\d+.?\d+?\%\s*coverage/
|
||||
tags:
|
||||
- docker
|
||||
allow_failure: true
|
||||
|
||||
testLTS:
|
||||
stage: test
|
||||
script:
|
||||
- npmci test lts
|
||||
coverage: /\d+.?\d+?\%\s*coverage/
|
||||
tags:
|
||||
- docker
|
||||
|
||||
testSTABLE:
|
||||
stage: test
|
||||
script:
|
||||
- npmci test stable
|
||||
coverage: /\d+.?\d+?\%\s*coverage/
|
||||
tags:
|
||||
- docker
|
||||
|
||||
release:
|
||||
stage: release
|
||||
script:
|
||||
- npmci publish
|
||||
only:
|
||||
- tags
|
||||
tags:
|
||||
- docker
|
||||
|
||||
trigger:
|
||||
stage: trigger
|
||||
script:
|
||||
- npmci trigger
|
||||
only:
|
||||
- tags
|
||||
tags:
|
||||
- docker
|
||||
|
||||
pages:
|
||||
image: hosttoday/ht-docker-node:npmci
|
||||
stage: pages
|
||||
script:
|
||||
- npmci command yarn global add npmpage
|
||||
- npmci command npmpage
|
||||
tags:
|
||||
- docker
|
||||
only:
|
||||
- tags
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
paths:
|
||||
- public
|
11
.vscode/launch.json
vendored
Normal file
11
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "npm test",
|
||||
"name": "Run npm test",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
26
.vscode/settings.json
vendored
Normal file
26
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": ["/npmextra.json"],
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"npmci": {
|
||||
"type": "object",
|
||||
"description": "settings for npmci"
|
||||
},
|
||||
"gitzone": {
|
||||
"type": "object",
|
||||
"description": "settings for gitzone",
|
||||
"properties": {
|
||||
"projectType": {
|
||||
"type": "string",
|
||||
"enum": ["website", "element", "service", "npm", "wcc"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
66
README.md
66
README.md
@@ -1,66 +0,0 @@
|
||||
# smartrequest
|
||||
dropin replacement for request
|
||||
|
||||
## Availabililty
|
||||
[](https://www.npmjs.com/package/smartrequest)
|
||||
[](https://GitLab.com/pushrocks/smartrequest)
|
||||
[](https://github.com/pushrocks/smartrequest)
|
||||
[](https://pushrocks.gitlab.io/smartrequest/)
|
||||
|
||||
## Status for master
|
||||
[](https://GitLab.com/pushrocks/smartrequest/commits/master)
|
||||
[](https://GitLab.com/pushrocks/smartrequest/commits/master)
|
||||
[](https://www.npmjs.com/package/smartrequest)
|
||||
[](https://david-dm.org/pushrocks/smartrequest)
|
||||
[](https://www.bithound.io/github/pushrocks/smartrequest/master/dependencies/npm)
|
||||
[](https://www.bithound.io/github/pushrocks/smartrequest)
|
||||
[](https://nodejs.org/dist/latest-v6.x/docs/api/)
|
||||
[](https://nodejs.org/dist/latest-v6.x/docs/api/)
|
||||
[](http://standardjs.com/)
|
||||
|
||||
## Usage
|
||||
Use TypeScript for best in class instellisense.
|
||||
|
||||
> note: smartrequest uses the **native** node request module under the hood (not the one from npm)
|
||||
|
||||
```javascript
|
||||
import * as smartrequest from 'smartrequest'
|
||||
|
||||
// simple post
|
||||
let options: smartrequest.ISmartRequestOptions = { // typed options
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
"Authorization": "Bearer token"
|
||||
},
|
||||
requestBody: {
|
||||
key1: 'value1',
|
||||
key2: 3
|
||||
}
|
||||
}
|
||||
|
||||
smartrequest.post('https://example.com', options).then(res => {
|
||||
console.log(res.status)
|
||||
console.log(res.body) // if json, body will be parsed automatically
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
// also available
|
||||
smartrequest.get(...)
|
||||
smartrequest.put(...)
|
||||
smartrequest.del(...)
|
||||
|
||||
// streaming
|
||||
smartrequest.get('https://example.com/bigfile.mp4', optionsArg, true).then(res => { // third arg = true signals streaming
|
||||
console.log(res.status)
|
||||
res.on('data', data => {
|
||||
// do something with the data chunk here
|
||||
}
|
||||
res.on('end', () => {
|
||||
// do something when things have ended
|
||||
})
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
[](https://push.rocks)
|
98
changelog.md
Normal file
98
changelog.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-07-27 - 3.0.0 - BREAKING CHANGE(core)
|
||||
Major architectural refactoring with fetch-like API
|
||||
|
||||
**Breaking Changes:**
|
||||
- Legacy API functions are now imported from `@push.rocks/smartrequest/legacy` instead of the main export
|
||||
- Modern API response objects now use fetch-like methods (`.json()`, `.text()`, `.arrayBuffer()`, `.stream()`) instead of direct `.body` access
|
||||
- Renamed `responseType()` method to `accept()` in modern API
|
||||
- Removed automatic defaults:
|
||||
- No default keepAlive (must be explicitly set)
|
||||
- No default timeouts
|
||||
- No automatic JSON parsing in core
|
||||
- Complete internal architecture refactoring:
|
||||
- Core module now always returns raw streams
|
||||
- Response parsing happens in SmartResponse methods
|
||||
- Legacy API is now just an adapter over the core module
|
||||
|
||||
**Features:**
|
||||
- New fetch-like response API with single-use body consumption
|
||||
- Better TypeScript support and type safety
|
||||
- Cleaner separation of concerns between request and response
|
||||
- More predictable behavior aligned with fetch API standards
|
||||
|
||||
**Documentation:**
|
||||
- Updated all examples to show correct import paths
|
||||
- Added comprehensive examples for the new response API
|
||||
- Enhanced migration guide
|
||||
|
||||
## 2025-04-03 - 2.1.0 - feat(docs)
|
||||
Enhance documentation and tests with modern API usage examples and migration guide
|
||||
|
||||
- Updated README to include detailed examples for the modern fluent API, covering GET, POST, headers, query, timeout, retries, and pagination
|
||||
- Added a migration guide comparing the legacy API and modern API usage
|
||||
- Improved installation instructions with npm, pnpm, and yarn examples
|
||||
- Added and updated test files for both legacy and modern API functionalities
|
||||
- Minor formatting improvements in the code and documentation examples
|
||||
|
||||
## 2024-11-06 - 2.0.23 - fix(core)
|
||||
Enhance type safety for response in binary requests
|
||||
|
||||
- Updated the dependency versions in package.json to their latest versions.
|
||||
- Improved type inference for the response body in getBinary method of smartrequest.binaryrest.ts.
|
||||
- Introduced generic typing to IExtendedIncomingMessage interface for better type safety.
|
||||
|
||||
## 2024-05-29 - 2.0.22 - Documentation
|
||||
update description
|
||||
|
||||
## 2024-04-01 - 2.0.21 - Configuration
|
||||
Updated configuration files
|
||||
|
||||
- Updated `tsconfig`
|
||||
- Updated `npmextra.json`: githost
|
||||
|
||||
## 2023-07-10 - 2.0.15 - Structure
|
||||
Refactored the organization structure
|
||||
|
||||
- Switched to a new organization scheme
|
||||
|
||||
## 2022-07-29 - 1.1.57 to 2.0.0 - Major Update
|
||||
Significant changes and improvements leading to a major version update
|
||||
|
||||
- **BREAKING CHANGE**: Switched the core to use ECMAScript modules (ESM)
|
||||
|
||||
## 2018-08-14 - 1.1.12 to 1.1.13 - Functional Enhancements
|
||||
Enhanced request capabilities and removed unnecessary dependencies
|
||||
|
||||
- Fixed request module to allow sending strings
|
||||
- Removed CI dependencies
|
||||
|
||||
## 2018-07-19 - 1.1.1 to 1.1.11 - Various Fixes and Improvements
|
||||
Improvements and fixes across various components
|
||||
|
||||
- Added formData capability
|
||||
- Corrected path resolution to use current working directory (CWD)
|
||||
- Improved formData handling
|
||||
- Included correct headers
|
||||
- Updated request ending method
|
||||
|
||||
## 2018-06-19 - 1.0.14 - Structural Fix
|
||||
Resolved conflicts with file extensions
|
||||
|
||||
- Changed `.json.ts` to `.jsonrest.ts` to avoid conflicts
|
||||
|
||||
## 2018-06-13 - 1.0.8 to 1.0.10 - Core Updates
|
||||
Ensured binary handling compliance
|
||||
|
||||
- Enhanced core to uphold latest standards
|
||||
- Correct binary file handling response
|
||||
- Fix for handling and returning binary responses
|
||||
|
||||
## 2017-06-09 - 1.0.4 to 1.0.6 - Infrastructure and Type Improvements
|
||||
Types and infrastructure updates
|
||||
|
||||
- Improved types
|
||||
- Removed need for content type on post requests
|
||||
- Updated for new infrastructure
|
||||
|
7
dist/index.d.ts
vendored
7
dist/index.d.ts
vendored
@@ -1,7 +0,0 @@
|
||||
import * as interfaces from './smartrequest.interfaces';
|
||||
export { request } from './smartrequest.request';
|
||||
export { ISmartRequestOptions } from './smartrequest.interfaces';
|
||||
export declare let get: (domainArg: string, optionsArg?: interfaces.ISmartRequestOptions) => Promise<Response>;
|
||||
export declare let post: (domainArg: string, optionsArg?: interfaces.ISmartRequestOptions) => Promise<Response>;
|
||||
export declare let put: (domainArg: string, optionsArg?: interfaces.ISmartRequestOptions) => Promise<Response>;
|
||||
export declare let del: (domainArg: string, optionsArg?: interfaces.ISmartRequestOptions) => Promise<Response>;
|
34
dist/index.js
vendored
34
dist/index.js
vendored
@@ -1,34 +0,0 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const smartrequest_request_1 = require("./smartrequest.request");
|
||||
var smartrequest_request_2 = require("./smartrequest.request");
|
||||
exports.request = smartrequest_request_2.request;
|
||||
exports.get = (domainArg, optionsArg = {}) => __awaiter(this, void 0, void 0, function* () {
|
||||
optionsArg.method = 'GET';
|
||||
let response = yield smartrequest_request_1.request(domainArg, optionsArg);
|
||||
return response;
|
||||
});
|
||||
exports.post = (domainArg, optionsArg = {}) => __awaiter(this, void 0, void 0, function* () {
|
||||
optionsArg.method = 'POST';
|
||||
let response = yield smartrequest_request_1.request(domainArg, optionsArg);
|
||||
return response;
|
||||
});
|
||||
exports.put = (domainArg, optionsArg = {}) => __awaiter(this, void 0, void 0, function* () {
|
||||
optionsArg.method = 'PUT';
|
||||
let response = yield smartrequest_request_1.request(domainArg, optionsArg);
|
||||
return response;
|
||||
});
|
||||
exports.del = (domainArg, optionsArg = {}) => __awaiter(this, void 0, void 0, function* () {
|
||||
optionsArg.method = 'DELETE';
|
||||
let response = yield smartrequest_request_1.request(domainArg, optionsArg);
|
||||
return response;
|
||||
});
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7O0FBS0EsaUVBQWdEO0FBRWhELCtEQUFnRDtBQUF2Qyx5Q0FBQSxPQUFPLENBQUE7QUFHTCxRQUFBLEdBQUcsR0FBRyxDQUFPLFNBQWlCLEVBQUUsYUFBOEMsRUFBRTtJQUN6RixVQUFVLENBQUMsTUFBTSxHQUFHLEtBQUssQ0FBQTtJQUN6QixJQUFJLFFBQVEsR0FBRyxNQUFNLDhCQUFPLENBQUMsU0FBUyxFQUFFLFVBQVUsQ0FBQyxDQUFBO0lBQ25ELE1BQU0sQ0FBQyxRQUFRLENBQUE7QUFDakIsQ0FBQyxDQUFBLENBQUE7QUFFVSxRQUFBLElBQUksR0FBRyxDQUFPLFNBQWlCLEVBQUUsYUFBOEMsRUFBRTtJQUMxRixVQUFVLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQTtJQUMxQixJQUFJLFFBQVEsR0FBRyxNQUFNLDhCQUFPLENBQUMsU0FBUyxFQUFFLFVBQVUsQ0FBQyxDQUFBO0lBQ25ELE1BQU0sQ0FBQyxRQUFRLENBQUE7QUFDakIsQ0FBQyxDQUFBLENBQUE7QUFFVSxRQUFBLEdBQUcsR0FBRyxDQUFPLFNBQWlCLEVBQUUsYUFBOEMsRUFBRTtJQUN6RixVQUFVLENBQUMsTUFBTSxHQUFHLEtBQUssQ0FBQTtJQUN6QixJQUFJLFFBQVEsR0FBRyxNQUFNLDhCQUFPLENBQUMsU0FBUyxFQUFFLFVBQVUsQ0FBQyxDQUFBO0lBQ25ELE1BQU0sQ0FBQyxRQUFRLENBQUE7QUFDakIsQ0FBQyxDQUFBLENBQUE7QUFFVSxRQUFBLEdBQUcsR0FBRyxDQUFPLFNBQWlCLEVBQUUsYUFBOEMsRUFBRTtJQUN6RixVQUFVLENBQUMsTUFBTSxHQUFHLFFBQVEsQ0FBQTtJQUM1QixJQUFJLFFBQVEsR0FBRyxNQUFNLDhCQUFPLENBQUMsU0FBUyxFQUFFLFVBQVUsQ0FBQyxDQUFBO0lBQ25ELE1BQU0sQ0FBQyxRQUFRLENBQUE7QUFDakIsQ0FBQyxDQUFBLENBQUEifQ==
|
4
dist/smartrequest.interfaces.d.ts
vendored
4
dist/smartrequest.interfaces.d.ts
vendored
@@ -1,4 +0,0 @@
|
||||
import * as https from 'https';
|
||||
export interface ISmartRequestOptions extends https.RequestOptions {
|
||||
requestBody?: any;
|
||||
}
|
3
dist/smartrequest.interfaces.js
vendored
3
dist/smartrequest.interfaces.js
vendored
@@ -1,3 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRyZXF1ZXN0LmludGVyZmFjZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydHJlcXVlc3QuaW50ZXJmYWNlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIn0=
|
6
dist/smartrequest.plugins.d.ts
vendored
6
dist/smartrequest.plugins.d.ts
vendored
@@ -1,6 +0,0 @@
|
||||
import 'typings-global';
|
||||
import * as url from 'url';
|
||||
import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
import * as q from 'smartq';
|
||||
export { url, http, https, q };
|
12
dist/smartrequest.plugins.js
vendored
12
dist/smartrequest.plugins.js
vendored
@@ -1,12 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
require("typings-global");
|
||||
const url = require("url");
|
||||
exports.url = url;
|
||||
const http = require("http");
|
||||
exports.http = http;
|
||||
const https = require("https");
|
||||
exports.https = https;
|
||||
const q = require("smartq");
|
||||
exports.q = q;
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRyZXF1ZXN0LnBsdWdpbnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydHJlcXVlc3QucGx1Z2lucy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLDBCQUF1QjtBQUN2QiwyQkFBMEI7QUFPeEIsa0JBQUc7QUFOTCw2QkFBNEI7QUFPMUIsb0JBQUk7QUFOTiwrQkFBOEI7QUFPNUIsc0JBQUs7QUFMUCw0QkFBMkI7QUFNekIsY0FBQyJ9
|
2
dist/smartrequest.request.d.ts
vendored
2
dist/smartrequest.request.d.ts
vendored
@@ -1,2 +0,0 @@
|
||||
import * as interfaces from './smartrequest.interfaces';
|
||||
export declare let request: (domainArg: string, optionsArg?: interfaces.ISmartRequestOptions, streamArg?: boolean) => Promise<Response>;
|
86
dist/smartrequest.request.js
vendored
86
dist/smartrequest.request.js
vendored
@@ -1,86 +0,0 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const plugins = require("./smartrequest.plugins");
|
||||
let buildResponse = (responseArg) => {
|
||||
let done = plugins.q.defer();
|
||||
// Continuously update stream with data
|
||||
let body = '';
|
||||
responseArg.on('data', function (chunkArg) {
|
||||
body += chunkArg;
|
||||
});
|
||||
responseArg.on('end', function () {
|
||||
try {
|
||||
responseArg.body = JSON.parse(body);
|
||||
}
|
||||
catch (err) {
|
||||
responseArg.body = body;
|
||||
}
|
||||
done.resolve(responseArg);
|
||||
});
|
||||
return done.promise;
|
||||
};
|
||||
exports.request = (domainArg, optionsArg = {}, streamArg = false) => __awaiter(this, void 0, void 0, function* () {
|
||||
let done = plugins.q.defer();
|
||||
let parsedUrl;
|
||||
if (domainArg) {
|
||||
parsedUrl = plugins.url.parse(domainArg);
|
||||
optionsArg.hostname = parsedUrl.hostname;
|
||||
if (parsedUrl.port) {
|
||||
optionsArg.port = parseInt(parsedUrl.port);
|
||||
}
|
||||
optionsArg.path = parsedUrl.path;
|
||||
}
|
||||
if (!parsedUrl || parsedUrl.protocol === 'https:') {
|
||||
let request = plugins.https.request(optionsArg, response => {
|
||||
if (streamArg) {
|
||||
done.resolve(response);
|
||||
}
|
||||
else {
|
||||
buildResponse(response).then(done.resolve);
|
||||
}
|
||||
});
|
||||
if (optionsArg.requestBody) {
|
||||
if (typeof optionsArg.requestBody !== 'string') {
|
||||
optionsArg.requestBody = JSON.stringify(optionsArg.requestBody);
|
||||
}
|
||||
request.write(optionsArg.requestBody);
|
||||
}
|
||||
request.on('error', (e) => {
|
||||
console.error(e);
|
||||
});
|
||||
request.end();
|
||||
}
|
||||
else if (parsedUrl.protocol === 'http:') {
|
||||
let request = plugins.http.request(optionsArg, response => {
|
||||
if (streamArg) {
|
||||
done.resolve(response);
|
||||
}
|
||||
else {
|
||||
buildResponse(response).then(done.resolve);
|
||||
}
|
||||
});
|
||||
if (optionsArg.requestBody) {
|
||||
if (typeof optionsArg.requestBody !== 'string') {
|
||||
optionsArg.requestBody = JSON.stringify(optionsArg.requestBody);
|
||||
}
|
||||
request.write(optionsArg.requestBody);
|
||||
}
|
||||
request.on('error', (e) => {
|
||||
console.error(e);
|
||||
});
|
||||
request.end();
|
||||
}
|
||||
else {
|
||||
throw new Error(`unsupported protocol: ${parsedUrl.protocol}`);
|
||||
}
|
||||
return done.promise;
|
||||
});
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRyZXF1ZXN0LnJlcXVlc3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydHJlcXVlc3QucmVxdWVzdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7O0FBQ0Esa0RBQWlEO0FBR2pELElBQUksYUFBYSxHQUFHLENBQUMsV0FBVztJQUM5QixJQUFJLElBQUksR0FBRyxPQUFPLENBQUMsQ0FBQyxDQUFDLEtBQUssRUFBRSxDQUFBO0lBQzVCLHVDQUF1QztJQUN2QyxJQUFJLElBQUksR0FBRyxFQUFFLENBQUE7SUFDYixXQUFXLENBQUMsRUFBRSxDQUFDLE1BQU0sRUFBRSxVQUFVLFFBQVE7UUFDdkMsSUFBSSxJQUFJLFFBQVEsQ0FBQTtJQUNsQixDQUFDLENBQUMsQ0FBQTtJQUNGLFdBQVcsQ0FBQyxFQUFFLENBQUMsS0FBSyxFQUFFO1FBQ3BCLElBQUksQ0FBQztZQUNILFdBQVcsQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQTtRQUNyQyxDQUFDO1FBQUMsS0FBSyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztZQUNiLFdBQVcsQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFBO1FBQ3pCLENBQUM7UUFDRCxJQUFJLENBQUMsT0FBTyxDQUFDLFdBQVcsQ0FBQyxDQUFBO0lBQzNCLENBQUMsQ0FBQyxDQUFBO0lBQ0YsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUE7QUFDckIsQ0FBQyxDQUFBO0FBRVUsUUFBQSxPQUFPLEdBQUcsQ0FBTyxTQUFpQixFQUFFLGFBQThDLEVBQUUsRUFBRSxZQUFxQixLQUFLO0lBQ3pILElBQUksSUFBSSxHQUFHLE9BQU8sQ0FBQyxDQUFDLENBQUMsS0FBSyxFQUFPLENBQUE7SUFDakMsSUFBSSxTQUEwQixDQUFBO0lBQzlCLEVBQUUsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUM7UUFDZCxTQUFTLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUE7UUFDeEMsVUFBVSxDQUFDLFFBQVEsR0FBRyxTQUFTLENBQUMsUUFBUSxDQUFBO1FBQ3hDLEVBQUUsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO1lBQUMsVUFBVSxDQUFDLElBQUksR0FBRyxRQUFRLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFBO1FBQUMsQ0FBQztRQUNsRSxVQUFVLENBQUMsSUFBSSxHQUFHLFNBQVMsQ0FBQyxJQUFJLENBQUE7SUFDbEMsQ0FBQztJQUNELEVBQUUsQ0FBQyxDQUFDLENBQUMsU0FBUyxJQUFJLFNBQVMsQ0FBQyxRQUFRLEtBQUssUUFBUSxDQUFDLENBQUMsQ0FBQztRQUNsRCxJQUFJLE9BQU8sR0FBRyxPQUFPLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxVQUFVLEVBQUUsUUFBUTtZQUN0RCxFQUFFLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDO2dCQUNkLElBQUksQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLENBQUE7WUFDeEIsQ0FBQztZQUFDLElBQUksQ0FBQyxDQUFDO2dCQUNOLGFBQWEsQ0FBQyxRQUFRLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFBO1lBQzVDLENBQUM7UUFDSCxDQUFDLENBQUMsQ0FBQTtRQUNGLEVBQUUsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDO1lBQzNCLEVBQUUsQ0FBQyxDQUFDLE9BQU8sVUFBVSxDQUFDLFdBQVcsS0FBSyxRQUFRLENBQUMsQ0FBQyxDQUFDO2dCQUMvQyxVQUFVLENBQUMsV0FBVyxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsVUFBVSxDQUFDLFdBQVcsQ0FBQyxDQUFBO1lBQ2pFLENBQUM7WUFDRCxPQUFPLENBQUMsS0FBSyxDQUFDLFVBQVUsQ0FBQyxXQUFXLENBQUMsQ0FBQTtRQUN2QyxDQUFDO1FBQ0QsT0FBTyxDQUFDLEVBQUUsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDO1lBQ3BCLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUE7UUFDbEIsQ0FBQyxDQUFDLENBQUE7UUFDRixPQUFPLENBQUMsR0FBRyxFQUFFLENBQUE7SUFDZixDQUFDO0lBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxRQUFRLEtBQUssT0FBTyxDQUFDLENBQUMsQ0FBQztRQUMxQyxJQUFJLE9BQU8sR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxVQUFVLEVBQUUsUUFBUTtZQUNyRCxFQUFFLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDO2dCQUNkLElBQUksQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLENBQUE7WUFDeEIsQ0FBQztZQUFDLElBQUksQ0FBQyxDQUFDO2dCQUNOLGFBQWEsQ0FBQyxRQUFRLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFBO1lBQzVDLENBQUM7UUFDSCxDQUFDLENBQUMsQ0FBQTtRQUNGLEVBQUUsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDO1lBQzNCLEVBQUUsQ0FBQyxDQUFDLE9BQU8sVUFBVSxDQUFDLFdBQVcsS0FBSyxRQUFRLENBQUMsQ0FBQyxDQUFDO2dCQUMvQyxVQUFVLENBQUMsV0FBVyxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsVUFBVSxDQUFDLFdBQVcsQ0FBQyxDQUFBO1lBQ2pFLENBQUM7WUFDRCxPQUFPLENBQUMsS0FBSyxDQUFDLFVBQVUsQ0FBQyxXQUFXLENBQUMsQ0FBQTtRQUN2QyxDQUFDO1FBQ0QsT0FBTyxDQUFDLEVBQUUsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDO1lBQ3BCLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUE7UUFDbEIsQ0FBQyxDQUFDLENBQUE7UUFDRixPQUFPLENBQUMsR0FBRyxFQUFFLENBQUE7SUFDZixDQUFDO0lBQUMsSUFBSSxDQUFDLENBQUM7UUFDTixNQUFNLElBQUksS0FBSyxDQUFDLHlCQUF5QixTQUFTLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQTtJQUNoRSxDQUFDO0lBQ0QsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUE7QUFDckIsQ0FBQyxDQUFBLENBQUEifQ==
|
@@ -1,7 +1,37 @@
|
||||
{
|
||||
"npmts": {
|
||||
"coverageTreshold": 50
|
||||
},
|
||||
"npmci": {
|
||||
"globalNpmTools": [
|
||||
"npmts"
|
||||
]
|
||||
"npmGlobalTools": [],
|
||||
"npmAccessLevel": "public"
|
||||
},
|
||||
"gitzone": {
|
||||
"projectType": "npm",
|
||||
"module": {
|
||||
"githost": "code.foss.global",
|
||||
"gitscope": "push.rocks",
|
||||
"gitrepo": "smartrequest",
|
||||
"description": "A module for modern HTTP/HTTPS requests with support for form data, file uploads, JSON, binary data, streams, and more.",
|
||||
"npmPackagename": "@push.rocks/smartrequest",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"HTTP",
|
||||
"HTTPS",
|
||||
"request library",
|
||||
"form data",
|
||||
"file uploads",
|
||||
"JSON",
|
||||
"binary data",
|
||||
"streams",
|
||||
"keepAlive",
|
||||
"TypeScript",
|
||||
"modern web requests",
|
||||
"drop-in replacement"
|
||||
]
|
||||
}
|
||||
},
|
||||
"tsdoc": {
|
||||
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
|
||||
}
|
||||
}
|
70
package.json
70
package.json
@@ -1,31 +1,69 @@
|
||||
{
|
||||
"name": "smartrequest",
|
||||
"version": "1.0.6",
|
||||
"description": "dropin replacement for request",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"name": "@push.rocks/smartrequest",
|
||||
"version": "3.0.0",
|
||||
"private": false,
|
||||
"description": "A module for modern HTTP/HTTPS requests with support for form data, file uploads, JSON, binary data, streams, and more.",
|
||||
"exports": {
|
||||
".": "./dist_ts_web/index.js",
|
||||
"./legacy": "./dist_ts/legacy/index.js"
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "(npmts)"
|
||||
"test": "(tstest test/ --web)",
|
||||
"build": "(tsbuild --web)",
|
||||
"buildDocs": "tsdoc"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+ssh://git@gitlab.com/pushrocks/smartrequest.git"
|
||||
"url": "https://code.foss.global/push.rocks/smartrequest.git"
|
||||
},
|
||||
"keywords": [
|
||||
"request"
|
||||
"HTTP",
|
||||
"HTTPS",
|
||||
"request library",
|
||||
"form data",
|
||||
"file uploads",
|
||||
"JSON",
|
||||
"binary data",
|
||||
"streams",
|
||||
"keepAlive",
|
||||
"TypeScript",
|
||||
"modern web requests",
|
||||
"drop-in replacement"
|
||||
],
|
||||
"author": "Lossless GmbH",
|
||||
"author": "Task Venture Capital GmbH",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://gitlab.com/pushrocks/smartrequest/issues"
|
||||
"url": "https://gitlab.com/push.rocks/smartrequest/issues"
|
||||
},
|
||||
"homepage": "https://gitlab.com/pushrocks/smartrequest#README",
|
||||
"homepage": "https://code.foss.global/push.rocks/smartrequest",
|
||||
"dependencies": {
|
||||
"smartq": "^1.1.1",
|
||||
"typings-global": "^1.0.17"
|
||||
"@push.rocks/smartpromise": "^4.0.4",
|
||||
"@push.rocks/smarturl": "^3.1.0",
|
||||
"agentkeepalive": "^4.5.0",
|
||||
"form-data": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tapbundle": "^1.0.14",
|
||||
"typings-test": "^1.0.3"
|
||||
}
|
||||
"@git.zone/tsbuild": "^2.2.0",
|
||||
"@git.zone/tsrun": "^1.3.3",
|
||||
"@git.zone/tstest": "^1.0.90",
|
||||
"@pushrocks/tapbundle": "^5.0.8",
|
||||
"@types/node": "^22.9.0"
|
||||
},
|
||||
"files": [
|
||||
"ts/**/*",
|
||||
"ts_web/**/*",
|
||||
"dist/**/*",
|
||||
"dist_*/**/*",
|
||||
"dist_ts/**/*",
|
||||
"dist_ts_web/**/*",
|
||||
"assets/**/*",
|
||||
"cli.js",
|
||||
"npmextra.json",
|
||||
"readme.md"
|
||||
],
|
||||
"browserslist": [
|
||||
"last 1 chrome versions"
|
||||
],
|
||||
"packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6"
|
||||
}
|
||||
|
9960
pnpm-lock.yaml
generated
Normal file
9960
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
54
readme.hints.md
Normal file
54
readme.hints.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# SmartRequest Architecture Hints
|
||||
|
||||
## Core Features
|
||||
- supports http
|
||||
- supports https
|
||||
- supports unix socks
|
||||
- supports formData
|
||||
- supports file uploads
|
||||
- supports best practice keepAlive
|
||||
- dedicated functions for working with JSON request/response cycles
|
||||
- written in TypeScript
|
||||
- continuously updated
|
||||
- uses node native http and https modules
|
||||
- used in modules like @push.rocks/smartproxy and @api.global/typedrequest
|
||||
|
||||
## Architecture Overview (as of latest refactoring)
|
||||
- The project is now structured with a clean separation between core functionality and API layers
|
||||
- Core module (ts/core/) contains the essential HTTP request logic using Node.js http/https modules
|
||||
- **Core always returns raw streams** - no parsing or body collection happens in the core request function
|
||||
- Modern API (ts/modern/) provides a fluent, chainable interface with fetch-like Response objects
|
||||
- Legacy API is maintained through a thin adapter layer for backward compatibility
|
||||
|
||||
## Key Components
|
||||
|
||||
### Core Module (ts/core/)
|
||||
- `request.ts`: Core HTTP/HTTPS request logic with unix socket support and keep-alive agents
|
||||
- `coreRequest()` always returns a raw Node.js IncomingMessage stream
|
||||
- No response parsing or body collection happens here
|
||||
- `response.ts`: SmartResponse class providing fetch-like API
|
||||
- Methods like `json()`, `text()`, `arrayBuffer()` handle all parsing and body collection
|
||||
- Response body is streamed and collected only when these methods are called
|
||||
- Body can only be consumed once (throws error on second attempt)
|
||||
- `types.ts`: Core TypeScript interfaces and types
|
||||
- `plugins.ts`: Centralized dependencies
|
||||
|
||||
### Modern API
|
||||
- SmartRequestClient: Fluent API with method chaining
|
||||
- Returns SmartResponse objects with fetch-like methods
|
||||
- Supports pagination, retries, timeouts, and various response types
|
||||
|
||||
### Binary Request Handling
|
||||
- Binary requests are handled correctly when `responseType: 'binary'` is set
|
||||
- Response body is kept as Buffer without string conversion
|
||||
- No automatic transformations applied to binary data
|
||||
|
||||
### Legacy Compatibility
|
||||
- All legacy functions (getJson, postJson, etc.) are maintained through adapter.ts
|
||||
- Legacy API returns IExtendedIncomingMessage for backward compatibility
|
||||
- Modern API can be accessed alongside legacy API
|
||||
|
||||
## Testing
|
||||
- Use `pnpm test` to run all tests
|
||||
- Modern API tests use the new SmartResponse methods (response.json(), response.text())
|
||||
- Legacy API tests continue to use the body property directly
|
558
readme.md
Normal file
558
readme.md
Normal file
@@ -0,0 +1,558 @@
|
||||
# @push.rocks/smartrequest
|
||||
A modern HTTP/HTTPS request library for Node.js with support for form data, file uploads, JSON, binary data, streams, and unix sockets. Features both a legacy API for backward compatibility and a modern fetch-like API for new projects.
|
||||
|
||||
## Install
|
||||
To install `@push.rocks/smartrequest`, use one of the following commands:
|
||||
|
||||
```bash
|
||||
# Using npm
|
||||
npm install @push.rocks/smartrequest --save
|
||||
|
||||
# Using pnpm
|
||||
pnpm add @push.rocks/smartrequest
|
||||
|
||||
# Using yarn
|
||||
yarn add @push.rocks/smartrequest
|
||||
```
|
||||
|
||||
This will add `@push.rocks/smartrequest` to your project's dependencies.
|
||||
|
||||
## Key Features
|
||||
|
||||
- 🚀 **Modern Fetch-like API** - Familiar response methods (`.json()`, `.text()`, `.arrayBuffer()`, `.stream()`)
|
||||
- 🔄 **Two API Styles** - Legacy function-based API and modern fluent chainable API
|
||||
- 🌐 **Unix Socket Support** - Connect to local services like Docker
|
||||
- 📦 **Form Data & File Uploads** - Built-in support for multipart/form-data
|
||||
- 🔁 **Pagination Support** - Multiple strategies (offset, cursor, Link headers)
|
||||
- ⚡ **Keep-Alive Connections** - Efficient connection pooling
|
||||
- 🛡️ **TypeScript First** - Full type safety and IntelliSense support
|
||||
- 🎯 **Zero Magic Defaults** - Explicit configuration following fetch API principles
|
||||
- 🔌 **Streaming Support** - Handle large files and real-time data
|
||||
- 🔧 **Highly Configurable** - Timeouts, retries, headers, and more
|
||||
|
||||
## Usage
|
||||
|
||||
`@push.rocks/smartrequest` is designed as a versatile, modern HTTP client library for making HTTP/HTTPS requests in Node.js environments. It provides a clean, type-safe API inspired by the native fetch API but with additional features needed for server-side applications.
|
||||
|
||||
The library provides two distinct APIs:
|
||||
|
||||
1. **Legacy API** - Simple function-based API for quick requests and backward compatibility
|
||||
2. **Modern Fluent API** - A chainable, fetch-like API for more complex scenarios
|
||||
|
||||
Below we will cover key usage scenarios of `@push.rocks/smartrequest`, showcasing its capabilities and providing you with a solid starting point to integrate it into your projects.
|
||||
|
||||
### Import Guide
|
||||
|
||||
```typescript
|
||||
// Modern API (recommended for new projects)
|
||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
||||
|
||||
// Legacy API (for backward compatibility)
|
||||
import { getJson, postJson, request } from '@push.rocks/smartrequest/legacy';
|
||||
```
|
||||
|
||||
### Simple GET Request
|
||||
|
||||
For fetching data from a REST API or any web service that returns JSON:
|
||||
|
||||
```typescript
|
||||
import { getJson } from '@push.rocks/smartrequest/legacy';
|
||||
|
||||
async function fetchGitHubUserInfo(username: string) {
|
||||
const response = await getJson(`https://api.github.com/users/${username}`);
|
||||
console.log(response.body); // The body contains the JSON response
|
||||
}
|
||||
|
||||
fetchGitHubUserInfo('octocat');
|
||||
```
|
||||
|
||||
The `getJson` function simplifies the process of sending a GET request and parsing the JSON response.
|
||||
|
||||
### POST Requests with JSON
|
||||
|
||||
When you need to send JSON data to a server, for example, creating a new resource:
|
||||
|
||||
```typescript
|
||||
import { postJson } from '@push.rocks/smartrequest/legacy';
|
||||
|
||||
async function createTodoItem(todoDetails: { title: string; completed: boolean }) {
|
||||
const response = await postJson('https://jsonplaceholder.typicode.com/todos', {
|
||||
requestBody: todoDetails
|
||||
});
|
||||
console.log(response.body); // Log the created todo item
|
||||
}
|
||||
|
||||
createTodoItem({ title: 'Implement smartrequest', completed: false });
|
||||
```
|
||||
|
||||
`postJson` handles setting the appropriate content-type header and stringifies the JSON body.
|
||||
|
||||
### Handling Form Data and File Uploads
|
||||
|
||||
`@push.rocks/smartrequest` simplifies the process of uploading files and submitting form data to a server:
|
||||
|
||||
```typescript
|
||||
import { postFormData, IFormField } from '@push.rocks/smartrequest/legacy';
|
||||
|
||||
async function uploadProfilePicture(formDataFields: IFormField[]) {
|
||||
await postFormData('https://api.example.com/upload', {}, formDataFields);
|
||||
}
|
||||
|
||||
uploadProfilePicture([
|
||||
{ name: 'avatar', type: 'filePath', payload: './path/to/avatar.jpg', fileName: 'avatar.jpg', contentType: 'image/jpeg' },
|
||||
{ name: 'user_id', type: 'string', payload: '12345' }
|
||||
]);
|
||||
```
|
||||
|
||||
### Streaming Support
|
||||
|
||||
For cases when dealing with large datasets or streaming APIs, `@push.rocks/smartrequest` provides streaming capabilities:
|
||||
|
||||
```typescript
|
||||
import { getStream } from '@push.rocks/smartrequest/legacy';
|
||||
|
||||
async function streamLargeFile(url: string) {
|
||||
const stream = await getStream(url);
|
||||
|
||||
stream.on('data', (chunk) => {
|
||||
console.log('Received chunk of data.');
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
console.log('Stream ended.');
|
||||
});
|
||||
}
|
||||
|
||||
streamLargeFile('https://example.com/largefile');
|
||||
```
|
||||
|
||||
`getStream` allows you to handle data as it's received, which can be beneficial for performance and scalability.
|
||||
|
||||
### Advanced Options and Customization
|
||||
|
||||
`@push.rocks/smartrequest` is built to be flexible, allowing you to specify additional options to tailor requests to your needs:
|
||||
|
||||
```typescript
|
||||
import { request, ISmartRequestOptions } from '@push.rocks/smartrequest/legacy';
|
||||
|
||||
async function customRequestExample() {
|
||||
const options: ISmartRequestOptions = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Custom-Header': 'Value'
|
||||
},
|
||||
keepAlive: true // Enables connection keep-alive
|
||||
};
|
||||
|
||||
const response = await request('https://example.com/data', options);
|
||||
console.log(response.body);
|
||||
}
|
||||
|
||||
customRequestExample();
|
||||
```
|
||||
|
||||
`request` is the underlying function that powers the simpler `getJson`, `postJson`, etc., and provides you with full control over the HTTP request.
|
||||
|
||||
## Modern Fluent API
|
||||
|
||||
In addition to the legacy API shown above, `@push.rocks/smartrequest` provides a modern, fluent API with a fetch-like response interface that offers a more chainable and TypeScript-friendly approach to making HTTP requests.
|
||||
|
||||
### Basic Usage with the Modern API
|
||||
|
||||
```typescript
|
||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
||||
|
||||
// Simple GET request
|
||||
async function fetchUserData(userId: number) {
|
||||
const response = await SmartRequestClient.create()
|
||||
.url(`https://jsonplaceholder.typicode.com/users/${userId}`)
|
||||
.get();
|
||||
|
||||
// Use the fetch-like response API
|
||||
const userData = await response.json();
|
||||
console.log(userData); // The parsed JSON response
|
||||
}
|
||||
|
||||
// POST request with JSON body
|
||||
async function createPost(title: string, body: string, userId: number) {
|
||||
const response = await SmartRequestClient.create()
|
||||
.url('https://jsonplaceholder.typicode.com/posts')
|
||||
.json({ title, body, userId })
|
||||
.post();
|
||||
|
||||
const createdPost = await response.json();
|
||||
console.log(createdPost); // The created post
|
||||
}
|
||||
```
|
||||
|
||||
### Setting Headers and Query Parameters
|
||||
|
||||
```typescript
|
||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
||||
|
||||
async function searchRepositories(query: string, perPage: number = 10) {
|
||||
const response = await SmartRequestClient.create()
|
||||
.url('https://api.github.com/search/repositories')
|
||||
.header('Accept', 'application/vnd.github.v3+json')
|
||||
.query({
|
||||
q: query,
|
||||
per_page: perPage.toString()
|
||||
})
|
||||
.get();
|
||||
|
||||
const data = await response.json();
|
||||
return data.items;
|
||||
}
|
||||
```
|
||||
|
||||
### Handling Timeouts and Retries
|
||||
|
||||
```typescript
|
||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
||||
|
||||
async function fetchWithRetry(url: string) {
|
||||
const response = await SmartRequestClient.create()
|
||||
.url(url)
|
||||
.timeout(5000) // 5 seconds timeout
|
||||
.retry(3) // Retry up to 3 times on failure
|
||||
.get();
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
```
|
||||
|
||||
### Working with Different Response Types
|
||||
|
||||
The modern API provides a fetch-like interface for handling different response types:
|
||||
|
||||
```typescript
|
||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
||||
|
||||
// JSON response (default)
|
||||
async function fetchJson(url: string) {
|
||||
const response = await SmartRequestClient.create()
|
||||
.url(url)
|
||||
.get();
|
||||
|
||||
return await response.json(); // Parses JSON automatically
|
||||
}
|
||||
|
||||
// Text response
|
||||
async function fetchText(url: string) {
|
||||
const response = await SmartRequestClient.create()
|
||||
.url(url)
|
||||
.get();
|
||||
|
||||
return await response.text(); // Returns response as string
|
||||
}
|
||||
|
||||
// Binary data
|
||||
async function downloadImage(url: string) {
|
||||
const response = await SmartRequestClient.create()
|
||||
.url(url)
|
||||
.accept('binary') // Optional: hints to server we want binary
|
||||
.get();
|
||||
|
||||
const buffer = await response.arrayBuffer();
|
||||
return Buffer.from(buffer); // Convert ArrayBuffer to Buffer if needed
|
||||
}
|
||||
|
||||
// Streaming response
|
||||
async function streamLargeFile(url: string) {
|
||||
const response = await SmartRequestClient.create()
|
||||
.url(url)
|
||||
.get();
|
||||
|
||||
// Get the underlying Node.js stream
|
||||
const stream = response.stream();
|
||||
|
||||
stream.on('data', (chunk) => {
|
||||
console.log(`Received ${chunk.length} bytes of data`);
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
stream.on('end', resolve);
|
||||
stream.on('error', reject);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Pagination Support
|
||||
|
||||
The modern API includes built-in support for various pagination strategies:
|
||||
|
||||
```typescript
|
||||
import { SmartRequestClient, PaginationStrategy } from '@push.rocks/smartrequest';
|
||||
|
||||
// Offset-based pagination (page & limit)
|
||||
async function fetchAllUsers() {
|
||||
const client = SmartRequestClient.create()
|
||||
.url('https://api.example.com/users')
|
||||
.withOffsetPagination({
|
||||
pageParam: 'page',
|
||||
limitParam: 'limit',
|
||||
startPage: 1,
|
||||
pageSize: 20,
|
||||
totalPath: 'meta.total'
|
||||
});
|
||||
|
||||
// Get first page with pagination info
|
||||
const firstPage = await client.getPaginated();
|
||||
console.log(`Found ${firstPage.items.length} users on first page`);
|
||||
console.log(`Has more pages: ${firstPage.hasNextPage}`);
|
||||
|
||||
if (firstPage.hasNextPage) {
|
||||
// Get next page
|
||||
const secondPage = await firstPage.getNextPage();
|
||||
console.log(`Found ${secondPage.items.length} more users`);
|
||||
}
|
||||
|
||||
// Or get all pages at once (use with caution for large datasets)
|
||||
const allUsers = await client.getAllPages();
|
||||
console.log(`Retrieved ${allUsers.length} users in total`);
|
||||
}
|
||||
|
||||
// Cursor-based pagination
|
||||
async function fetchAllPosts() {
|
||||
const allPosts = await SmartRequestClient.create()
|
||||
.url('https://api.example.com/posts')
|
||||
.withCursorPagination({
|
||||
cursorParam: 'cursor',
|
||||
cursorPath: 'meta.nextCursor',
|
||||
hasMorePath: 'meta.hasMore'
|
||||
})
|
||||
.getAllPages();
|
||||
|
||||
console.log(`Retrieved ${allPosts.length} posts in total`);
|
||||
}
|
||||
|
||||
// Link header-based pagination (GitHub API style)
|
||||
async function fetchAllIssues(repo: string) {
|
||||
const paginatedResponse = await SmartRequestClient.create()
|
||||
.url(`https://api.github.com/repos/${repo}/issues`)
|
||||
.header('Accept', 'application/vnd.github.v3+json')
|
||||
.withLinkPagination()
|
||||
.getPaginated();
|
||||
|
||||
return paginatedResponse.getAllPages();
|
||||
}
|
||||
```
|
||||
|
||||
### Advanced Features
|
||||
|
||||
#### Unix Socket Support
|
||||
|
||||
```typescript
|
||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
||||
|
||||
// Connect to a service via Unix socket
|
||||
async function queryViaUnixSocket() {
|
||||
const response = await SmartRequestClient.create()
|
||||
.url('http://unix:/var/run/docker.sock:/v1.24/containers/json')
|
||||
.get();
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
```
|
||||
|
||||
#### Form Data with File Uploads
|
||||
|
||||
```typescript
|
||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
||||
|
||||
async function uploadMultipleFiles(files: Array<{name: string, path: string}>) {
|
||||
const formFields = files.map(file => ({
|
||||
name: 'files',
|
||||
value: fs.readFileSync(file.path),
|
||||
filename: file.name,
|
||||
contentType: 'application/octet-stream'
|
||||
}));
|
||||
|
||||
const response = await SmartRequestClient.create()
|
||||
.url('https://api.example.com/upload')
|
||||
.formData(formFields)
|
||||
.post();
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
```
|
||||
|
||||
#### Keep-Alive Connections
|
||||
|
||||
```typescript
|
||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
||||
|
||||
// Enable keep-alive for better performance with multiple requests
|
||||
async function performMultipleRequests() {
|
||||
const client = SmartRequestClient.create()
|
||||
.header('Connection', 'keep-alive');
|
||||
|
||||
// Requests will reuse the same connection
|
||||
const results = await Promise.all([
|
||||
client.url('https://api.example.com/endpoint1').get(),
|
||||
client.url('https://api.example.com/endpoint2').get(),
|
||||
client.url('https://api.example.com/endpoint3').get()
|
||||
]);
|
||||
|
||||
return Promise.all(results.map(r => r.json()));
|
||||
}
|
||||
```
|
||||
|
||||
### Response Object Methods
|
||||
|
||||
The modern API returns a `SmartResponse` object with the following methods:
|
||||
|
||||
- `json<T>(): Promise<T>` - Parse response as JSON
|
||||
- `text(): Promise<string>` - Get response as text
|
||||
- `arrayBuffer(): Promise<ArrayBuffer>` - Get response as ArrayBuffer
|
||||
- `stream(): NodeJS.ReadableStream` - Get the underlying Node.js stream
|
||||
- `raw(): http.IncomingMessage` - Get the raw http.IncomingMessage
|
||||
|
||||
Each body method can only be called once per response, similar to the fetch API.
|
||||
|
||||
Through its comprehensive set of features tailored for modern web development, `@push.rocks/smartrequest` aims to provide developers with a powerful tool for handling HTTP/HTTPS requests efficiently. Whether it's a simple API call, handling form data, processing streams, or working with paginated APIs, `@push.rocks/smartrequest` delivers a robust, type-safe solution to fit your project's requirements.
|
||||
|
||||
## Migration Guide: Legacy API to Modern API
|
||||
|
||||
If you're currently using the legacy API and want to migrate to the modern fluent API, here's a quick reference guide:
|
||||
|
||||
| Legacy API | Modern API |
|
||||
|------------|------------|
|
||||
| `getJson(url)` | `SmartRequestClient.create().url(url).get()` |
|
||||
| `postJson(url, { requestBody: data })` | `SmartRequestClient.create().url(url).json(data).post()` |
|
||||
| `putJson(url, { requestBody: data })` | `SmartRequestClient.create().url(url).json(data).put()` |
|
||||
| `delJson(url)` | `SmartRequestClient.create().url(url).delete()` |
|
||||
| `postFormData(url, {}, fields)` | `SmartRequestClient.create().url(url).formData(fields).post()` |
|
||||
| `getStream(url)` | `SmartRequestClient.create().url(url).accept('stream').get()` |
|
||||
| `request(url, options)` | `SmartRequestClient.create().url(url).[...configure options...].get()` |
|
||||
|
||||
The modern API provides more flexibility and better TypeScript integration, making it the recommended approach for new projects.
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Building a REST API Client
|
||||
|
||||
Here's a complete example of building a typed API client using smartrequest:
|
||||
|
||||
```typescript
|
||||
import { SmartRequestClient, type SmartResponse } from '@push.rocks/smartrequest';
|
||||
|
||||
interface User {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
interface Post {
|
||||
id: number;
|
||||
title: string;
|
||||
body: string;
|
||||
userId: number;
|
||||
}
|
||||
|
||||
class BlogApiClient {
|
||||
private baseUrl = 'https://jsonplaceholder.typicode.com';
|
||||
|
||||
private async request(path: string) {
|
||||
return SmartRequestClient.create()
|
||||
.url(`${this.baseUrl}${path}`)
|
||||
.header('Accept', 'application/json');
|
||||
}
|
||||
|
||||
async getUser(id: number): Promise<User> {
|
||||
const response = await this.request(`/users/${id}`).get();
|
||||
return response.json<User>();
|
||||
}
|
||||
|
||||
async createPost(post: Omit<Post, 'id'>): Promise<Post> {
|
||||
const response = await this.request('/posts')
|
||||
.json(post)
|
||||
.post();
|
||||
return response.json<Post>();
|
||||
}
|
||||
|
||||
async deletePost(id: number): Promise<void> {
|
||||
const response = await this.request(`/posts/${id}`).delete();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to delete post: ${response.statusText}`);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllPosts(userId?: number): Promise<Post[]> {
|
||||
const client = this.request('/posts');
|
||||
|
||||
if (userId) {
|
||||
client.query({ userId: userId.toString() });
|
||||
}
|
||||
|
||||
const response = await client.get();
|
||||
return response.json<Post[]>();
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const api = new BlogApiClient();
|
||||
const user = await api.getUser(1);
|
||||
const posts = await api.getAllPosts(user.id);
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
```typescript
|
||||
import { SmartRequestClient } from '@push.rocks/smartrequest';
|
||||
|
||||
async function fetchWithErrorHandling(url: string) {
|
||||
try {
|
||||
const response = await SmartRequestClient.create()
|
||||
.url(url)
|
||||
.timeout(5000)
|
||||
.retry(2)
|
||||
.get();
|
||||
|
||||
// Check if request was successful
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
// Handle different content types
|
||||
const contentType = response.headers['content-type'];
|
||||
|
||||
if (contentType?.includes('application/json')) {
|
||||
return await response.json();
|
||||
} else if (contentType?.includes('text/')) {
|
||||
return await response.text();
|
||||
} else {
|
||||
return await response.arrayBuffer();
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code === 'ECONNREFUSED') {
|
||||
console.error('Connection refused - is the server running?');
|
||||
} else if (error.code === 'ETIMEDOUT') {
|
||||
console.error('Request timed out');
|
||||
} else {
|
||||
console.error('Request failed:', error.message);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
|
||||
|
||||
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
||||
|
||||
### Trademarks
|
||||
|
||||
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
|
||||
|
||||
### Company Information
|
||||
|
||||
Task Venture Capital GmbH
|
||||
Registered at District court Bremen HRB 35230 HB, Germany
|
||||
|
||||
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
||||
|
||||
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
53
readme.plan.md
Normal file
53
readme.plan.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Smartrequest Refactoring Plan
|
||||
|
||||
Command to reread CLAUDE.md: `cat /home/philkunz/.claude/CLAUDE.md`
|
||||
|
||||
## Objective
|
||||
Refactor smartrequest to use native fetch-like API with a streamlined core that supports unix sockets and keep-alive.
|
||||
|
||||
## Architecture Overview
|
||||
- Rename `legacy/` to `core/` and remove "smartrequest." prefix from filenames
|
||||
- Create a modern Response class similar to fetch API
|
||||
- Use core as foundation for modern API, not as legacy adapter
|
||||
- Maintain unix socket and keep-alive support
|
||||
|
||||
## Task Checklist
|
||||
|
||||
- [x] Reread /home/philkunz/.claude/CLAUDE.md
|
||||
- [x] Create ts/core directory structure with request.ts, types.ts, and plugins.ts
|
||||
- [x] Migrate core request logic from legacy to core/request.ts
|
||||
- [x] Create modern Response class with fetch-like API
|
||||
- [x] Update modern API to use new core module
|
||||
- [x] Create legacy adapter for backward compatibility
|
||||
- [x] Update exports in ts/index.ts
|
||||
- [x] Run tests and fix any issues
|
||||
- [x] Clean up old legacy files
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Core Module Structure
|
||||
```
|
||||
ts/core/
|
||||
├── request.ts # Core HTTP/HTTPS request logic with unix socket support
|
||||
├── types.ts # Core interfaces and types
|
||||
├── plugins.ts # Dependencies (http, https, agentkeepalive, etc.)
|
||||
└── response.ts # Modern Response class
|
||||
```
|
||||
|
||||
### Response Class API
|
||||
The new Response class will provide fetch-like methods:
|
||||
- `json()`: Promise<T> - Parse response as JSON
|
||||
- `text()`: Promise<string> - Get response as text
|
||||
- `arrayBuffer()`: Promise<ArrayBuffer> - Get response as ArrayBuffer
|
||||
- `stream()`: ReadableStream - Get response as stream
|
||||
- `ok`: boolean - Status is 2xx
|
||||
- `status`: number - HTTP status code
|
||||
- `statusText`: string - HTTP status text
|
||||
- `headers`: Headers - Response headers
|
||||
|
||||
### Migration Strategy
|
||||
1. Move core request logic without breaking changes
|
||||
2. Create Response wrapper that provides modern API
|
||||
3. Update SmartRequestClient to use new core
|
||||
4. Add legacy adapter for backward compatibility
|
||||
5. Ensure all tests pass throughout migration
|
1
test/test.d.ts
vendored
1
test/test.d.ts
vendored
@@ -1 +0,0 @@
|
||||
import 'typings-test';
|
18
test/test.js
18
test/test.js
@@ -1,18 +0,0 @@
|
||||
"use strict";
|
||||
require("typings-test");
|
||||
const smartchai_1 = require("smartchai");
|
||||
const smartrequest = require("../dist/index");
|
||||
describe('smartrequest', function () {
|
||||
it('should request a html document over https', function () {
|
||||
this.timeout(10000);
|
||||
return smartchai_1.expect(smartrequest.get('https://encrypted.google.com/')).to.eventually.property('body').be.a('string');
|
||||
});
|
||||
it('should request a JSON document over https', function () {
|
||||
return smartchai_1.expect(smartrequest.get('https://jsonplaceholder.typicode.com/posts/1')).to.eventually.property('body').property('id').equal(1);
|
||||
});
|
||||
it('should post a JSON document over http', function () {
|
||||
this.timeout(5000);
|
||||
return smartchai_1.expect(smartrequest.post('http://md5.jsontest.com/?text=example_text')).to.eventually.property('body').property('md5').equal('fa4c6baa0812e5b5c80ed8885e55a8a6');
|
||||
});
|
||||
});
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInRlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLHdCQUFxQjtBQUVyQix5Q0FBa0M7QUFFbEMsOENBQTZDO0FBRTdDLFFBQVEsQ0FBQyxjQUFjLEVBQUU7SUFDckIsRUFBRSxDQUFDLDJDQUEyQyxFQUFFO1FBQzVDLElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUE7UUFDbkIsTUFBTSxDQUFDLGtCQUFNLENBQUMsWUFBWSxDQUFDLEdBQUcsQ0FBQywrQkFBK0IsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQTtJQUNsSCxDQUFDLENBQUMsQ0FBQTtJQUVGLEVBQUUsQ0FBQywyQ0FBMkMsRUFBRTtRQUM1QyxNQUFNLENBQUMsa0JBQU0sQ0FBQyxZQUFZLENBQUMsR0FBRyxDQUFDLDhDQUE4QyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsVUFBVSxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFBO0lBQzFJLENBQUMsQ0FBQyxDQUFBO0lBRUYsRUFBRSxDQUFDLHVDQUF1QyxFQUFFO1FBQ3hDLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUE7UUFDbEIsTUFBTSxDQUFDLGtCQUFNLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyw0Q0FBNEMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxDQUFDLEtBQUssQ0FBQyxrQ0FBa0MsQ0FBQyxDQUFBO0lBQzNLLENBQUMsQ0FBQyxDQUFBO0FBQ04sQ0FBQyxDQUFDLENBQUEifQ==
|
45
test/test.legacy.ts
Normal file
45
test/test.legacy.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { tap, expect, expectAsync } from '@pushrocks/tapbundle';
|
||||
|
||||
import * as smartrequest from '../ts/legacy/index.js';
|
||||
|
||||
tap.test('should request a html document over https', async () => {
|
||||
await expectAsync(smartrequest.getJson('https://encrypted.google.com/')).toHaveProperty('body');
|
||||
});
|
||||
|
||||
tap.test('should request a JSON document over https', async () => {
|
||||
await expectAsync(smartrequest.getJson('https://jsonplaceholder.typicode.com/posts/1'))
|
||||
.property('body')
|
||||
.property('id')
|
||||
.toEqual(1);
|
||||
});
|
||||
|
||||
tap.test('should post a JSON document over http', async () => {
|
||||
const testData = { text: 'example_text' };
|
||||
await expectAsync(smartrequest.postJson('https://httpbin.org/post', { requestBody: testData }))
|
||||
.property('body')
|
||||
.property('json')
|
||||
.property('text')
|
||||
.toEqual('example_text');
|
||||
});
|
||||
|
||||
tap.test('should safe get stuff', async () => {
|
||||
smartrequest.safeGet('http://coffee.link/');
|
||||
smartrequest.safeGet('https://coffee.link/');
|
||||
});
|
||||
|
||||
tap.skip.test('should deal with unix socks', async () => {
|
||||
const socketResponse = await smartrequest.request(
|
||||
'http://unix:/var/run/docker.sock:/containers/json',
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Host: 'docker.sock',
|
||||
},
|
||||
}
|
||||
);
|
||||
console.log(socketResponse.body);
|
||||
});
|
||||
|
||||
tap.skip.test('should correctly upload a file using formData', async () => {});
|
||||
|
||||
tap.start();
|
95
test/test.modern.ts
Normal file
95
test/test.modern.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { tap, expect } from '@pushrocks/tapbundle';
|
||||
|
||||
import { SmartRequestClient } from '../ts/modern/index.js';
|
||||
|
||||
tap.test('modern: should request a html document over https', async () => {
|
||||
const response = await SmartRequestClient.create()
|
||||
.url('https://encrypted.google.com/')
|
||||
.get();
|
||||
|
||||
expect(response).not.toBeNull();
|
||||
expect(response).toHaveProperty('status');
|
||||
expect(response.status).toBeGreaterThan(0);
|
||||
const text = await response.text();
|
||||
expect(text.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
tap.test('modern: should request a JSON document over https', async () => {
|
||||
const response = await SmartRequestClient.create()
|
||||
.url('https://jsonplaceholder.typicode.com/posts/1')
|
||||
.get();
|
||||
|
||||
const body = await response.json();
|
||||
expect(body).toHaveProperty('id');
|
||||
expect(body.id).toEqual(1);
|
||||
});
|
||||
|
||||
tap.test('modern: should post a JSON document over http', async () => {
|
||||
const testData = { text: 'example_text' };
|
||||
const response = await SmartRequestClient.create()
|
||||
.url('https://httpbin.org/post')
|
||||
.json(testData)
|
||||
.post();
|
||||
|
||||
const body = await response.json();
|
||||
expect(body).toHaveProperty('json');
|
||||
expect(body.json).toHaveProperty('text');
|
||||
expect(body.json.text).toEqual('example_text');
|
||||
});
|
||||
|
||||
tap.test('modern: should set headers correctly', async () => {
|
||||
const customHeader = 'X-Custom-Header';
|
||||
const headerValue = 'test-value';
|
||||
|
||||
const response = await SmartRequestClient.create()
|
||||
.url('https://httpbin.org/headers')
|
||||
.header(customHeader, headerValue)
|
||||
.get();
|
||||
|
||||
const body = await response.json();
|
||||
expect(body).toHaveProperty('headers');
|
||||
|
||||
// Check if the header exists (case-sensitive)
|
||||
expect(body.headers).toHaveProperty(customHeader);
|
||||
expect(body.headers[customHeader]).toEqual(headerValue);
|
||||
});
|
||||
|
||||
tap.test('modern: should handle query parameters', async () => {
|
||||
const params = { param1: 'value1', param2: 'value2' };
|
||||
|
||||
const response = await SmartRequestClient.create()
|
||||
.url('https://httpbin.org/get')
|
||||
.query(params)
|
||||
.get();
|
||||
|
||||
const body = await response.json();
|
||||
expect(body).toHaveProperty('args');
|
||||
expect(body.args).toHaveProperty('param1');
|
||||
expect(body.args.param1).toEqual('value1');
|
||||
expect(body.args).toHaveProperty('param2');
|
||||
expect(body.args.param2).toEqual('value2');
|
||||
});
|
||||
|
||||
tap.test('modern: should handle timeout configuration', async () => {
|
||||
// This test just verifies that the timeout method doesn't throw
|
||||
const client = SmartRequestClient.create()
|
||||
.url('https://httpbin.org/get')
|
||||
.timeout(5000);
|
||||
|
||||
const response = await client.get();
|
||||
expect(response).toHaveProperty('ok');
|
||||
expect(response.ok).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('modern: should handle retry configuration', async () => {
|
||||
// This test just verifies that the retry method doesn't throw
|
||||
const client = SmartRequestClient.create()
|
||||
.url('https://httpbin.org/get')
|
||||
.retry(1);
|
||||
|
||||
const response = await client.get();
|
||||
expect(response).toHaveProperty('ok');
|
||||
expect(response.ok).toBeTrue();
|
||||
});
|
||||
|
||||
tap.start();
|
25
test/test.ts
25
test/test.ts
@@ -1,25 +0,0 @@
|
||||
import 'typings-test'
|
||||
|
||||
import { tap, expect } from 'tapbundle'
|
||||
|
||||
import * as smartrequest from '../dist/index'
|
||||
|
||||
tap.test('should request a html document over https', async () => {
|
||||
await expect(
|
||||
smartrequest.get('https://encrypted.google.com/')
|
||||
).to.eventually.property('body').be.a('string')
|
||||
})
|
||||
|
||||
tap.test('should request a JSON document over https', async () => {
|
||||
await expect(
|
||||
smartrequest.get('https://jsonplaceholder.typicode.com/posts/1')
|
||||
).to.eventually.property('body').property('id').equal(1)
|
||||
})
|
||||
|
||||
tap.test('should post a JSON document over http', async () => {
|
||||
await expect(
|
||||
smartrequest.post('http://md5.jsontest.com/?text=example_text')
|
||||
).to.eventually.property('body').property('md5').equal('fa4c6baa0812e5b5c80ed8885e55a8a6')
|
||||
})
|
||||
|
||||
tap.start()
|
8
ts/00_commitinfo_data.ts
Normal file
8
ts/00_commitinfo_data.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* autocreated commitinfo by @push.rocks/commitinfo
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartrequest',
|
||||
version: '2.1.0',
|
||||
description: 'A module for modern HTTP/HTTPS requests with support for form data, file uploads, JSON, binary data, streams, and more.'
|
||||
}
|
4
ts/core/index.ts
Normal file
4
ts/core/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// Core exports
|
||||
export * from './types.js';
|
||||
export * from './response.js';
|
||||
export { request, coreRequest, isUnixSocket, parseUnixSocketUrl } from './request.js';
|
19
ts/core/plugins.ts
Normal file
19
ts/core/plugins.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
// node native scope
|
||||
import * as fs from 'fs';
|
||||
import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
import * as path from 'path';
|
||||
|
||||
export { http, https, fs, path };
|
||||
|
||||
// pushrocks scope
|
||||
import * as smartpromise from '@push.rocks/smartpromise';
|
||||
import * as smarturl from '@push.rocks/smarturl';
|
||||
|
||||
export { smartpromise, smarturl };
|
||||
|
||||
// third party scope
|
||||
import agentkeepalive from 'agentkeepalive';
|
||||
import formData from 'form-data';
|
||||
|
||||
export { agentkeepalive, formData };
|
159
ts/core/request.ts
Normal file
159
ts/core/request.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as types from './types.js';
|
||||
import { SmartResponse } from './response.js';
|
||||
|
||||
// Keep-alive agents for connection pooling
|
||||
const httpAgent = new plugins.agentkeepalive({
|
||||
keepAlive: true,
|
||||
maxFreeSockets: 10,
|
||||
maxSockets: 100,
|
||||
maxTotalSockets: 1000,
|
||||
});
|
||||
|
||||
const httpAgentKeepAliveFalse = new plugins.agentkeepalive({
|
||||
keepAlive: false,
|
||||
});
|
||||
|
||||
const httpsAgent = new plugins.agentkeepalive.HttpsAgent({
|
||||
keepAlive: true,
|
||||
maxFreeSockets: 10,
|
||||
maxSockets: 100,
|
||||
maxTotalSockets: 1000,
|
||||
});
|
||||
|
||||
const httpsAgentKeepAliveFalse = new plugins.agentkeepalive.HttpsAgent({
|
||||
keepAlive: false,
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests if a URL is a unix socket
|
||||
*/
|
||||
export const isUnixSocket = (url: string): boolean => {
|
||||
const unixRegex = /^(http:\/\/|https:\/\/|)unix:/;
|
||||
return unixRegex.test(url);
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses socket path and route from unix socket URL
|
||||
*/
|
||||
export const parseUnixSocketUrl = (url: string): { socketPath: string; path: string } => {
|
||||
const parseRegex = /(.*):(.*)/;
|
||||
const result = parseRegex.exec(url);
|
||||
return {
|
||||
socketPath: result[1],
|
||||
path: result[2],
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Core request function that handles all HTTP/HTTPS requests
|
||||
*/
|
||||
export async function coreRequest(
|
||||
urlArg: string,
|
||||
optionsArg: types.ICoreRequestOptions = {},
|
||||
requestDataFunc: ((req: plugins.http.ClientRequest) => void) | null = null
|
||||
): Promise<plugins.http.IncomingMessage> {
|
||||
const done = plugins.smartpromise.defer<plugins.http.IncomingMessage>();
|
||||
|
||||
// No defaults - let users explicitly set options to match fetch behavior
|
||||
|
||||
// Parse URL
|
||||
const parsedUrl = plugins.smarturl.Smarturl.createFromUrl(urlArg, {
|
||||
searchParams: optionsArg.queryParams || {},
|
||||
});
|
||||
|
||||
optionsArg.hostname = parsedUrl.hostname;
|
||||
if (parsedUrl.port) {
|
||||
optionsArg.port = parseInt(parsedUrl.port, 10);
|
||||
}
|
||||
optionsArg.path = parsedUrl.path;
|
||||
|
||||
// Handle unix socket URLs
|
||||
if (isUnixSocket(urlArg)) {
|
||||
const { socketPath, path } = parseUnixSocketUrl(optionsArg.path);
|
||||
optionsArg.socketPath = socketPath;
|
||||
optionsArg.path = path;
|
||||
}
|
||||
|
||||
// Determine agent based on protocol and keep-alive setting
|
||||
if (!optionsArg.agent) {
|
||||
// Only use keep-alive agents if explicitly requested
|
||||
if (optionsArg.keepAlive === true) {
|
||||
optionsArg.agent = parsedUrl.protocol === 'https:' ? httpsAgent : httpAgent;
|
||||
} else if (optionsArg.keepAlive === false) {
|
||||
optionsArg.agent = parsedUrl.protocol === 'https:' ? httpsAgentKeepAliveFalse : httpAgentKeepAliveFalse;
|
||||
}
|
||||
// If keepAlive is undefined, don't set any agent (more fetch-like behavior)
|
||||
}
|
||||
|
||||
// Determine request module
|
||||
const requestModule = parsedUrl.protocol === 'https:' ? plugins.https : plugins.http;
|
||||
|
||||
if (!requestModule) {
|
||||
throw new Error(`The request to ${urlArg} is missing a viable protocol. Must be http or https`);
|
||||
}
|
||||
|
||||
// Perform the request
|
||||
const request = requestModule.request(optionsArg, async (response) => {
|
||||
// Handle hard timeout
|
||||
if (optionsArg.hardDataCuttingTimeout) {
|
||||
setTimeout(() => {
|
||||
response.destroy();
|
||||
done.reject(new Error('Request timed out'));
|
||||
}, optionsArg.hardDataCuttingTimeout);
|
||||
}
|
||||
|
||||
// Always return the raw stream
|
||||
done.resolve(response);
|
||||
});
|
||||
|
||||
// Write request body
|
||||
if (optionsArg.requestBody) {
|
||||
if (optionsArg.requestBody instanceof plugins.formData) {
|
||||
optionsArg.requestBody.pipe(request).on('finish', () => {
|
||||
request.end();
|
||||
});
|
||||
} else {
|
||||
// Write body as-is - caller is responsible for serialization
|
||||
const bodyData = typeof optionsArg.requestBody === 'string'
|
||||
? optionsArg.requestBody
|
||||
: optionsArg.requestBody instanceof Buffer
|
||||
? optionsArg.requestBody
|
||||
: JSON.stringify(optionsArg.requestBody); // Still stringify for backward compatibility
|
||||
request.write(bodyData);
|
||||
request.end();
|
||||
}
|
||||
} else if (requestDataFunc) {
|
||||
requestDataFunc(request);
|
||||
} else {
|
||||
request.end();
|
||||
}
|
||||
|
||||
// Handle request errors
|
||||
request.on('error', (e) => {
|
||||
console.error(e);
|
||||
request.destroy();
|
||||
done.reject(e);
|
||||
});
|
||||
|
||||
// Get response and handle response errors
|
||||
const response = await done.promise;
|
||||
response.on('error', (err) => {
|
||||
console.error(err);
|
||||
response.destroy();
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modern request function that returns a SmartResponse
|
||||
*/
|
||||
export async function request(
|
||||
urlArg: string,
|
||||
optionsArg: types.ICoreRequestOptions = {}
|
||||
): Promise<SmartResponse> {
|
||||
const response = await coreRequest(urlArg, optionsArg);
|
||||
return new SmartResponse(response, urlArg);
|
||||
}
|
110
ts/core/response.ts
Normal file
110
ts/core/response.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as types from './types.js';
|
||||
|
||||
/**
|
||||
* Modern Response class that provides a fetch-like API
|
||||
*/
|
||||
export class SmartResponse<T = any> implements types.ICoreResponse<T> {
|
||||
private incomingMessage: plugins.http.IncomingMessage;
|
||||
private bodyBufferPromise: Promise<Buffer> | null = null;
|
||||
private consumed = false;
|
||||
|
||||
// Public properties
|
||||
public readonly ok: boolean;
|
||||
public readonly status: number;
|
||||
public readonly statusText: string;
|
||||
public readonly headers: plugins.http.IncomingHttpHeaders;
|
||||
public readonly url: string;
|
||||
|
||||
constructor(incomingMessage: plugins.http.IncomingMessage, url: string) {
|
||||
this.incomingMessage = incomingMessage;
|
||||
this.url = url;
|
||||
this.status = incomingMessage.statusCode || 0;
|
||||
this.statusText = incomingMessage.statusMessage || '';
|
||||
this.ok = this.status >= 200 && this.status < 300;
|
||||
this.headers = incomingMessage.headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the body can only be consumed once
|
||||
*/
|
||||
private ensureNotConsumed(): void {
|
||||
if (this.consumed) {
|
||||
throw new Error('Body has already been consumed');
|
||||
}
|
||||
this.consumed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects the body as a buffer
|
||||
*/
|
||||
private async collectBody(): Promise<Buffer> {
|
||||
this.ensureNotConsumed();
|
||||
|
||||
if (this.bodyBufferPromise) {
|
||||
return this.bodyBufferPromise;
|
||||
}
|
||||
|
||||
this.bodyBufferPromise = new Promise<Buffer>((resolve, reject) => {
|
||||
const chunks: Buffer[] = [];
|
||||
|
||||
this.incomingMessage.on('data', (chunk: Buffer) => {
|
||||
chunks.push(chunk);
|
||||
});
|
||||
|
||||
this.incomingMessage.on('end', () => {
|
||||
resolve(Buffer.concat(chunks));
|
||||
});
|
||||
|
||||
this.incomingMessage.on('error', reject);
|
||||
});
|
||||
|
||||
return this.bodyBufferPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse response as JSON
|
||||
*/
|
||||
async json(): Promise<T> {
|
||||
const buffer = await this.collectBody();
|
||||
const text = buffer.toString('utf-8');
|
||||
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to parse JSON: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get response as text
|
||||
*/
|
||||
async text(): Promise<string> {
|
||||
const buffer = await this.collectBody();
|
||||
return buffer.toString('utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get response as ArrayBuffer
|
||||
*/
|
||||
async arrayBuffer(): Promise<ArrayBuffer> {
|
||||
const buffer = await this.collectBody();
|
||||
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get response as a readable stream
|
||||
*/
|
||||
stream(): NodeJS.ReadableStream {
|
||||
this.ensureNotConsumed();
|
||||
return this.incomingMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw IncomingMessage (for legacy compatibility)
|
||||
*/
|
||||
raw(): plugins.http.IncomingMessage {
|
||||
return this.incomingMessage;
|
||||
}
|
||||
|
||||
}
|
67
ts/core/types.ts
Normal file
67
ts/core/types.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
/**
|
||||
* Core request options extending Node.js RequestOptions
|
||||
*/
|
||||
export interface ICoreRequestOptions extends plugins.https.RequestOptions {
|
||||
keepAlive?: boolean;
|
||||
requestBody?: any;
|
||||
queryParams?: { [key: string]: string };
|
||||
hardDataCuttingTimeout?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP Methods supported
|
||||
*/
|
||||
export type THttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
|
||||
|
||||
/**
|
||||
* Response types supported
|
||||
*/
|
||||
export type ResponseType = 'json' | 'text' | 'binary' | 'stream';
|
||||
|
||||
/**
|
||||
* Extended IncomingMessage with body property (legacy compatibility)
|
||||
*/
|
||||
export interface IExtendedIncomingMessage<T = any> extends plugins.http.IncomingMessage {
|
||||
body: T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form field data for multipart/form-data requests
|
||||
*/
|
||||
export interface IFormField {
|
||||
name: string;
|
||||
value: string | Buffer;
|
||||
filename?: string;
|
||||
contentType?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* URL encoded form field
|
||||
*/
|
||||
export interface IUrlEncodedField {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Core response object that provides fetch-like API
|
||||
*/
|
||||
export interface ICoreResponse<T = any> {
|
||||
// Properties
|
||||
ok: boolean;
|
||||
status: number;
|
||||
statusText: string;
|
||||
headers: plugins.http.IncomingHttpHeaders;
|
||||
url: string;
|
||||
|
||||
// Methods
|
||||
json(): Promise<T>;
|
||||
text(): Promise<string>;
|
||||
arrayBuffer(): Promise<ArrayBuffer>;
|
||||
stream(): NodeJS.ReadableStream;
|
||||
|
||||
// Legacy compatibility
|
||||
raw(): plugins.http.IncomingMessage;
|
||||
}
|
39
ts/index.ts
39
ts/index.ts
@@ -1,33 +1,12 @@
|
||||
import * as https from 'https'
|
||||
// Legacy API exports (for backward compatibility)
|
||||
export * from './legacy/index.js';
|
||||
|
||||
import * as plugins from './smartrequest.plugins'
|
||||
import * as interfaces from './smartrequest.interfaces'
|
||||
// Modern API exports
|
||||
export * from './modern/index.js';
|
||||
|
||||
import { request } from './smartrequest.request'
|
||||
// Core exports for advanced usage
|
||||
export { SmartResponse, type ICoreRequestOptions, type ICoreResponse } from './core/index.js';
|
||||
|
||||
export { request } from './smartrequest.request'
|
||||
export { ISmartRequestOptions } from './smartrequest.interfaces'
|
||||
|
||||
export let get = async (domainArg: string, optionsArg: interfaces.ISmartRequestOptions = {}) => {
|
||||
optionsArg.method = 'GET'
|
||||
let response = await request(domainArg, optionsArg)
|
||||
return response
|
||||
}
|
||||
|
||||
export let post = async (domainArg: string, optionsArg: interfaces.ISmartRequestOptions = {}) => {
|
||||
optionsArg.method = 'POST'
|
||||
let response = await request(domainArg, optionsArg)
|
||||
return response
|
||||
}
|
||||
|
||||
export let put = async (domainArg: string, optionsArg: interfaces.ISmartRequestOptions = {}) => {
|
||||
optionsArg.method = 'PUT'
|
||||
let response = await request(domainArg, optionsArg)
|
||||
return response
|
||||
}
|
||||
|
||||
export let del = async (domainArg: string, optionsArg: interfaces.ISmartRequestOptions = {}) => {
|
||||
optionsArg.method = 'DELETE'
|
||||
let response = await request(domainArg, optionsArg)
|
||||
return response
|
||||
}
|
||||
// Default export for easier importing
|
||||
import { SmartRequestClient } from './modern/smartrequestclient.js';
|
||||
export default SmartRequestClient;
|
242
ts/legacy/adapter.ts
Normal file
242
ts/legacy/adapter.ts
Normal file
@@ -0,0 +1,242 @@
|
||||
/**
|
||||
* Legacy adapter that provides backward compatibility
|
||||
* Maps legacy API to the new core module
|
||||
*/
|
||||
|
||||
import * as core from '../core/index.js';
|
||||
import * as plugins from '../core/plugins.js';
|
||||
|
||||
const smartpromise = plugins.smartpromise;
|
||||
|
||||
// Re-export types for backward compatibility
|
||||
export { type IExtendedIncomingMessage } from '../core/types.js';
|
||||
export interface ISmartRequestOptions extends core.ICoreRequestOptions {
|
||||
autoJsonParse?: boolean;
|
||||
responseType?: 'json' | 'text' | 'binary' | 'stream';
|
||||
}
|
||||
|
||||
// Re-export interface for form fields
|
||||
export interface IFormField {
|
||||
name: string;
|
||||
type: 'string' | 'filePath' | 'Buffer';
|
||||
payload: string | Buffer;
|
||||
fileName?: string;
|
||||
contentType?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to convert stream to IExtendedIncomingMessage for legacy compatibility
|
||||
*/
|
||||
async function streamToExtendedMessage(
|
||||
stream: plugins.http.IncomingMessage,
|
||||
autoJsonParse = true
|
||||
): Promise<core.IExtendedIncomingMessage> {
|
||||
const done = smartpromise.defer<core.IExtendedIncomingMessage>();
|
||||
const chunks: Buffer[] = [];
|
||||
|
||||
stream.on('data', (chunk: Buffer) => {
|
||||
chunks.push(chunk);
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
const buffer = Buffer.concat(chunks);
|
||||
const extendedMessage = stream as core.IExtendedIncomingMessage;
|
||||
|
||||
if (autoJsonParse) {
|
||||
const text = buffer.toString('utf-8');
|
||||
try {
|
||||
extendedMessage.body = JSON.parse(text);
|
||||
} catch (err) {
|
||||
extendedMessage.body = text;
|
||||
}
|
||||
} else {
|
||||
extendedMessage.body = buffer;
|
||||
}
|
||||
|
||||
done.resolve(extendedMessage);
|
||||
});
|
||||
|
||||
stream.on('error', (err) => {
|
||||
done.reject(err);
|
||||
});
|
||||
|
||||
return done.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy request function that returns IExtendedIncomingMessage
|
||||
*/
|
||||
export async function request(
|
||||
urlArg: string,
|
||||
optionsArg: ISmartRequestOptions = {},
|
||||
responseStreamArg = false,
|
||||
requestDataFunc?: (req: plugins.http.ClientRequest) => void
|
||||
): Promise<core.IExtendedIncomingMessage> {
|
||||
const stream = await core.coreRequest(urlArg, optionsArg, requestDataFunc);
|
||||
|
||||
if (responseStreamArg) {
|
||||
// For stream responses, just cast and return
|
||||
return stream as core.IExtendedIncomingMessage;
|
||||
}
|
||||
|
||||
// Convert stream to IExtendedIncomingMessage
|
||||
const autoJsonParse = optionsArg.autoJsonParse !== false;
|
||||
return streamToExtendedMessage(stream, autoJsonParse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe GET request
|
||||
*/
|
||||
export async function safeGet(urlArg: string): Promise<core.IExtendedIncomingMessage | null> {
|
||||
const agentToUse = urlArg.startsWith('http://')
|
||||
? new plugins.http.Agent()
|
||||
: new plugins.https.Agent();
|
||||
|
||||
try {
|
||||
const response = await request(urlArg, {
|
||||
method: 'GET',
|
||||
agent: agentToUse,
|
||||
timeout: 5000,
|
||||
hardDataCuttingTimeout: 5000,
|
||||
autoJsonParse: false,
|
||||
});
|
||||
return response;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET JSON request
|
||||
*/
|
||||
export async function getJson(urlArg: string, optionsArg: ISmartRequestOptions = {}) {
|
||||
optionsArg.method = 'GET';
|
||||
return request(urlArg, optionsArg);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST JSON request
|
||||
*/
|
||||
export async function postJson(urlArg: string, optionsArg: ISmartRequestOptions = {}) {
|
||||
optionsArg.method = 'POST';
|
||||
if (
|
||||
typeof optionsArg.requestBody === 'object' &&
|
||||
(!optionsArg.headers || !optionsArg.headers['Content-Type'])
|
||||
) {
|
||||
// make sure headers exist
|
||||
if (!optionsArg.headers) {
|
||||
optionsArg.headers = {};
|
||||
}
|
||||
|
||||
// assign the right Content-Type, leaving all other headers in place
|
||||
optionsArg.headers = {
|
||||
...optionsArg.headers,
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
}
|
||||
return request(urlArg, optionsArg);
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT JSON request
|
||||
*/
|
||||
export async function putJson(urlArg: string, optionsArg: ISmartRequestOptions = {}) {
|
||||
optionsArg.method = 'PUT';
|
||||
return request(urlArg, optionsArg);
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE JSON request
|
||||
*/
|
||||
export async function delJson(urlArg: string, optionsArg: ISmartRequestOptions = {}) {
|
||||
optionsArg.method = 'DELETE';
|
||||
return request(urlArg, optionsArg);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET binary data
|
||||
*/
|
||||
export async function getBinary(urlArg: string, optionsArg: ISmartRequestOptions = {}) {
|
||||
optionsArg = {
|
||||
...optionsArg,
|
||||
autoJsonParse: false,
|
||||
responseType: 'binary'
|
||||
};
|
||||
return request(urlArg, optionsArg);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST form data
|
||||
*/
|
||||
export async function postFormData(urlArg: string, formFields: IFormField[], optionsArg: ISmartRequestOptions = {}) {
|
||||
const form = new plugins.formData();
|
||||
|
||||
for (const formField of formFields) {
|
||||
if (formField.type === 'filePath') {
|
||||
const fileData = plugins.fs.readFileSync(
|
||||
plugins.path.isAbsolute(formField.payload as string)
|
||||
? formField.payload as string
|
||||
: plugins.path.join(process.cwd(), formField.payload as string)
|
||||
);
|
||||
form.append(formField.name, fileData, {
|
||||
filename: formField.fileName || plugins.path.basename(formField.payload as string),
|
||||
contentType: formField.contentType
|
||||
});
|
||||
} else if (formField.type === 'Buffer') {
|
||||
form.append(formField.name, formField.payload, {
|
||||
filename: formField.fileName,
|
||||
contentType: formField.contentType
|
||||
});
|
||||
} else {
|
||||
form.append(formField.name, formField.payload);
|
||||
}
|
||||
}
|
||||
|
||||
optionsArg.method = 'POST';
|
||||
optionsArg.requestBody = form;
|
||||
if (!optionsArg.headers) {
|
||||
optionsArg.headers = {};
|
||||
}
|
||||
optionsArg.headers = {
|
||||
...optionsArg.headers,
|
||||
...form.getHeaders()
|
||||
};
|
||||
|
||||
return request(urlArg, optionsArg);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST URL encoded form data
|
||||
*/
|
||||
export async function postFormDataUrlEncoded(
|
||||
urlArg: string,
|
||||
formFields: { key: string; content: string }[],
|
||||
optionsArg: ISmartRequestOptions = {}
|
||||
) {
|
||||
optionsArg.method = 'POST';
|
||||
if (!optionsArg.headers) {
|
||||
optionsArg.headers = {};
|
||||
}
|
||||
optionsArg.headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
|
||||
const urlEncodedBody = formFields
|
||||
.map(field => `${encodeURIComponent(field.key)}=${encodeURIComponent(field.content)}`)
|
||||
.join('&');
|
||||
|
||||
optionsArg.requestBody = urlEncodedBody;
|
||||
|
||||
return request(urlArg, optionsArg);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET stream
|
||||
*/
|
||||
export async function getStream(
|
||||
urlArg: string,
|
||||
optionsArg: ISmartRequestOptions = {}
|
||||
): Promise<plugins.http.IncomingMessage> {
|
||||
optionsArg.method = 'GET';
|
||||
const response = await request(urlArg, optionsArg, true);
|
||||
return response;
|
||||
}
|
2
ts/legacy/index.ts
Normal file
2
ts/legacy/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
// Export everything from the legacy adapter
|
||||
export * from './adapter.js';
|
175
ts/modern/features/pagination.ts
Normal file
175
ts/modern/features/pagination.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
import { type SmartResponse } from '../../core/index.js';
|
||||
import { type TPaginationConfig, PaginationStrategy, type TPaginatedResponse } from '../types/pagination.js';
|
||||
|
||||
/**
|
||||
* Creates a paginated response from a regular response
|
||||
*/
|
||||
export async function createPaginatedResponse<T>(
|
||||
response: SmartResponse<any>,
|
||||
paginationConfig: TPaginationConfig,
|
||||
queryParams: Record<string, string>,
|
||||
fetchNextPage: (params: Record<string, string>) => Promise<TPaginatedResponse<T>>
|
||||
): Promise<TPaginatedResponse<T>> {
|
||||
// Parse response body first
|
||||
const body = await response.json();
|
||||
|
||||
// Default to response.body for items if response is JSON
|
||||
let items: T[] = Array.isArray(body)
|
||||
? body
|
||||
: (body?.items || body?.data || body?.results || []);
|
||||
|
||||
let hasNextPage = false;
|
||||
let nextPageParams: Record<string, string> = {};
|
||||
|
||||
// Determine if there's a next page based on pagination strategy
|
||||
switch (paginationConfig.strategy) {
|
||||
case PaginationStrategy.OFFSET: {
|
||||
const config = paginationConfig;
|
||||
const currentPage = parseInt(queryParams[config.pageParam || 'page'] || String(config.startPage || 1));
|
||||
const limit = parseInt(queryParams[config.limitParam || 'limit'] || String(config.pageSize || 20));
|
||||
const total = getValueByPath(body, config.totalPath || 'total') || 0;
|
||||
|
||||
hasNextPage = currentPage * limit < total;
|
||||
|
||||
if (hasNextPage) {
|
||||
nextPageParams = {
|
||||
...queryParams,
|
||||
[config.pageParam || 'page']: String(currentPage + 1)
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PaginationStrategy.CURSOR: {
|
||||
const config = paginationConfig;
|
||||
const nextCursor = getValueByPath(body, config.cursorPath || 'nextCursor');
|
||||
const hasMore = getValueByPath(body, config.hasMorePath || 'hasMore');
|
||||
|
||||
hasNextPage = !!nextCursor || !!hasMore;
|
||||
|
||||
if (hasNextPage && nextCursor) {
|
||||
nextPageParams = {
|
||||
...queryParams,
|
||||
[config.cursorParam || 'cursor']: nextCursor
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PaginationStrategy.LINK_HEADER: {
|
||||
const linkHeader = response.headers['link'] || '';
|
||||
// Handle both string and string[] types for the link header
|
||||
const headerValue = Array.isArray(linkHeader) ? linkHeader[0] : linkHeader;
|
||||
const links = parseLinkHeader(headerValue);
|
||||
|
||||
hasNextPage = !!links.next;
|
||||
|
||||
if (hasNextPage && links.next) {
|
||||
// Extract query parameters from next link URL
|
||||
const url = new URL(links.next);
|
||||
nextPageParams = {};
|
||||
|
||||
url.searchParams.forEach((value, key) => {
|
||||
nextPageParams[key] = value;
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PaginationStrategy.CUSTOM: {
|
||||
const config = paginationConfig;
|
||||
hasNextPage = config.hasNextPage(response);
|
||||
|
||||
if (hasNextPage) {
|
||||
nextPageParams = config.getNextPageParams(response, queryParams);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a function to fetch the next page
|
||||
const getNextPage = async (): Promise<TPaginatedResponse<T>> => {
|
||||
if (!hasNextPage) {
|
||||
throw new Error('No more pages available');
|
||||
}
|
||||
|
||||
return fetchNextPage(nextPageParams);
|
||||
};
|
||||
|
||||
// Create a function to fetch all remaining pages
|
||||
const getAllPages = async (): Promise<T[]> => {
|
||||
const allItems = [...items];
|
||||
let currentPage: TPaginatedResponse<T> = { items, hasNextPage, getNextPage, getAllPages, response };
|
||||
|
||||
while (currentPage.hasNextPage) {
|
||||
try {
|
||||
currentPage = await currentPage.getNextPage();
|
||||
allItems.push(...currentPage.items);
|
||||
} catch (error) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return allItems;
|
||||
};
|
||||
|
||||
return {
|
||||
items,
|
||||
hasNextPage,
|
||||
getNextPage,
|
||||
getAllPages,
|
||||
response
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse Link header for pagination
|
||||
* Link: <https://api.example.com/users?page=2>; rel="next", <https://api.example.com/users?page=5>; rel="last"
|
||||
*/
|
||||
export function parseLinkHeader(header: string): Record<string, string> {
|
||||
const links: Record<string, string> = {};
|
||||
|
||||
if (!header) {
|
||||
return links;
|
||||
}
|
||||
|
||||
// Split parts by comma
|
||||
const parts = header.split(',');
|
||||
|
||||
// Parse each part into a name:value pair
|
||||
for (const part of parts) {
|
||||
const section = part.split(';');
|
||||
if (section.length < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const url = section[0].replace(/<(.*)>/, '$1').trim();
|
||||
const name = section[1].replace(/rel="(.*)"/, '$1').trim();
|
||||
|
||||
links[name] = url;
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a nested value from an object using dot notation path
|
||||
* e.g., getValueByPath(obj, "data.pagination.nextCursor")
|
||||
*/
|
||||
export function getValueByPath(obj: any, path?: string): any {
|
||||
if (!path || !obj) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const keys = path.split('.');
|
||||
let current = obj;
|
||||
|
||||
for (const key of keys) {
|
||||
if (current === null || current === undefined || typeof current !== 'object') {
|
||||
return undefined;
|
||||
}
|
||||
current = current[key];
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
48
ts/modern/index.ts
Normal file
48
ts/modern/index.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
// Export the main client
|
||||
export { SmartRequestClient } from './smartrequestclient.js';
|
||||
|
||||
// Export response type from core
|
||||
export { SmartResponse } from '../core/index.js';
|
||||
|
||||
// Export types
|
||||
export type { HttpMethod, ResponseType, FormField, RetryConfig, TimeoutConfig } from './types/common.js';
|
||||
export {
|
||||
PaginationStrategy,
|
||||
type TPaginationConfig as PaginationConfig,
|
||||
type OffsetPaginationConfig,
|
||||
type CursorPaginationConfig,
|
||||
type LinkPaginationConfig,
|
||||
type CustomPaginationConfig,
|
||||
type TPaginatedResponse as PaginatedResponse
|
||||
} from './types/pagination.js';
|
||||
|
||||
// Convenience factory functions
|
||||
import { SmartRequestClient } from './smartrequestclient.js';
|
||||
|
||||
/**
|
||||
* Create a client pre-configured for JSON requests
|
||||
*/
|
||||
export function createJsonClient<T = any>() {
|
||||
return SmartRequestClient.create<T>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a client pre-configured for form data requests
|
||||
*/
|
||||
export function createFormClient<T = any>() {
|
||||
return SmartRequestClient.create<T>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a client pre-configured for binary data
|
||||
*/
|
||||
export function createBinaryClient<T = any>() {
|
||||
return SmartRequestClient.create<T>().accept('binary');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a client pre-configured for streaming
|
||||
*/
|
||||
export function createStreamClient() {
|
||||
return SmartRequestClient.create().accept('stream');
|
||||
}
|
329
ts/modern/smartrequestclient.ts
Normal file
329
ts/modern/smartrequestclient.ts
Normal file
@@ -0,0 +1,329 @@
|
||||
import { request, SmartResponse, type ICoreRequestOptions } from '../core/index.js';
|
||||
import * as plugins from '../core/plugins.js';
|
||||
|
||||
import type { HttpMethod, ResponseType, FormField } from './types/common.js';
|
||||
import {
|
||||
type TPaginationConfig,
|
||||
PaginationStrategy,
|
||||
type OffsetPaginationConfig,
|
||||
type CursorPaginationConfig,
|
||||
type CustomPaginationConfig,
|
||||
type TPaginatedResponse
|
||||
} from './types/pagination.js';
|
||||
import { createPaginatedResponse } from './features/pagination.js';
|
||||
|
||||
/**
|
||||
* Modern fluent client for making HTTP requests
|
||||
*/
|
||||
export class SmartRequestClient<T = any> {
|
||||
private _url: string;
|
||||
private _options: ICoreRequestOptions = {};
|
||||
private _retries: number = 0;
|
||||
private _queryParams: Record<string, string> = {};
|
||||
private _paginationConfig?: TPaginationConfig;
|
||||
|
||||
/**
|
||||
* Create a new SmartRequestClient instance
|
||||
*/
|
||||
static create<T = any>(): SmartRequestClient<T> {
|
||||
return new SmartRequestClient<T>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the URL for the request
|
||||
*/
|
||||
url(url: string): this {
|
||||
this._url = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the HTTP method
|
||||
*/
|
||||
method(method: HttpMethod): this {
|
||||
this._options.method = method;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set JSON body for the request
|
||||
*/
|
||||
json(data: any): this {
|
||||
if (!this._options.headers) {
|
||||
this._options.headers = {};
|
||||
}
|
||||
this._options.headers['Content-Type'] = 'application/json';
|
||||
this._options.requestBody = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set form data for the request
|
||||
*/
|
||||
formData(data: FormField[]): this {
|
||||
const form = new plugins.formData();
|
||||
|
||||
for (const item of data) {
|
||||
if (Buffer.isBuffer(item.value)) {
|
||||
form.append(item.name, item.value, {
|
||||
filename: item.filename || 'file',
|
||||
contentType: item.contentType || 'application/octet-stream'
|
||||
});
|
||||
} else {
|
||||
form.append(item.name, item.value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this._options.headers) {
|
||||
this._options.headers = {};
|
||||
}
|
||||
|
||||
this._options.headers = {
|
||||
...this._options.headers,
|
||||
...form.getHeaders()
|
||||
};
|
||||
|
||||
this._options.requestBody = form;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set request timeout in milliseconds
|
||||
*/
|
||||
timeout(ms: number): this {
|
||||
this._options.timeout = ms;
|
||||
this._options.hardDataCuttingTimeout = ms;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set number of retry attempts
|
||||
*/
|
||||
retry(count: number): this {
|
||||
this._retries = count;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set HTTP headers
|
||||
*/
|
||||
headers(headers: Record<string, string>): this {
|
||||
if (!this._options.headers) {
|
||||
this._options.headers = {};
|
||||
}
|
||||
this._options.headers = {
|
||||
...this._options.headers,
|
||||
...headers
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a single HTTP header
|
||||
*/
|
||||
header(name: string, value: string): this {
|
||||
if (!this._options.headers) {
|
||||
this._options.headers = {};
|
||||
}
|
||||
this._options.headers[name] = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set query parameters
|
||||
*/
|
||||
query(params: Record<string, string>): this {
|
||||
this._queryParams = {
|
||||
...this._queryParams,
|
||||
...params
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Accept header to indicate what content type is expected
|
||||
*/
|
||||
accept(type: ResponseType): this {
|
||||
// Map response types to Accept header values
|
||||
const acceptHeaders: Record<ResponseType, string> = {
|
||||
'json': 'application/json',
|
||||
'text': 'text/plain',
|
||||
'binary': 'application/octet-stream',
|
||||
'stream': '*/*'
|
||||
};
|
||||
|
||||
return this.header('Accept', acceptHeaders[type]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure pagination for requests
|
||||
*/
|
||||
pagination(config: TPaginationConfig): this {
|
||||
this._paginationConfig = config;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure offset-based pagination (page & limit)
|
||||
*/
|
||||
withOffsetPagination(config: Omit<OffsetPaginationConfig, 'strategy'> = {}): this {
|
||||
this._paginationConfig = {
|
||||
strategy: PaginationStrategy.OFFSET,
|
||||
pageParam: config.pageParam || 'page',
|
||||
limitParam: config.limitParam || 'limit',
|
||||
startPage: config.startPage || 1,
|
||||
pageSize: config.pageSize || 20,
|
||||
totalPath: config.totalPath || 'total'
|
||||
};
|
||||
|
||||
// Add initial pagination parameters
|
||||
this.query({
|
||||
[this._paginationConfig.pageParam]: String(this._paginationConfig.startPage),
|
||||
[this._paginationConfig.limitParam]: String(this._paginationConfig.pageSize)
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure cursor-based pagination
|
||||
*/
|
||||
withCursorPagination(config: Omit<CursorPaginationConfig, 'strategy'> = {}): this {
|
||||
this._paginationConfig = {
|
||||
strategy: PaginationStrategy.CURSOR,
|
||||
cursorParam: config.cursorParam || 'cursor',
|
||||
cursorPath: config.cursorPath || 'nextCursor',
|
||||
hasMorePath: config.hasMorePath || 'hasMore'
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure Link header-based pagination
|
||||
*/
|
||||
withLinkPagination(): this {
|
||||
this._paginationConfig = {
|
||||
strategy: PaginationStrategy.LINK_HEADER
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure custom pagination
|
||||
*/
|
||||
withCustomPagination(config: Omit<CustomPaginationConfig, 'strategy'>): this {
|
||||
this._paginationConfig = {
|
||||
strategy: PaginationStrategy.CUSTOM,
|
||||
hasNextPage: config.hasNextPage,
|
||||
getNextPageParams: config.getNextPageParams
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a GET request
|
||||
*/
|
||||
async get<R = T>(): Promise<SmartResponse<R>> {
|
||||
return this.execute<R>('GET');
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a POST request
|
||||
*/
|
||||
async post<R = T>(): Promise<SmartResponse<R>> {
|
||||
return this.execute<R>('POST');
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a PUT request
|
||||
*/
|
||||
async put<R = T>(): Promise<SmartResponse<R>> {
|
||||
return this.execute<R>('PUT');
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a DELETE request
|
||||
*/
|
||||
async delete<R = T>(): Promise<SmartResponse<R>> {
|
||||
return this.execute<R>('DELETE');
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a PATCH request
|
||||
*/
|
||||
async patch<R = T>(): Promise<SmartResponse<R>> {
|
||||
return this.execute<R>('PATCH');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get paginated response
|
||||
*/
|
||||
async getPaginated<ItemType = T>(): Promise<TPaginatedResponse<ItemType>> {
|
||||
if (!this._paginationConfig) {
|
||||
throw new Error('Pagination not configured. Call one of the pagination methods first.');
|
||||
}
|
||||
|
||||
// Default to GET if no method specified
|
||||
if (!this._options.method) {
|
||||
this._options.method = 'GET';
|
||||
}
|
||||
|
||||
const response = await this.execute();
|
||||
|
||||
return await createPaginatedResponse<ItemType>(
|
||||
response,
|
||||
this._paginationConfig,
|
||||
this._queryParams,
|
||||
(nextPageParams) => {
|
||||
// Create a new client with the same configuration but updated query params
|
||||
const nextClient = new SmartRequestClient<ItemType>();
|
||||
Object.assign(nextClient, this);
|
||||
nextClient._queryParams = nextPageParams;
|
||||
|
||||
return nextClient.getPaginated<ItemType>();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all pages at once (use with caution for large datasets)
|
||||
*/
|
||||
async getAllPages<ItemType = T>(): Promise<ItemType[]> {
|
||||
const firstPage = await this.getPaginated<ItemType>();
|
||||
return firstPage.getAllPages();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the HTTP request
|
||||
*/
|
||||
private async execute<R = T>(method?: HttpMethod): Promise<SmartResponse<R>> {
|
||||
if (method) {
|
||||
this._options.method = method;
|
||||
}
|
||||
|
||||
this._options.queryParams = this._queryParams;
|
||||
|
||||
// Handle retry logic
|
||||
let lastError: Error;
|
||||
|
||||
for (let attempt = 0; attempt <= this._retries; attempt++) {
|
||||
try {
|
||||
const response = await request(this._url, this._options);
|
||||
return response as SmartResponse<R>;
|
||||
} catch (error) {
|
||||
lastError = error as Error;
|
||||
|
||||
// If this is the last attempt, throw the error
|
||||
if (attempt === this._retries) {
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
// Otherwise, wait before retrying
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
}
|
||||
|
||||
// This should never be reached due to the throw in the loop above
|
||||
throw lastError;
|
||||
}
|
||||
}
|
49
ts/modern/types/common.ts
Normal file
49
ts/modern/types/common.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* HTTP Methods supported by the client
|
||||
*/
|
||||
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
|
||||
|
||||
/**
|
||||
* Response types supported by the client
|
||||
*/
|
||||
export type ResponseType = 'json' | 'text' | 'binary' | 'stream';
|
||||
|
||||
/**
|
||||
* Form field data for multipart/form-data requests
|
||||
*/
|
||||
export interface FormField {
|
||||
name: string;
|
||||
value: string | Buffer;
|
||||
filename?: string;
|
||||
contentType?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* URL encoded form field
|
||||
*/
|
||||
export interface UrlEncodedField {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry configuration
|
||||
*/
|
||||
export interface RetryConfig {
|
||||
attempts: number; // Number of retry attempts
|
||||
initialDelay?: number; // Initial delay in ms
|
||||
maxDelay?: number; // Maximum delay in ms
|
||||
factor?: number; // Backoff factor
|
||||
statusCodes?: number[]; // Status codes to retry on
|
||||
shouldRetry?: (error: Error, attemptCount: number) => boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timeout configuration
|
||||
*/
|
||||
export interface TimeoutConfig {
|
||||
request?: number; // Overall request timeout in ms
|
||||
connection?: number; // Connection timeout in ms
|
||||
socket?: number; // Socket idle timeout in ms
|
||||
response?: number; // Response timeout in ms
|
||||
}
|
66
ts/modern/types/pagination.ts
Normal file
66
ts/modern/types/pagination.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { type SmartResponse } from '../../core/index.js';
|
||||
|
||||
/**
|
||||
* Pagination strategy options
|
||||
*/
|
||||
export enum PaginationStrategy {
|
||||
OFFSET = 'offset', // Uses page & limit parameters
|
||||
CURSOR = 'cursor', // Uses a cursor/token for next page
|
||||
LINK_HEADER = 'link', // Uses Link headers
|
||||
CUSTOM = 'custom' // Uses a custom pagination handler
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for offset-based pagination
|
||||
*/
|
||||
export interface OffsetPaginationConfig {
|
||||
strategy: PaginationStrategy.OFFSET;
|
||||
pageParam?: string; // Parameter name for page number (default: "page")
|
||||
limitParam?: string; // Parameter name for page size (default: "limit")
|
||||
startPage?: number; // Starting page number (default: 1)
|
||||
pageSize?: number; // Number of items per page (default: 20)
|
||||
totalPath?: string; // JSON path to total item count (default: "total")
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for cursor-based pagination
|
||||
*/
|
||||
export interface CursorPaginationConfig {
|
||||
strategy: PaginationStrategy.CURSOR;
|
||||
cursorParam?: string; // Parameter name for cursor (default: "cursor")
|
||||
cursorPath?: string; // JSON path to next cursor (default: "nextCursor")
|
||||
hasMorePath?: string; // JSON path to check if more items exist (default: "hasMore")
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for Link header-based pagination
|
||||
*/
|
||||
export interface LinkPaginationConfig {
|
||||
strategy: PaginationStrategy.LINK_HEADER;
|
||||
// No additional config needed, uses standard Link header format
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for custom pagination
|
||||
*/
|
||||
export interface CustomPaginationConfig {
|
||||
strategy: PaginationStrategy.CUSTOM;
|
||||
hasNextPage: (response: SmartResponse<any>) => boolean;
|
||||
getNextPageParams: (response: SmartResponse<any>, currentParams: Record<string, string>) => Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Union type of all pagination configurations
|
||||
*/
|
||||
export type TPaginationConfig = OffsetPaginationConfig | CursorPaginationConfig | LinkPaginationConfig | CustomPaginationConfig;
|
||||
|
||||
/**
|
||||
* Interface for a paginated response
|
||||
*/
|
||||
export interface TPaginatedResponse<T> {
|
||||
items: T[]; // Current page items
|
||||
hasNextPage: boolean; // Whether there are more pages
|
||||
getNextPage: () => Promise<TPaginatedResponse<T>>; // Function to get the next page
|
||||
getAllPages: () => Promise<T[]>; // Function to get all remaining pages and combine
|
||||
response: SmartResponse<any>; // Original response
|
||||
}
|
@@ -1,6 +0,0 @@
|
||||
import * as plugins from './smartrequest.plugins'
|
||||
import * as https from 'https'
|
||||
|
||||
export interface ISmartRequestOptions extends https.RequestOptions {
|
||||
requestBody?: any
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
import 'typings-global'
|
||||
import * as url from 'url'
|
||||
import * as http from 'http'
|
||||
import * as https from 'https'
|
||||
|
||||
import * as q from 'smartq'
|
||||
|
||||
export {
|
||||
url,
|
||||
http,
|
||||
https,
|
||||
q
|
||||
}
|
@@ -1,72 +0,0 @@
|
||||
import * as https from 'https'
|
||||
import * as plugins from './smartrequest.plugins'
|
||||
import * as interfaces from './smartrequest.interfaces'
|
||||
|
||||
let buildResponse = (responseArg): Promise<any> => {
|
||||
let done = plugins.q.defer()
|
||||
// Continuously update stream with data
|
||||
let body = ''
|
||||
responseArg.on('data', function (chunkArg) {
|
||||
body += chunkArg
|
||||
})
|
||||
responseArg.on('end', function () {
|
||||
try {
|
||||
responseArg.body = JSON.parse(body)
|
||||
} catch (err) {
|
||||
responseArg.body = body
|
||||
}
|
||||
done.resolve(responseArg)
|
||||
})
|
||||
return done.promise
|
||||
}
|
||||
|
||||
export let request = async (domainArg: string, optionsArg: interfaces.ISmartRequestOptions = {}, streamArg: boolean = false): Promise<Response> => {
|
||||
let done = plugins.q.defer<any>()
|
||||
let parsedUrl: plugins.url.Url
|
||||
if (domainArg) {
|
||||
parsedUrl = plugins.url.parse(domainArg)
|
||||
optionsArg.hostname = parsedUrl.hostname
|
||||
if (parsedUrl.port) { optionsArg.port = parseInt(parsedUrl.port) }
|
||||
optionsArg.path = parsedUrl.path
|
||||
}
|
||||
if (!parsedUrl || parsedUrl.protocol === 'https:') {
|
||||
let request = plugins.https.request(optionsArg, response => {
|
||||
if (streamArg) {
|
||||
done.resolve(response)
|
||||
} else {
|
||||
buildResponse(response).then(done.resolve)
|
||||
}
|
||||
})
|
||||
if (optionsArg.requestBody) {
|
||||
if (typeof optionsArg.requestBody !== 'string') {
|
||||
optionsArg.requestBody = JSON.stringify(optionsArg.requestBody)
|
||||
}
|
||||
request.write(optionsArg.requestBody)
|
||||
}
|
||||
request.on('error', (e) => {
|
||||
console.error(e)
|
||||
})
|
||||
request.end()
|
||||
} else if (parsedUrl.protocol === 'http:') {
|
||||
let request = plugins.http.request(optionsArg, response => {
|
||||
if (streamArg) {
|
||||
done.resolve(response)
|
||||
} else {
|
||||
buildResponse(response).then(done.resolve)
|
||||
}
|
||||
})
|
||||
if (optionsArg.requestBody) {
|
||||
if (typeof optionsArg.requestBody !== 'string') {
|
||||
optionsArg.requestBody = JSON.stringify(optionsArg.requestBody)
|
||||
}
|
||||
request.write(optionsArg.requestBody)
|
||||
}
|
||||
request.on('error', (e) => {
|
||||
console.error(e)
|
||||
})
|
||||
request.end()
|
||||
} else {
|
||||
throw new Error(`unsupported protocol: ${parsedUrl.protocol}`)
|
||||
}
|
||||
return done.promise
|
||||
}
|
14
tsconfig.json
Normal file
14
tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"useDefineForClassFields": false,
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"esModuleInterop": true,
|
||||
"verbatimModuleSyntax": true
|
||||
},
|
||||
"exclude": [
|
||||
"dist_*/**/*.d.ts"
|
||||
]
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": "tslint-config-standard"
|
||||
}
|
310
yarn.lock
310
yarn.lock
@@ -1,310 +0,0 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@types/chai-as-promised@0.0.29":
|
||||
version "0.0.29"
|
||||
resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-0.0.29.tgz#43d52892aa998e185a3de3e2477edb8573be1d77"
|
||||
dependencies:
|
||||
"@types/chai" "*"
|
||||
"@types/promises-a-plus" "*"
|
||||
|
||||
"@types/chai-string@^1.1.30":
|
||||
version "1.1.30"
|
||||
resolved "https://registry.yarnpkg.com/@types/chai-string/-/chai-string-1.1.30.tgz#4d8744b31a5a2295fc01c981ed1e2d4c8a070f0a"
|
||||
dependencies:
|
||||
"@types/chai" "*"
|
||||
|
||||
"@types/chai@*", "@types/chai@^3.4.35":
|
||||
version "3.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-3.5.2.tgz#c11cd2817d3a401b7ba0f5a420f35c56139b1c1e"
|
||||
|
||||
"@types/mocha@^2.2.31":
|
||||
version "2.2.41"
|
||||
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.41.tgz#e27cf0817153eb9f2713b2d3f6c68f1e1c3ca608"
|
||||
|
||||
"@types/node@*", "@types/node@^7.0.29":
|
||||
version "7.0.29"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.29.tgz#ccfcec5b7135c7caf6c4ffb8c7f33102340d99df"
|
||||
|
||||
"@types/promises-a-plus@*":
|
||||
version "0.0.27"
|
||||
resolved "https://registry.yarnpkg.com/@types/promises-a-plus/-/promises-a-plus-0.0.27.tgz#c64651134614c84b8f5d7114ce8901d36a609780"
|
||||
|
||||
"@types/shelljs@^0.6.0":
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.6.0.tgz#090b705c102ce7fc5c0c5ea9b524418ff15840df"
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/which@^1.0.28":
|
||||
version "1.0.28"
|
||||
resolved "https://registry.yarnpkg.com/@types/which/-/which-1.0.28.tgz#016e387629b8817bed653fe32eab5d11279c8df6"
|
||||
|
||||
ansi-256-colors@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-256-colors/-/ansi-256-colors-1.1.0.tgz#910de50efcc7c09e3d82f2f87abd6b700c18818a"
|
||||
|
||||
assertion-error@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c"
|
||||
|
||||
balanced-match@^0.4.1:
|
||||
version "0.4.2"
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838"
|
||||
|
||||
beautycolor@^1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/beautycolor/-/beautycolor-1.0.7.tgz#a4715738ac4c8221371e9cbeb5a6cc6d11ecbf7c"
|
||||
dependencies:
|
||||
ansi-256-colors "^1.1.0"
|
||||
typings-global "^1.0.14"
|
||||
|
||||
bindings@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.2.1.tgz#14ad6113812d2d37d72e67b4cacb4bb726505f11"
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.7"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.7.tgz#3effc3c50e000531fb720eaff80f0ae8ef23cf59"
|
||||
dependencies:
|
||||
balanced-match "^0.4.1"
|
||||
concat-map "0.0.1"
|
||||
|
||||
chai-as-promised@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/chai-as-promised/-/chai-as-promised-6.0.0.tgz#1a02a433a6f24dafac63b9c96fa1684db1aa8da6"
|
||||
dependencies:
|
||||
check-error "^1.0.2"
|
||||
|
||||
chai-string@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/chai-string/-/chai-string-1.3.0.tgz#df6139f294391b1035be5606f60a843b3a5041e7"
|
||||
|
||||
chai@^3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/chai/-/chai-3.5.0.tgz#4d02637b067fe958bdbfdd3a40ec56fef7373247"
|
||||
dependencies:
|
||||
assertion-error "^1.0.1"
|
||||
deep-eql "^0.1.3"
|
||||
type-detect "^1.0.0"
|
||||
|
||||
check-error@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
|
||||
|
||||
concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
|
||||
deep-eql@^0.1.3:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2"
|
||||
dependencies:
|
||||
type-detect "0.1.1"
|
||||
|
||||
early@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/early/-/early-2.1.1.tgz#841e23254ea5dc54d8afaeee82f5ab65c00ee23c"
|
||||
dependencies:
|
||||
beautycolor "^1.0.7"
|
||||
smartq "^1.1.1"
|
||||
typings-global "^1.0.16"
|
||||
|
||||
es6-error@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.0.2.tgz#eec5c726eacef51b7f6b73c20db6e1b13b069c98"
|
||||
|
||||
fs.realpath@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||
|
||||
glob@^7.0.0:
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
|
||||
dependencies:
|
||||
fs.realpath "^1.0.0"
|
||||
inflight "^1.0.4"
|
||||
inherits "2"
|
||||
minimatch "^3.0.4"
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
inflight@^1.0.4:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
||||
dependencies:
|
||||
once "^1.3.0"
|
||||
wrappy "1"
|
||||
|
||||
inherits@2:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||
|
||||
interpret@^1.0.0:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.3.tgz#cbc35c62eeee73f19ab7b10a801511401afc0f90"
|
||||
|
||||
isexe@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||
|
||||
leakage@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/leakage/-/leakage-0.3.0.tgz#15d698abdc76bbc6439601f4f3020e77e2d50c39"
|
||||
dependencies:
|
||||
es6-error "^4.0.2"
|
||||
left-pad "^1.1.3"
|
||||
memwatch-next "^0.3.0"
|
||||
minimist "^1.2.0"
|
||||
pretty-bytes "^4.0.2"
|
||||
|
||||
left-pad@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.1.3.tgz#612f61c033f3a9e08e939f1caebeea41b6f3199a"
|
||||
|
||||
memwatch-next@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/memwatch-next/-/memwatch-next-0.3.0.tgz#2111050f9a906e0aa2d72a4ec0f0089c78726f8f"
|
||||
dependencies:
|
||||
bindings "^1.2.1"
|
||||
nan "^2.3.2"
|
||||
|
||||
minimatch@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimist@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
||||
|
||||
nan@^2.3.2:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45"
|
||||
|
||||
once@^1.3.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||
dependencies:
|
||||
wrappy "1"
|
||||
|
||||
path-is-absolute@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
||||
|
||||
path-parse@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
|
||||
|
||||
pretty-bytes@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9"
|
||||
|
||||
rechoir@^0.6.2:
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
|
||||
dependencies:
|
||||
resolve "^1.1.6"
|
||||
|
||||
resolve@^1.1.6:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5"
|
||||
dependencies:
|
||||
path-parse "^1.0.5"
|
||||
|
||||
semver@^5.3.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
|
||||
|
||||
shelljs@^0.7.6:
|
||||
version "0.7.8"
|
||||
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3"
|
||||
dependencies:
|
||||
glob "^7.0.0"
|
||||
interpret "^1.0.0"
|
||||
rechoir "^0.6.2"
|
||||
|
||||
smartchai@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/smartchai/-/smartchai-1.0.3.tgz#de6d010bb8b5aef24cb70b31a5f5334e8c41b72f"
|
||||
dependencies:
|
||||
"@types/chai" "^3.4.35"
|
||||
"@types/chai-as-promised" "0.0.29"
|
||||
"@types/chai-string" "^1.1.30"
|
||||
chai "^3.5.0"
|
||||
chai-as-promised "^6.0.0"
|
||||
chai-string "^1.3.0"
|
||||
|
||||
smartdelay@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/smartdelay/-/smartdelay-1.0.3.tgz#5fd44dad77262d110702f0293efa80c072cfb579"
|
||||
dependencies:
|
||||
smartq "^1.1.1"
|
||||
typings-global "^1.0.16"
|
||||
|
||||
smartq@^1.1.0, smartq@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/smartq/-/smartq-1.1.1.tgz#efb358705260d41ae18aef7ffd815f7b6fe17dd3"
|
||||
dependencies:
|
||||
typed-promisify "^0.3.0"
|
||||
typings-global "^1.0.14"
|
||||
|
||||
smartshell@^1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/smartshell/-/smartshell-1.0.6.tgz#27b1c79029784abe72ac7e91fe698b7ebecc6629"
|
||||
dependencies:
|
||||
"@types/shelljs" "^0.6.0"
|
||||
"@types/which" "^1.0.28"
|
||||
shelljs "^0.7.6"
|
||||
smartq "^1.1.0"
|
||||
which "^1.2.12"
|
||||
|
||||
tapbundle@^1.0.14:
|
||||
version "1.0.14"
|
||||
resolved "https://registry.yarnpkg.com/tapbundle/-/tapbundle-1.0.14.tgz#75827e335fcb02216f0267a26a26d702ddc02e3c"
|
||||
dependencies:
|
||||
early "^2.1.1"
|
||||
leakage "^0.3.0"
|
||||
smartchai "^1.0.3"
|
||||
smartdelay "^1.0.3"
|
||||
smartq "^1.1.1"
|
||||
typings-global "^1.0.16"
|
||||
|
||||
type-detect@0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822"
|
||||
|
||||
type-detect@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2"
|
||||
|
||||
typed-promisify@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/typed-promisify/-/typed-promisify-0.3.0.tgz#1ba0af5e444c87d8047406f18ce49092a1191853"
|
||||
|
||||
typings-global@*, typings-global@^1.0.14, typings-global@^1.0.16, typings-global@^1.0.17:
|
||||
version "1.0.17"
|
||||
resolved "https://registry.yarnpkg.com/typings-global/-/typings-global-1.0.17.tgz#41edc331ccec3168289adc8849e1e255efbe7152"
|
||||
dependencies:
|
||||
"@types/node" "^7.0.29"
|
||||
semver "^5.3.0"
|
||||
smartshell "^1.0.6"
|
||||
|
||||
typings-test@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/typings-test/-/typings-test-1.0.3.tgz#fbab895eb3f0c44842e73db059f65946b971e369"
|
||||
dependencies:
|
||||
"@types/mocha" "^2.2.31"
|
||||
typings-global "*"
|
||||
|
||||
which@^1.2.12:
|
||||
version "1.2.14"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5"
|
||||
dependencies:
|
||||
isexe "^2.0.0"
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
Reference in New Issue
Block a user