Skip to content

Commit 7f51efd

Browse files
authored
Merge pull request #128 from chantouchsek/feat/add-first-by-to-validator
Added new first by method validator class
2 parents 015e220 + 87c25c0 commit 7f51efd

21 files changed

+1101
-2408
lines changed

README.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,27 @@ export default {
5050
// simple usage
5151
'vue-api-queries/nuxt',
5252
// With options
53-
['vue-api-queries/nuxt', { errorProperty: 'errors' }],
53+
['vue-api-queries/nuxt', { errorProperty: 'errors', blockDuplicate: false }],
5454
'@nuxtjs/axios',
5555
],
56-
apiQueries: { errorProperty: 'errors' },
56+
apiQueries: { errorProperty: 'errors', blockDuplicate: false },
5757
}
5858
```
5959

60+
### Options
61+
62+
If `blockDuplicate` enabled, the default option of `axios-duplicate-blocker` will be:
63+
```js
64+
{
65+
blockDuplicate: false
66+
debug: true
67+
onPageChange: true
68+
blockByDefault: true
69+
headerBlockerKey: ''
70+
}
71+
```
72+
you can overwrite it, by adding in config above.
73+
6074
### Note:
6175

6276
`baseURL` is required.

jest.config.js

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,10 @@ module.exports = {
55
collectCoverageFrom: ['src/**/*.ts', 'src/**/*.js'],
66
coverageThreshold: {
77
global: {
8-
// branches: 100,
9-
// functions: 100,
10-
// lines: 100,
11-
// statements: 100,
12-
branches: 65,
13-
functions: 70,
14-
lines: 50,
15-
statements: 50,
8+
lines: 100,
9+
functions: 100,
10+
branches: 90,
11+
statements: 99,
1612
},
1713
},
1814
testEnvironment: 'node',

nuxt/index.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
import { resolve, join } from 'path'
2-
import merge from 'lodash.merge'
32

43
module.exports = function nuxtVueApiQueriesModule(moduleOptions = {}) {
5-
const { apiQueries = {} } = this.options
6-
const options = merge({}, moduleOptions, apiQueries)
4+
const { apiQueries = {}, dev } = this.options
5+
const defaultOpts = {
6+
debug: dev,
7+
onPageChange: true,
8+
blockByDefault: true,
9+
headerBlockerKey: '',
10+
}
11+
const options = Object.assign(defaultOpts, moduleOptions, apiQueries)
712
this.addPlugin({
13+
options,
14+
ssr: true,
815
src: resolve(__dirname, './templates/plugin.js'),
916
fileName: join('vue-api-queries.js'),
10-
options,
1117
})
1218
this.options.build.transpile.push(/^escape-string-regexp/)
1319
}

nuxt/templates/plugin.js

Lines changed: 154 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,158 @@
11
import Vue from 'vue'
2-
import VueApiQueries, { Validator } from 'vue-api-queries'
2+
import { CancelToken } from 'axios'
3+
import VueApiQueries, { isPlainObject, Validator } from 'vue-api-queries'
34

4-
const options = <%= serialize(options) %> || {}
5-
const { errorProperty, parsedQs } = options
5+
const errorProperty = '<%= options.errorProperty %>'
6+
const parsedQs = '<%= options.parsedQs %>'
7+
const blockDuplicate = '<%= options.blockDuplicate %>'
68

7-
export default function (ctx) {
8-
Vue.use(VueApiQueries, { errorProperty, axios: ctx.$axios, parsedQs })
9-
ctx.$errors = Validator
9+
function createRequestKey(url, params) {
10+
let requestKey = url
11+
if (params) {
12+
requestKey += `:${createStringFromParameters(params)}`
13+
}
14+
return requestKey
15+
}
16+
17+
function createStringFromParameters(obj) {
18+
const keysArr = []
19+
for (const key in obj) {
20+
keysArr.push(key)
21+
if (isPlainObject(obj[key])) {
22+
keysArr.push(createStringFromParameters(obj[key]))
23+
}
24+
}
25+
return keysArr.join('|')
26+
}
27+
28+
function createCancelMessage(requestKey, paramsStr) {
29+
return {
30+
statusCode: 100,
31+
requestKey: requestKey,
32+
message: `Request canceled: ${requestKey}`,
33+
paramsStr: paramsStr,
34+
}
35+
}
36+
37+
export default function ({ $axios, app }, inject) {
38+
if (blockDuplicate === 'true') {
39+
$axios.activeRequests = {}
40+
$axios.onRequest((config) => {
41+
let blockerConfigContainer = config
42+
const headerBlockerKey = '<%= options.headerBlockerKey %>'
43+
if (headerBlockerKey) {
44+
if (config.headers.hasOwnProperty(headerBlockerKey)) {
45+
blockerConfigContainer = config.headers[headerBlockerKey]
46+
delete config.headers[headerBlockerKey]
47+
}
48+
}
49+
let requestBlockingAllowed = blockerConfigContainer.blockAllowed
50+
if (requestBlockingAllowed === undefined) {
51+
requestBlockingAllowed = '<%= options.blockByDefault %>' === 'true'
52+
}
53+
if (!requestBlockingAllowed) {
54+
return config
55+
}
56+
57+
//Check if user has set a custom requestKey
58+
let { requestKey } = blockerConfigContainer
59+
if (!requestKey) {
60+
//If there is no custom requestKey, create a default requestKey based on url and parameters
61+
requestKey = createRequestKey(
62+
config.baseURL + config.url,
63+
config.params,
64+
)
65+
}
66+
const paramsStr = JSON.stringify(config.params)
67+
//If another request with the same requestKey already exists, cancel it
68+
if (
69+
$axios.activeRequests.hasOwnProperty(requestKey) &&
70+
$axios.activeRequests[requestKey].cancelToken
71+
) {
72+
$axios.activeRequests[requestKey].cancelToken.cancel(
73+
createCancelMessage(requestKey, paramsStr),
74+
)
75+
}
76+
if (!$axios.hasOwnProperty(requestKey)) {
77+
//If request has not been sent before, create a custom promise
78+
let reqResolve, reqReject
79+
const promise = new Promise((resolve, reject) => {
80+
reqResolve = resolve
81+
reqReject = reject
82+
})
83+
//Insert current request to activeRequests
84+
$axios.activeRequests[requestKey] = {
85+
promise: promise,
86+
resolve: reqResolve,
87+
reject: reqReject,
88+
}
89+
}
90+
//Update the active request's params
91+
$axios.activeRequests[requestKey].paramsStr = paramsStr
92+
//Create a cancel token for current request
93+
const cancelToken = CancelToken.source()
94+
$axios.activeRequests[requestKey].cancelToken = cancelToken
95+
//Add the cancel token to the request
96+
return {
97+
...config,
98+
cancelToken: cancelToken && cancelToken.token,
99+
}
100+
})
101+
$axios.onError((err) => {
102+
//Check if error message has a requestKey set in active requests
103+
if (
104+
err.hasOwnProperty('message') &&
105+
err.message.hasOwnProperty('requestKey') &&
106+
$axios.activeRequests.hasOwnProperty(err.message.requestKey)
107+
) {
108+
const currentRequest = $axios.activeRequests[err.message.requestKey]
109+
//Check if error concerns a cancellation
110+
if (
111+
err.message &&
112+
err.message.statusCode === 100 &&
113+
currentRequest &&
114+
currentRequest.paramsStr === err.message.paramsStr
115+
) {
116+
//Handle the cancellation error
117+
const debug = '<%= options.debug %>'
118+
if (debug === 'true') {
119+
console.warn(err.message.message)
120+
}
121+
//Return a promise to the active request that overrides the current one
122+
return $axios.activeRequests[err.message.requestKey].promise
123+
}
124+
}
125+
return Promise.reject(err)
126+
})
127+
$axios.onResponse((response) => {
128+
//Check if user has set a custom requestKey
129+
let { requestKey } = response.config
130+
if (!requestKey) {
131+
//If there is no custom requestKey, create a default requestKey based on url and parameters
132+
requestKey = createRequestKey(
133+
response.config.url,
134+
response.config.params,
135+
)
136+
}
137+
if ($axios.activeRequests.hasOwnProperty(requestKey)) {
138+
//Inform all previously cancelled requests with the current response & remove requestKey from activeRequests
139+
$axios.activeRequests[requestKey].resolve(response)
140+
delete $axios.activeRequests[requestKey]
141+
}
142+
})
143+
const onPageChange = '<%= options.onPageChange %>'
144+
if (onPageChange === 'true') {
145+
app.router.beforeEach((to, from, next) => {
146+
for (const requestKey in $axios.activeRequests) {
147+
$axios.activeRequests[requestKey].cancelToken.cancel(
148+
createCancelMessage(requestKey),
149+
)
150+
delete $axios.activeRequests[requestKey]
151+
}
152+
next()
153+
})
154+
}
155+
}
156+
Vue.use(VueApiQueries, { errorProperty, $axios, parsedQs })
157+
inject('errors', Validator)
10158
}

package.json

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,14 @@
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",
77
"scripts": {
8-
"dev": "nuxt-ts demo",
98
"lint": "eslint './src/**/*.{js,ts,tsx}'",
109
"test": "jest --env=jsdom",
1110
"test:watch": "jest --watchAll",
1211
"test:watch:jsdom": "jest --watchAll --env=jsdom",
1312
"build": "yarn clean && tsc",
1413
"watch": "tsc -w",
1514
"start": "yarn link && nodemon",
16-
"semantic-release": "semantic-release",
15+
"release": "standard-version && git push --follow-tags origin main && yarn publish",
1716
"prepublish": "yarn test && yarn build",
1817
"clean": "rimraf dist",
1918
"prepare": "husky install"
@@ -29,11 +28,16 @@
2928
"rest",
3029
"query",
3130
"builder",
32-
"laravel"
31+
"laravel",
32+
"queries",
33+
"vue-api-queries",
34+
"vue api queries",
35+
"api request"
3336
],
3437
"author": {
3538
"name": "Chantouch Sek",
36-
"email": "[email protected]"
39+
"email": "[email protected]",
40+
"url": "https://chantouch.me"
3741
},
3842
"license": "ISC",
3943
"bugs": {
@@ -46,7 +50,6 @@
4650
"@nuxt/types": "^2.15.8",
4751
"@types/escape-string-regexp": "^2.0.1",
4852
"@types/jest": "^27.0.1",
49-
"@types/lodash.merge": "^4.6.6",
5053
"@types/node": "^16.9.3",
5154
"@types/qs": "^6.9.7",
5255
"@typescript-eslint/eslint-plugin": "^4.31.1",
@@ -62,12 +65,14 @@
6265
"husky": "^7.0.2",
6366
"jest": "^27.2.0",
6467
"nodemon": "^2.0.12",
65-
"nuxt": "^2.15.8",
68+
"nuxt-edge": "^2.16.0-27217455.034b9901",
6669
"prettier": "^2.4.1",
6770
"rimraf": "^3.0.2",
68-
"semantic-release": "^18.0.0",
71+
"standard-version": "^9.3.1",
6972
"ts-jest": "^27.0.5",
70-
"typescript": "^4.4.3"
73+
"typescript": "^4.4.3",
74+
"vue": "^2.6.14",
75+
"vue-api-queries": "^1.1.4"
7176
},
7277
"files": [
7378
"dist",

src/__tests__/base-proxy.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Axios from 'axios'
22
import BaseProxy from '../core/BaseProxy'
33
import MockAdapter from 'axios-mock-adapter'
4-
import PostProxy from '../core/PostPorxy'
4+
import PostProxy from '../util/PostPorxy'
55
import type { ValidatorType } from '../core/Validator'
66
import Validator from '../core/Validator'
77
import BaseTransformer from '../core/BaseTransformer'

src/__tests__/form-data.test.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { objectToFormData } from '../util/formData'
1+
import { objectToFormData } from '../util'
22

33
describe('FormData', () => {
44
it('if object is null, undefined or array length is zero', async () => {
@@ -11,4 +11,18 @@ describe('FormData', () => {
1111
expect(formData.get('items1')).toHaveLength(0)
1212
expect(formData.get('name')).toBe('')
1313
})
14+
it('window is undefined', async () => {
15+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
16+
// @ts-ignore
17+
delete global.window
18+
const formData = objectToFormData({
19+
null: '',
20+
items1: [],
21+
name: null,
22+
__proto__: 'Hi',
23+
})
24+
expect(formData.get('null')).toBe('')
25+
expect(formData.get('items1')).toHaveLength(0)
26+
expect(formData.get('name')).toBe('')
27+
})
1428
})

src/__tests__/object.test.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { isFile, cloneDeep } from '../util/objects'
1+
import {
2+
isFile,
3+
cloneDeep,
4+
hasOwnProperty,
5+
merge,
6+
isPlainObject,
7+
} from '../util'
28

39
describe('Object Test', () => {
410
// const { window, File } = global
@@ -35,4 +41,36 @@ describe('cloneDeep', () => {
3541
const obj = [{ name: 'Chantouch' }]
3642
expect(cloneDeep(obj)).toBeInstanceOf(Object)
3743
})
44+
it('Has own property undefined', () => {
45+
const obj = [{ name: 'Chantouch' }]
46+
expect(hasOwnProperty(obj, undefined)).toBeFalsy()
47+
})
48+
it('Merge object into object', () => {
49+
const obj1 = { name: 'Chantouch' }
50+
const obj2 = { email: '[email protected]' }
51+
const merged = { email: '[email protected]', name: 'Chantouch' }
52+
expect(merge(obj1, obj2)).toEqual(merged)
53+
})
54+
it('should return `true` if the object is created by the `Object` constructor.', () => {
55+
expect(isPlainObject(Object.create({}))).toBeTruthy()
56+
expect(isPlainObject(Object.create(Object.prototype))).toBeTruthy()
57+
expect(isPlainObject({ foo: 'bar' })).toBeTruthy()
58+
expect(isPlainObject({})).toBeTruthy()
59+
expect(isPlainObject(Object.create(null))).toBeTruthy()
60+
})
61+
it('should return `false` if the object is not created by the `Object` constructor.', () => {
62+
function Foo(this: any) {
63+
this.abc = {}
64+
}
65+
expect(isPlainObject(/foo/)).toBeFalsy()
66+
// eslint-disable-next-line @typescript-eslint/no-empty-function
67+
expect(isPlainObject(function () {})).toBeFalsy()
68+
expect(isPlainObject(1)).toBeFalsy()
69+
expect(isPlainObject(['foo', 'bar'])).toBeFalsy()
70+
expect(isPlainObject([])).toBeFalsy()
71+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
72+
// @ts-ignore
73+
expect(isPlainObject(new Foo())).toBeFalsy()
74+
expect(isPlainObject(null)).toBeFalsy()
75+
})
3876
})

0 commit comments

Comments
 (0)