@pollyjs/core
Advanced tools
Comparing version 1.2.0 to 1.3.0
@@ -6,2 +6,22 @@ # Change Log | ||
# [1.3.0](https://github.com/netflix/pollyjs/tree/master/packages/@pollyjs/core/compare/v1.2.0...v1.3.0) (2018-11-28) | ||
### Bug Fixes | ||
* Support URL objects ([#139](https://github.com/netflix/pollyjs/tree/master/packages/[@pollyjs](https://github.com/pollyjs)/core/issues/139)) ([cf0d755](https://github.com/netflix/pollyjs/tree/master/packages/@pollyjs/core/commit/cf0d755)) | ||
* **core:** Handle trailing slashes when generating route names ([#142](https://github.com/netflix/pollyjs/tree/master/packages/[@pollyjs](https://github.com/pollyjs)/core/issues/142)) ([19147f7](https://github.com/netflix/pollyjs/tree/master/packages/@pollyjs/core/commit/19147f7)) | ||
* **core:** Ignore `context` options from being deep merged ([#144](https://github.com/netflix/pollyjs/tree/master/packages/[@pollyjs](https://github.com/pollyjs)/core/issues/144)) ([2123d83](https://github.com/netflix/pollyjs/tree/master/packages/@pollyjs/core/commit/2123d83)) | ||
* **core:** Support multiple handlers for same paths ([#141](https://github.com/netflix/pollyjs/tree/master/packages/[@pollyjs](https://github.com/pollyjs)/core/issues/141)) ([79e04b8](https://github.com/netflix/pollyjs/tree/master/packages/@pollyjs/core/commit/79e04b8)) | ||
### Features | ||
* **core:** Support custom functions in matchRequestsBy config options ([#138](https://github.com/netflix/pollyjs/tree/master/packages/[@pollyjs](https://github.com/pollyjs)/core/issues/138)) ([626a84c](https://github.com/netflix/pollyjs/tree/master/packages/@pollyjs/core/commit/626a84c)) | ||
* Add an onIdentifyRequest hook to allow adapter level serialization ([#140](https://github.com/netflix/pollyjs/tree/master/packages/[@pollyjs](https://github.com/pollyjs)/core/issues/140)) ([548002c](https://github.com/netflix/pollyjs/tree/master/packages/@pollyjs/core/commit/548002c)) | ||
<a name="1.2.0"></a> | ||
@@ -8,0 +28,0 @@ # 1.2.0 (2018-09-16) |
{ | ||
"name": "@pollyjs/core", | ||
"version": "1.2.0", | ||
"version": "1.3.0", | ||
"description": "Record, replay, and stub HTTP Interactions", | ||
@@ -20,4 +20,2 @@ "main": "dist/cjs/pollyjs-core.js", | ||
"watch": "yarn rollup -w", | ||
"format": "prettier --config ../../../.prettier.js --write **/*.js", | ||
"lint": "eslint ./*.js src tests", | ||
"prepublishOnly": "npm-run-all clean --parallel rollup rollup:prod" | ||
@@ -50,3 +48,3 @@ }, | ||
"dependencies": { | ||
"@pollyjs/utils": "^1.2.0", | ||
"@pollyjs/utils": "^1.3.0", | ||
"@sindresorhus/fnv1a": "^1.0.0", | ||
@@ -56,17 +54,15 @@ "blueimp-md5": "^2.10.0", | ||
"is-absolute-url": "^2.1.0", | ||
"lodash-es": "^4.17.10", | ||
"merge-options": "^1.0.1", | ||
"lodash-es": "^4.17.11", | ||
"route-recognizer": "^0.3.4", | ||
"slugify": "^1.3.1" | ||
"slugify": "^1.3.3" | ||
}, | ||
"devDependencies": { | ||
"@pollyjs/adapter": "^1.2.0", | ||
"@pollyjs/adapter-fetch": "^1.2.0", | ||
"@pollyjs/persister": "^1.2.0", | ||
"eslint": "^4.18.1", | ||
"@pollyjs/adapter": "^1.3.0", | ||
"@pollyjs/adapter-fetch": "^1.3.0", | ||
"@pollyjs/persister": "^1.3.0", | ||
"npm-run-all": "^4.1.3", | ||
"prettier": "^1.14.2", | ||
"rimraf": "^2.6.2", | ||
"rollup": "^0.65.2" | ||
} | ||
"rollup": "^0.67.0" | ||
}, | ||
"gitHead": "222c38c81155b4f737e9dfcfe1f61a985465b812" | ||
} |
@@ -12,2 +12,4 @@ <p align="center"> | ||
> Interested in contributing or just seeing Polly in action? Head over to [CONTRIBUTING.md](CONTRIBUTING.md) to learn how to spin up the project! | ||
## Why Polly? | ||
@@ -180,2 +182,19 @@ | ||
## Credits | ||
_In alphabetical order:_ | ||
- [Jason Mitchell](https://twitter.com/_jasonmit) - Creator / Maintainer | ||
- [Offir Golan](https://twitter.com/offirgolan) - Creator / Maintainer | ||
- [Sophinie Som](https://twitter.com/s0phinie) - Branding / Logo | ||
## We're hiring! | ||
Join the Netflix Studio & Content Engineering teams to help us build projects like this! | ||
Open Roles: | ||
- [Senior UI Engineer - Production Visibility Engineering](https://jobs.netflix.com/jobs/868295) | ||
- [Senior UI Engineer - Production Workflows Engineering](https://jobs.netflix.com/jobs/868224) | ||
## License | ||
@@ -182,0 +201,0 @@ |
@@ -1,4 +0,5 @@ | ||
import HTTPHeaders from '../utils/http-headers'; | ||
import stringify from 'fast-json-stable-stringify'; | ||
import HTTPHeaders from '../utils/http-headers'; | ||
const { freeze } = Object; | ||
@@ -5,0 +6,0 @@ const { parse } = JSON; |
import md5 from 'blueimp-md5'; | ||
import stringify from 'fast-json-stable-stringify'; | ||
import mergeOptions from 'merge-options'; | ||
import PollyResponse from './response'; | ||
import isAbsoluteUrl from 'is-absolute-url'; | ||
import { URL, assert, timestamp } from '@pollyjs/utils'; | ||
import NormalizeRequest from '../utils/normalize-request'; | ||
import parseUrl from '../utils/parse-url'; | ||
import serializeRequestBody from '../utils/serialize-request-body'; | ||
import guidForRecording from '../utils/guid-for-recording'; | ||
import mergeConfigs from '../utils/merge-configs'; | ||
import defer from '../utils/deferred-promise'; | ||
import isAbsoluteUrl from 'is-absolute-url'; | ||
import HTTPBase from './http-base'; | ||
import { URL, assert, timestamp } from '@pollyjs/utils'; | ||
import { | ||
@@ -18,8 +16,15 @@ validateRecordingName, | ||
import HTTPBase from './http-base'; | ||
import PollyResponse from './response'; | ||
import EventEmitter from './event-emitter'; | ||
const { keys, freeze } = Object; | ||
const PARSED_URL = Symbol(); | ||
const ROUTE = Symbol(); | ||
const POLLY = Symbol(); | ||
const PARSED_URL = Symbol(); | ||
const EVENT_EMITTER = Symbol(); | ||
const SUPPORTED_EVENTS = ['identify']; | ||
export default class PollyRequest extends HTTPBase { | ||
@@ -29,4 +34,7 @@ constructor(polly, request) { | ||
assert('Url is required.', typeof request.url === 'string'); | ||
assert('Method is required.', typeof request.method === 'string'); | ||
assert('Url is required.', request.url); | ||
assert( | ||
'Method is required.', | ||
request.method && typeof request.method === 'string' | ||
); | ||
@@ -43,2 +51,3 @@ this.didRespond = false; | ||
this[POLLY] = polly; | ||
this[EVENT_EMITTER] = new EventEmitter({ eventNames: SUPPORTED_EVENTS }); | ||
@@ -72,3 +81,5 @@ /* | ||
set url(value) { | ||
this[PARSED_URL] = parseUrl(value, true); | ||
// Make sure to coerce the value into a string as the passed value could be | ||
// a WHATWG's URL object. | ||
this[PARSED_URL] = parseUrl(`${value}`, true); | ||
} | ||
@@ -126,2 +137,20 @@ | ||
on(eventName, listener) { | ||
this[EVENT_EMITTER].on(eventName, listener); | ||
return this; | ||
} | ||
once(eventName, listener) { | ||
this[EVENT_EMITTER].once(eventName, listener); | ||
return this; | ||
} | ||
off(eventName, listener) { | ||
this[EVENT_EMITTER].off(eventName, listener); | ||
return this; | ||
} | ||
async setup() { | ||
@@ -135,7 +164,4 @@ // Trigger the `request` event | ||
// Serialize the body which handles FormData + Blobs/Files | ||
this.serializedBody = await this.serializeBody(); | ||
// Setup this request's identifiers, id, and order | ||
this._identify(); | ||
await this._identify(); | ||
@@ -179,6 +205,2 @@ // Timestamp the request | ||
async serializeBody() { | ||
return serializeRequestBody(this.body); | ||
} | ||
_overrideRecordingName(recordingName) { | ||
@@ -192,3 +214,3 @@ validateRecordingName(recordingName); | ||
validateRequestConfig(config); | ||
this.config = mergeOptions(this[POLLY].config, this.config || {}, config); | ||
this.config = mergeConfigs(this[POLLY].config, this.config || {}, config); | ||
} | ||
@@ -204,13 +226,14 @@ | ||
_identify() { | ||
async _identify() { | ||
const polly = this[POLLY]; | ||
const { _requests: requests } = polly; | ||
const { matchRequestsBy } = this.config; | ||
const identifiers = {}; | ||
this.identifiers = {}; | ||
// Iterate through each normalizer | ||
keys(NormalizeRequest).forEach(key => { | ||
if (this[key] && matchRequestsBy[key]) { | ||
identifiers[key] = NormalizeRequest[key]( | ||
key === 'body' ? this.serializedBody : this[key], | ||
this.identifiers[key] = NormalizeRequest[key]( | ||
this[key], | ||
matchRequestsBy[key] | ||
@@ -221,7 +244,10 @@ ); | ||
// Store the identifiers for debugging and testing | ||
this.identifiers = freeze(identifiers); | ||
// Emit the `identify` event which adapters can use to serialize the request body | ||
await this[EVENT_EMITTER].emit('identify', this); | ||
// Freeze the identifiers so they can no longer be modified | ||
freeze(this.identifiers); | ||
// Guid is a string representation of the identifiers | ||
this.id = md5(stringify(identifiers)); | ||
this.id = md5(stringify(this.identifiers)); | ||
@@ -228,0 +254,0 @@ // Order is calculated on other requests with the same id |
@@ -1,4 +0,5 @@ | ||
import HTTPBase from './http-base'; | ||
import { assert, HTTP_STATUS_CODES } from '@pollyjs/utils'; | ||
import HTTPBase from './http-base'; | ||
const DEFAULT_STATUS_CODE = 200; | ||
@@ -5,0 +6,0 @@ |
import { MODES } from '@pollyjs/utils'; | ||
import Timing from '../utils/timing'; | ||
@@ -3,0 +4,0 @@ |
@@ -1,2 +0,5 @@ | ||
import mergeOptions from 'merge-options'; | ||
import { MODES, assert } from '@pollyjs/utils'; | ||
import { version } from '../package.json'; | ||
import Logger from './-private/logger'; | ||
@@ -7,7 +10,6 @@ import Container from './-private/container'; | ||
import guidForRecording from './utils/guid-for-recording'; | ||
import mergeConfigs from './utils/merge-configs'; | ||
import EventEmitter from './-private/event-emitter'; | ||
import Server from './server'; | ||
import { validateRecordingName } from './utils/validators'; | ||
import { version } from '../package.json'; | ||
import { MODES, assert } from '@pollyjs/utils'; | ||
@@ -161,3 +163,3 @@ const RECORDING_NAME = Symbol(); | ||
this.config = mergeOptions(DefaultConfig, this.config, config); | ||
this.config = mergeConfigs(DefaultConfig, this.config, config); | ||
@@ -164,0 +166,0 @@ // Register and connect to all specified adapters |
@@ -0,3 +1,4 @@ | ||
import { assert } from '@pollyjs/utils'; | ||
import EventEmitter from '../-private/event-emitter'; | ||
import { assert } from '@pollyjs/utils'; | ||
import { | ||
@@ -4,0 +5,0 @@ validateRecordingName, |
import RouteRecognizer from 'route-recognizer'; | ||
import castArray from 'lodash-es/castArray'; | ||
import { URL, assert, timeout, buildUrl } from '@pollyjs/utils'; | ||
import Route from './route'; | ||
import Handler from './handler'; | ||
import Middleware from './middleware'; | ||
import removeHostFromUrl from '../utils/remove-host-from-url'; | ||
import castArray from 'lodash-es/castArray'; | ||
import { URL, assert, timeout, buildUrl } from '@pollyjs/utils'; | ||
@@ -13,5 +13,10 @@ const HOST = Symbol(); | ||
const MIDDLEWARE = Symbol(); | ||
const SLASH = '/'; | ||
const STAR = '*'; | ||
const HANDLERS = Symbol(); | ||
const CHARS = { | ||
SLASH: '/', | ||
STAR: '*', | ||
COLON: ':' | ||
}; | ||
const METHODS = ['GET', 'PUT', 'POST', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']; | ||
@@ -22,3 +27,3 @@ | ||
function parseUrl(url) { | ||
const path = new URL(url); | ||
const parsedUrl = new URL(url); | ||
/* | ||
@@ -28,6 +33,6 @@ Use the full origin (http://hostname:port) if the host exists. If there | ||
*/ | ||
const host = path.host ? path.origin : SLASH; | ||
const href = removeHostFromUrl(path).href; | ||
const host = parsedUrl.host ? parsedUrl.origin : CHARS.SLASH; | ||
const path = parsedUrl.pathname || CHARS.SLASH; | ||
return { host, path: href }; | ||
return { host, path }; | ||
} | ||
@@ -113,4 +118,11 @@ | ||
const registry = this._registryForHost(host); | ||
const name = this._nameForPath(path); | ||
const router = registry[method.toUpperCase()]; | ||
registry[method.toUpperCase()].add([{ path, handler }]); | ||
if (router[HANDLERS].has(name)) { | ||
router[HANDLERS].get(name).push(handler); | ||
} else { | ||
router[HANDLERS].set(name, [handler]); | ||
router.add([{ path, handler: router[HANDLERS].get(name) }]); | ||
} | ||
}); | ||
@@ -131,3 +143,3 @@ | ||
if ( | ||
(!route || route === STAR) && | ||
(!route || route === CHARS.STAR) && | ||
!this[HOST] && | ||
@@ -165,8 +177,36 @@ this[NAMESPACES].length === 0 | ||
/** | ||
* Converts a url path into a name used to combine route handlers by | ||
* normalizing dynamic and star segments | ||
* @param {String} path | ||
* @returns {String} | ||
*/ | ||
_nameForPath(path = '') { | ||
const name = path | ||
.split(CHARS.SLASH) | ||
.map(segment => { | ||
switch (segment.charAt(0)) { | ||
// If this is a dynamic segment (e.g. :id), then just return `:` | ||
// since /path/:id is the same as /path/:uuid | ||
case CHARS.COLON: | ||
return CHARS.COLON; | ||
// If this is a star segment (e.g. *path), then just return `*` | ||
// since /path/*path is the same as /path/*all | ||
case CHARS.STAR: | ||
return CHARS.STAR; | ||
default: | ||
return segment; | ||
} | ||
}) | ||
.join(CHARS.SLASH); | ||
// Remove trailing slash, if we result with an empty string, return a slash | ||
return name.replace(/\/$/, '') || CHARS.SLASH; | ||
} | ||
_registryForHost(host) { | ||
host = host || SLASH; | ||
if (!this[REGISTRY][host]) { | ||
this[REGISTRY][host] = METHODS.reduce((acc, method) => { | ||
acc[method] = new RouteRecognizer(); | ||
acc[method][HANDLERS] = new Map(); | ||
@@ -173,0 +213,0 @@ return acc; |
import RouteRecognizer from 'route-recognizer'; | ||
import Route from './route'; | ||
@@ -14,3 +15,5 @@ | ||
this.paths.forEach(path => this._routeRecognizer.add([{ path, handler }])); | ||
this.paths.forEach(path => | ||
this._routeRecognizer.add([{ path, handler: [handler] }]) | ||
); | ||
} | ||
@@ -17,0 +20,0 @@ |
@@ -1,3 +0,2 @@ | ||
import Handler from './handler'; | ||
import mergeOptions from 'merge-options'; | ||
import mergeConfigs from '../utils/merge-configs'; | ||
@@ -31,6 +30,8 @@ async function invoke(fn, route, req, ...args) { | ||
async function emit(route, eventName, ...args) { | ||
const listeners = route.handler._eventEmitter.listeners(eventName); | ||
for (const handler of route.handlers) { | ||
const listeners = handler._eventEmitter.listeners(eventName); | ||
for (const listener of listeners) { | ||
await invoke(listener, route, ...args); | ||
for (const listener of listeners) { | ||
await invoke(listener, route, ...args); | ||
} | ||
} | ||
@@ -50,11 +51,10 @@ } | ||
this.queryParams = {}; | ||
this.handlers = []; | ||
this.middleware = middleware || []; | ||
if (result) { | ||
this.handler = result.handler; | ||
this.handlers = result.handler; | ||
this.params = { ...result.params }; | ||
this.queryParams = recognizeResults.queryParams; | ||
} | ||
this.handler = this.handler || new Handler(); | ||
} | ||
@@ -75,4 +75,4 @@ | ||
config() { | ||
return mergeOptions( | ||
...this._orderedRoutes().map(r => r.handler.get('config')) | ||
return mergeConfigs( | ||
...this._orderedHandlers().map(handler => handler.get('config')) | ||
); | ||
@@ -88,5 +88,5 @@ } | ||
async intercept(req, res, interceptor) { | ||
for (const route of this._orderedRoutes()) { | ||
if (route.handler.has('intercept') && interceptor.shouldIntercept) { | ||
await invoke(route.handler.get('intercept'), this, ...arguments); | ||
for (const handler of this._orderedHandlers()) { | ||
if (handler.has('intercept') && interceptor.shouldIntercept) { | ||
await invoke(handler.get('intercept'), this, ...arguments); | ||
} | ||
@@ -110,4 +110,8 @@ } | ||
_orderedRoutes() { | ||
return [...this.middleware, this]; | ||
_orderedHandlers() { | ||
return [...this.middleware, this].reduce((handlers, route) => { | ||
handlers.push(...route.handlers); | ||
return handlers; | ||
}, []); | ||
} | ||
@@ -118,5 +122,5 @@ | ||
for (const route of this._orderedRoutes()) { | ||
if (route.handler.has(key)) { | ||
value = route.handler.get(key); | ||
for (const handler of this._orderedHandlers()) { | ||
if (handler.has(key)) { | ||
value = handler.get(key); | ||
} | ||
@@ -123,0 +127,0 @@ } |
@@ -1,6 +0,7 @@ | ||
import parseUrl from './parse-url'; | ||
import isObjectLike from 'lodash-es/isObjectLike'; | ||
import HTTPHeaders from './http-headers'; | ||
import stringify from 'fast-json-stable-stringify'; | ||
import parseUrl from './parse-url'; | ||
import HTTPHeaders from './http-headers'; | ||
const { keys } = Object; | ||
@@ -10,6 +11,10 @@ const { isArray } = Array; | ||
export function method(method) { | ||
return (method || 'GET').toUpperCase(); | ||
function isFunction(fn) { | ||
return typeof fn === 'function'; | ||
} | ||
export function method(method, config) { | ||
return isFunction(config) ? config(method) : method.toUpperCase(); | ||
} | ||
export function url(url, config = {}) { | ||
@@ -19,3 +24,9 @@ const parsedUrl = parseUrl(url, true); | ||
// Remove any url properties that have been disabled via the config | ||
keys(config).forEach(key => !config[key] && parsedUrl.set(key, '')); | ||
keys(config).forEach(key => { | ||
if (isFunction(config[key])) { | ||
parsedUrl.set(key, config[key](parsedUrl[key])); | ||
} else if (!config[key]) { | ||
parsedUrl.set(key, ''); | ||
} | ||
}); | ||
@@ -31,11 +42,10 @@ // Sort Query Params | ||
export function headers(headers, config) { | ||
let normalizedHeaders = headers; | ||
const normalizedHeaders = new HTTPHeaders(headers); | ||
if (isObjectLike(normalizedHeaders)) { | ||
normalizedHeaders = new HTTPHeaders(normalizedHeaders); | ||
if (isFunction(config)) { | ||
return config(normalizedHeaders); | ||
} | ||
// Filter out excluded headers | ||
if (isObjectLike(config) && isArray(config.exclude)) { | ||
config.exclude.forEach(header => (normalizedHeaders[header] = null)); | ||
} | ||
if (isObjectLike(config) && isArray(config.exclude)) { | ||
config.exclude.forEach(header => delete normalizedHeaders[header]); | ||
} | ||
@@ -46,4 +56,4 @@ | ||
export function body(body) { | ||
return body; | ||
export function body(body, config) { | ||
return isFunction(config) ? config(body) : body; | ||
} | ||
@@ -50,0 +60,0 @@ |
import isAbsoluteUrl from 'is-absolute-url'; | ||
import removeHostFromUrl from './remove-host-from-url'; | ||
import { URL } from '@pollyjs/utils'; | ||
import removeHostFromUrl from './remove-host-from-url'; | ||
/** | ||
@@ -6,0 +7,0 @@ * Creates an exact representation of the passed url string with url-parse. |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2242291
8
6
24222
207
12
- Removedmerge-options@^1.0.1
- Removedis-plain-obj@1.1.0(transitive)
- Removedmerge-options@1.0.1(transitive)
Updated@pollyjs/utils@^1.3.0
Updatedlodash-es@^4.17.11
Updatedslugify@^1.3.3