@podium/client
Advanced tools
Comparing version 5.0.0-next.4 to 5.0.0-next.5
@@ -0,1 +1,43 @@ | ||
# [5.0.0-next.5](https://github.com/podium-lib/client/compare/v5.0.0-next.4...v5.0.0-next.5) (2020-07-28) | ||
### Features | ||
* Use ES private properties instead of Symbols and defineProperty for privacy ([#131](https://github.com/podium-lib/client/issues/131)) ([2787a6c](https://github.com/podium-lib/client/commit/2787a6c406f9fb2bb2e85583082b818c2c247b63)) | ||
### BREAKING CHANGES | ||
* Due to dropping node 10.x support we use ES private properties instead of Symbols and `.defineProperty()`. | ||
We've been using Symbols and `.defineProperty()` to define private properties in classes up until now. This has the downside that they are not true private and in later versions of node.js one would see these Symbols when inspecting an object. What we want is proper private properties. | ||
This PR does also add a pretty printer which outputs an object literal or the object so when debugging one can see the getters and setters of the object. | ||
Example: printing a object with `console.log()` would previously print the following: | ||
```sh | ||
PodiumClientResponse { | ||
[Symbol(podium:client:response:redirect)]: '', | ||
[Symbol(podium:client:response:content)]: '', | ||
[Symbol(podium:client:response:headers)]: {}, | ||
[Symbol(podium:client:response:css)]: [], | ||
[Symbol(podium:client:response:js)]: [] | ||
} | ||
``` | ||
Now the following will be printed: | ||
```sh | ||
{ | ||
redirect: '', | ||
content: '', | ||
headers: {}, | ||
css: [], | ||
js: [] | ||
} | ||
``` | ||
Co-authored-by: Trygve Lie <trygve.lie@finn.no> | ||
# [5.0.0-next.4](https://github.com/podium-lib/client/compare/v5.0.0-next.3...v5.0.0-next.4) (2020-07-16) | ||
@@ -2,0 +44,0 @@ |
@@ -1,5 +0,1 @@ | ||
/* eslint-disable import/order */ | ||
/* eslint-disable no-underscore-dangle */ | ||
/* eslint-disable prefer-spread */ | ||
'use strict'; | ||
@@ -14,3 +10,2 @@ | ||
const https = require('https'); | ||
const util = require('util'); | ||
@@ -20,8 +15,3 @@ const Resource = require('./resource'); | ||
const _resources = Symbol('podium:client:resources'); | ||
const _registry = Symbol('podium:client:registry'); | ||
const _metrics = Symbol('podium:client:metrics'); | ||
const _histogram = Symbol('podium:client:metrics:histogram'); | ||
const _options = Symbol('podium:client:options'); | ||
const _state = Symbol('podium:client:state'); | ||
const inspect = Symbol.for('nodejs.util.inspect.custom'); | ||
@@ -52,2 +42,8 @@ const HTTP_AGENT_OPTIONS = { | ||
const PodiumClient = class PodiumClient extends EventEmitter { | ||
#resources; | ||
#registry; | ||
#metrics; | ||
#histogram; | ||
#options; | ||
#state; | ||
constructor(options = {}) { | ||
@@ -63,3 +59,3 @@ super(); | ||
this[_options] = { | ||
this.#options = { | ||
name: '', | ||
@@ -75,17 +71,17 @@ retries: RETRIES, | ||
this[_state] = new State({ | ||
this.#state = new State({ | ||
resolveThreshold: options.resolveThreshold, | ||
resolveMax: options.resolveMax, | ||
}); | ||
this[_state].on('state', state => { | ||
this.#state.on('state', state => { | ||
this.emit('state', state); | ||
}); | ||
this[_resources] = new Map(); | ||
this.#resources = new Map(); | ||
this[_registry] = new Cache({ | ||
this.#registry = new Cache({ | ||
changefeed: true, | ||
ttl: options.maxAge, | ||
}); | ||
this[_registry].on('error', error => { | ||
this.#registry.on('error', error => { | ||
log.error( | ||
@@ -97,8 +93,8 @@ 'Error emitted by the registry in @podium/client module', | ||
this[_registry].on('set', () => { | ||
this[_state].setUnstableState(); | ||
this.#registry.on('set', () => { | ||
this.#state.setUnstableState(); | ||
}); | ||
this[_metrics] = new Metrics(); | ||
this[_metrics].on('error', error => { | ||
this.#metrics = new Metrics(); | ||
this.#metrics.on('error', error => { | ||
log.error( | ||
@@ -111,3 +107,3 @@ 'Error emitted by metric stream in @podium/client module', | ||
this[Symbol.iterator] = () => ({ | ||
items: Array.from(this[_resources]).map(item => item[1]), | ||
items: Array.from(this.#resources).map(item => item[1]), | ||
next: function next() { | ||
@@ -121,6 +117,6 @@ return { | ||
this[_histogram] = this[_metrics].histogram({ | ||
this.#histogram = this.#metrics.histogram({ | ||
name: 'podium_client_refresh_manifests', | ||
description: 'Time taken for podium client to refresh manifests', | ||
labels: { name: this[_options].name }, | ||
labels: { name: this.#options.name }, | ||
buckets: [0.001, 0.01, 0.1, 0.5, 1, 2, 10], | ||
@@ -131,11 +127,11 @@ }); | ||
get registry() { | ||
return this[_registry]; | ||
return this.#registry; | ||
} | ||
get metrics() { | ||
return this[_metrics]; | ||
return this.#metrics; | ||
} | ||
get state() { | ||
return this[_state].status; | ||
return this.#state.status; | ||
} | ||
@@ -154,3 +150,3 @@ | ||
if (this[_resources].has(options.name)) { | ||
if (this.#resources.has(options.name)) { | ||
throw new Error( | ||
@@ -162,22 +158,22 @@ `Resource with the name "${options.name}" has already been registered.`, | ||
const resourceOptions = { | ||
clientName: this[_options].name, | ||
retries: this[_options].retries, | ||
timeout: this[_options].timeout, | ||
logger: this[_options].logger, | ||
maxAge: this[_options].maxAge, | ||
clientName: this.#options.name, | ||
retries: this.#options.retries, | ||
timeout: this.#options.timeout, | ||
logger: this.#options.logger, | ||
maxAge: this.#options.maxAge, | ||
agent: options.uri.startsWith('https://') | ||
? this[_options].httpsAgent | ||
: this[_options].httpAgent, | ||
? this.#options.httpsAgent | ||
: this.#options.httpAgent, | ||
...options, | ||
}; | ||
const resource = new Resource( | ||
this[_registry], | ||
this[_state], | ||
this.#registry, | ||
this.#state, | ||
resourceOptions, | ||
); | ||
resource.metrics.pipe(this[_metrics]); | ||
resource.metrics.pipe(this.#metrics); | ||
Object.defineProperty(this, options.name, { | ||
get: () => this[_resources].get(options.name), | ||
get: () => this.#resources.get(options.name), | ||
set: () => { | ||
@@ -188,3 +184,3 @@ throw new Error('Cannot set read-only property.'); | ||
this[_resources].set(options.name, resource); | ||
this.#resources.set(options.name, resource); | ||
return resource; | ||
@@ -194,15 +190,15 @@ } | ||
dump() { | ||
return this[_registry].dump(); | ||
return this.#registry.dump(); | ||
} | ||
load(dump) { | ||
return this[_registry].load(dump); | ||
return this.#registry.load(dump); | ||
} | ||
async refreshManifests() { | ||
const end = this[_histogram].timer(); | ||
const end = this.#histogram.timer(); | ||
// Don't return this | ||
await Promise.all( | ||
Array.from(this[_resources]).map(resource => resource[1].refresh()), | ||
Array.from(this.#resources).map(resource => resource[1].refresh()), | ||
); | ||
@@ -213,17 +209,13 @@ | ||
[inspect]() { | ||
return { | ||
metrics: this.metrics, | ||
state: this.state, | ||
}; | ||
} | ||
get [Symbol.toStringTag]() { | ||
return 'PodiumClient'; | ||
} | ||
[util.inspect.custom](depth, options) { | ||
return util.inspect( | ||
{ | ||
metrics: this.metrics, | ||
state: this.state, | ||
}, | ||
depth, | ||
options, | ||
); | ||
} | ||
}; | ||
module.exports = PodiumClient; |
@@ -8,19 +8,18 @@ /* eslint-disable no-underscore-dangle */ | ||
const _killRecursions = Symbol('podium:httpoutgoing:killrecursions'); | ||
const _killThreshold = Symbol('podium:httpoutgoing:killthreshold'); | ||
const _redirectable = Symbol('podium:httpoutgoing:redirectable'); | ||
const _reqOptions = Symbol('podium:httpoutgoing:reqoptions'); | ||
const _throwable = Symbol('podium:httpoutgoing:throwable'); | ||
const _manifest = Symbol('podium:httpoutgoing:manifest'); | ||
const _incoming = Symbol('podium:httpoutgoing:incoming'); | ||
const _redirect = Symbol('podium:httpoutgoing:redirect'); | ||
const _timeout = Symbol('podium:httpoutgoing:timeout'); | ||
const _success = Symbol('podium:httpoutgoing:success'); | ||
const _headers = Symbol('podium:httpoutgoing:headers'); | ||
const _maxAge = Symbol('podium:httpoutgoing:maxage'); | ||
const _status = Symbol('podium:httpoutgoing:status'); | ||
const _name = Symbol('podium:httpoutgoing:name'); | ||
const _uri = Symbol('podium:httpoutgoing:uri'); | ||
const PodletClientHttpOutgoing = class PodletClientHttpOutgoing extends PassThrough { | ||
#killRecursions; | ||
#killThreshold; | ||
#redirectable; | ||
#reqOptions; | ||
#throwable; | ||
#manifest; | ||
#incoming; | ||
#redirect; | ||
#timeout; | ||
#success; | ||
#headers; | ||
#maxAge; | ||
#status; | ||
#name; | ||
#uri; | ||
constructor( | ||
@@ -47,11 +46,11 @@ { | ||
// A HttpIncoming object | ||
this[_incoming] = incoming; | ||
this.#incoming = incoming; | ||
// Kill switch for breaking the recursive promise chain | ||
// in case it is never able to completely resolve | ||
this[_killRecursions] = 0; | ||
this[_killThreshold] = retries; | ||
this.#killRecursions = 0; | ||
this.#killThreshold = retries; | ||
// Options to be appended to the content request | ||
this[_reqOptions] = { | ||
this.#reqOptions = { | ||
pathname: '', | ||
@@ -64,7 +63,7 @@ query: {}, | ||
// In the case of failure, should the resource throw or not | ||
this[_throwable] = throwable; | ||
this.#throwable = throwable; | ||
// Manifest which is either retrieved from the registry or | ||
// remote podlet (in other words its not saved in registry yet) | ||
this[_manifest] = { | ||
this.#manifest = { | ||
_fallback: '', | ||
@@ -74,10 +73,12 @@ }; | ||
// How long before a request should time out | ||
this[_timeout] = timeout; | ||
this.#timeout = timeout; | ||
// Done indicator to break the promise chain | ||
// Set to true when content is served | ||
this[_success] = false; | ||
this.#success = false; | ||
this.#headers = {}; | ||
// How long the manifest should be cached before refetched | ||
this[_maxAge] = maxAge; | ||
this.#maxAge = maxAge; | ||
@@ -92,135 +93,135 @@ // What status the manifest is in. This is used to tell what actions need to | ||
// "stale" - the manifest is outdated, a new manifest needs to be fetched | ||
this[_status] = 'empty'; | ||
this.#status = 'empty'; | ||
// Name of the resource (name given to client) | ||
this[_name] = name; | ||
this.#name = name; | ||
// URI to manifest | ||
this[_uri] = uri; | ||
this.#uri = uri; | ||
// Whether the user can handle redirects manually. | ||
this[_redirectable] = redirectable; | ||
this.#redirectable = redirectable; | ||
// When redirectable is true, this object should be populated with redirect information | ||
// such that a user can perform manual redirection | ||
this[_redirect] = null; | ||
this.#redirect = null; | ||
} | ||
get [Symbol.toStringTag]() { | ||
return 'PodletClientHttpOutgoing'; | ||
} | ||
get reqOptions() { | ||
return this[_reqOptions]; | ||
return this.#reqOptions; | ||
} | ||
get throwable() { | ||
return this[_throwable]; | ||
return this.#throwable; | ||
} | ||
get manifest() { | ||
return this[_manifest]; | ||
return this.#manifest; | ||
} | ||
set manifest(obj) { | ||
this[_manifest] = obj; | ||
this.#manifest = obj; | ||
} | ||
get fallback() { | ||
return this[_manifest]._fallback; | ||
return this.#manifest._fallback; | ||
} | ||
set fallback(value) { | ||
this[_manifest]._fallback = value; | ||
this.#manifest._fallback = value; | ||
} | ||
get timeout() { | ||
return this[_timeout]; | ||
return this.#timeout; | ||
} | ||
get success() { | ||
return this[_success]; | ||
return this.#success; | ||
} | ||
set success(value) { | ||
this[_success] = value; | ||
this.#success = value; | ||
} | ||
get context() { | ||
return this[_incoming].context; | ||
return this.#incoming.context; | ||
} | ||
get headers() { | ||
return this[_headers]; | ||
return this.#headers; | ||
} | ||
set headers(value) { | ||
this[_headers] = value; | ||
this.#headers = value; | ||
} | ||
get maxAge() { | ||
return this[_maxAge]; | ||
return this.#maxAge; | ||
} | ||
set maxAge(value) { | ||
this[_maxAge] = value; | ||
this.#maxAge = value; | ||
} | ||
get status() { | ||
return this[_status]; | ||
return this.#status; | ||
} | ||
set status(value) { | ||
this[_status] = value; | ||
this.#status = value; | ||
} | ||
get name() { | ||
return this[_name]; | ||
return this.#name; | ||
} | ||
get manifestUri() { | ||
return this[_uri]; | ||
return this.#uri; | ||
} | ||
get fallbackUri() { | ||
return this[_manifest].fallback; | ||
return this.#manifest.fallback; | ||
} | ||
get contentUri() { | ||
return this[_manifest].content; | ||
return this.#manifest.content; | ||
} | ||
get kill() { | ||
return this[_killRecursions] === this[_killThreshold]; | ||
return this.#killRecursions === this.#killThreshold; | ||
} | ||
get recursions() { | ||
return this[_killRecursions]; | ||
return this.#killRecursions; | ||
} | ||
set recursions(value) { | ||
this[_killRecursions] = value; | ||
this.#killRecursions = value; | ||
} | ||
get redirect() { | ||
return this[_redirect]; | ||
return this.#redirect; | ||
} | ||
set redirect(value) { | ||
this[_redirect] = value; | ||
this.#redirect = value; | ||
} | ||
get redirectable() { | ||
return this[_redirectable]; | ||
return this.#redirectable; | ||
} | ||
set redirectable(value) { | ||
this[_redirectable] = value; | ||
this.#redirectable = value; | ||
} | ||
pushFallback() { | ||
this.push(this[_manifest]._fallback); | ||
this.push(this.#manifest._fallback); | ||
this.push(null); | ||
} | ||
get [Symbol.toStringTag]() { | ||
return 'PodletClientHttpOutgoing'; | ||
} | ||
}; | ||
module.exports = PodletClientHttpOutgoing; |
@@ -11,2 +11,4 @@ /* eslint-disable no-plusplus */ | ||
module.exports = class PodletClientCacheResolver { | ||
#registry; | ||
#log; | ||
constructor(registry, options = {}) { | ||
@@ -17,24 +19,14 @@ assert( | ||
); | ||
Object.defineProperty(this, 'registry', { | ||
value: registry, | ||
}); | ||
Object.defineProperty(this, 'log', { | ||
value: abslog(options.logger), | ||
}); | ||
this.#registry = registry; | ||
this.#log = abslog(options.logger); | ||
} | ||
get [Symbol.toStringTag]() { | ||
return 'PodletClientCacheResolver'; | ||
} | ||
load(outgoing) { | ||
return new Promise(resolve => { | ||
if (outgoing.status !== 'stale') { | ||
const cached = this.registry.get(outgoing.name); | ||
const cached = this.#registry.get(outgoing.name); | ||
if (cached) { | ||
outgoing.manifest = clonedeep(cached); | ||
outgoing.status = 'cached'; | ||
this.log.debug( | ||
this.#log.debug( | ||
`loaded manifest from cache - resource: ${outgoing.name}`, | ||
@@ -51,3 +43,3 @@ ); | ||
if (outgoing.status === 'fresh') { | ||
this.registry.set( | ||
this.#registry.set( | ||
outgoing.name, | ||
@@ -57,3 +49,3 @@ outgoing.manifest, | ||
); | ||
this.log.debug( | ||
this.#log.debug( | ||
`saved manifest to cache - resource: ${outgoing.name}`, | ||
@@ -68,2 +60,6 @@ ); | ||
} | ||
get [Symbol.toStringTag]() { | ||
return 'PodletClientCacheResolver'; | ||
} | ||
}; |
@@ -18,33 +18,16 @@ /* eslint-disable no-param-reassign */ | ||
module.exports = class PodletClientContentResolver { | ||
#log; | ||
#agent; | ||
#metrics; | ||
#histogram; | ||
constructor(options = {}) { | ||
Object.defineProperty(this, 'log', { | ||
value: abslog(options.logger), | ||
}); | ||
Object.defineProperty(this, 'clientName', { | ||
enumerable: false, | ||
value: options.clientName, | ||
}); | ||
Object.defineProperty(this, 'metrics', { | ||
enumerable: true, | ||
value: new Metrics(), | ||
}); | ||
Object.defineProperty(this, 'agent', { | ||
value: options.agent, | ||
}); | ||
this.metrics.on('error', error => { | ||
this.log.error( | ||
'Error emitted by metric stream in @podium/client module', | ||
error, | ||
); | ||
}); | ||
this.histogram = this.metrics.histogram({ | ||
const name = options.clientName; | ||
this.#log = abslog(options.logger); | ||
this.#agent = options.agent; | ||
this.#metrics = new Metrics(); | ||
this.#histogram = this.#metrics.histogram({ | ||
name: 'podium_client_resolver_content_resolve', | ||
description: 'Time taken for success/failure of content request', | ||
labels: { | ||
name: this.clientName, | ||
name, | ||
status: null, | ||
@@ -55,6 +38,13 @@ podlet: null, | ||
}); | ||
this.#metrics.on('error', error => { | ||
this.#log.error( | ||
'Error emitted by metric stream in @podium/client module', | ||
error, | ||
); | ||
}); | ||
} | ||
get [Symbol.toStringTag]() { | ||
return 'PodletClientContentResolver'; | ||
get metrics() { | ||
return this.#metrics; | ||
} | ||
@@ -65,3 +55,3 @@ | ||
if (outgoing.kill && outgoing.throwable) { | ||
this.log.warn( | ||
this.#log.warn( | ||
`recursion detected - failed to resolve fetching of podlet ${outgoing.recursions} times - throwing - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
@@ -78,3 +68,3 @@ ); | ||
if (outgoing.kill) { | ||
this.log.warn( | ||
this.#log.warn( | ||
`recursion detected - failed to resolve fetching of podlet ${outgoing.recursions} times - serving fallback - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
@@ -89,3 +79,3 @@ ); | ||
if (outgoing.status === 'empty' && outgoing.throwable) { | ||
this.log.warn( | ||
this.#log.warn( | ||
`no manifest available - cannot read content - throwing - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
@@ -100,3 +90,3 @@ ); | ||
if (outgoing.status === 'empty') { | ||
this.log.warn( | ||
this.#log.warn( | ||
`no manifest available - cannot read content - serving fallback - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
@@ -120,3 +110,3 @@ ); | ||
method: 'GET', | ||
agent: this.agent, | ||
agent: this.#agent, | ||
uri: putils.uriBuilder( | ||
@@ -134,3 +124,3 @@ outgoing.reqOptions.pathname, | ||
const timer = this.histogram.timer({ | ||
const timer = this.#histogram.timer({ | ||
labels: { | ||
@@ -141,3 +131,3 @@ podlet: outgoing.name, | ||
this.log.debug( | ||
this.#log.debug( | ||
`start reading content from remote resource - manifest version is ${outgoing.manifest.version} - resource: ${outgoing.name} - url: ${outgoing.contentUri}`, | ||
@@ -158,3 +148,3 @@ ); | ||
this.log.warn( | ||
this.#log.warn( | ||
`remote resource responded with non 200 http status code for content - code: ${response.statusCode} - resource: ${outgoing.name} - url: ${outgoing.contentUri}`, | ||
@@ -182,3 +172,3 @@ ); | ||
this.log.warn( | ||
this.#log.warn( | ||
`remote resource responded with non 200 http status code for content - code: ${response.statusCode} - resource: ${outgoing.name} - url: ${outgoing.contentUri}`, | ||
@@ -199,3 +189,3 @@ ); | ||
this.log.debug( | ||
this.#log.debug( | ||
`got head response from remote resource for content - header version is ${response.headers['podlet-version']} - resource: ${outgoing.name} - url: ${outgoing.contentUri}`, | ||
@@ -214,3 +204,3 @@ ); | ||
this.log.info( | ||
this.#log.info( | ||
`podlet version number in header differs from cached version number - aborting request to remote resource for content - resource: ${outgoing.name} - url: ${outgoing.contentUri}`, | ||
@@ -245,3 +235,3 @@ ); | ||
if (err) { | ||
this.log.warn('error while piping content stream', err); | ||
this.#log.warn('error while piping content stream', err); | ||
} | ||
@@ -260,3 +250,3 @@ }); | ||
this.log.warn( | ||
this.#log.warn( | ||
`could not create network connection to remote resource when trying to request content - resource: ${outgoing.name} - url: ${outgoing.contentUri}`, | ||
@@ -279,3 +269,3 @@ ); | ||
this.log.warn( | ||
this.#log.warn( | ||
`could not create network connection to remote resource when trying to request content - resource: ${outgoing.name} - url: ${outgoing.contentUri}`, | ||
@@ -297,3 +287,3 @@ ); | ||
this.log.debug( | ||
this.#log.debug( | ||
`successfully read content from remote resource - resource: ${outgoing.name} - url: ${outgoing.contentUri}`, | ||
@@ -306,2 +296,6 @@ ); | ||
} | ||
get [Symbol.toStringTag]() { | ||
return 'PodletClientContentResolver'; | ||
} | ||
}; |
@@ -13,33 +13,16 @@ /* eslint-disable no-param-reassign */ | ||
module.exports = class PodletClientFallbackResolver { | ||
#log; | ||
#agent; | ||
#metrics; | ||
#histogram; | ||
constructor(options = {}) { | ||
Object.defineProperty(this, 'log', { | ||
value: abslog(options.logger), | ||
}); | ||
Object.defineProperty(this, 'clientName', { | ||
enumerable: false, | ||
value: options.clientName, | ||
}); | ||
Object.defineProperty(this, 'metrics', { | ||
enumerable: true, | ||
value: new Metrics(), | ||
}); | ||
Object.defineProperty(this, 'agent', { | ||
value: options.agent, | ||
}); | ||
this.metrics.on('error', error => { | ||
this.log.error( | ||
'Error emitted by metric stream in @podium/client module', | ||
error, | ||
); | ||
}); | ||
this.histogram = this.metrics.histogram({ | ||
const name = options.clientName; | ||
this.#log = abslog(options.logger); | ||
this.#agent = options.agent; | ||
this.#metrics = new Metrics(); | ||
this.#histogram = this.#metrics.histogram({ | ||
name: 'podium_client_resolver_fallback_resolve', | ||
description: 'Time taken for success/failure of fallback request', | ||
labels: { | ||
name: this.clientName, | ||
name, | ||
status: null, | ||
@@ -50,6 +33,13 @@ podlet: null, | ||
}); | ||
this.#metrics.on('error', error => { | ||
this.#log.error( | ||
'Error emitted by metric stream in @podium/client module', | ||
error, | ||
); | ||
}); | ||
} | ||
get [Symbol.toStringTag]() { | ||
return 'PodletClientFallbackResolver'; | ||
get metrics() { | ||
return this.#metrics; | ||
} | ||
@@ -75,3 +65,3 @@ | ||
if (outgoing.manifest.fallback === '') { | ||
this.log.debug( | ||
this.#log.debug( | ||
`no fallback defined in manifest - resource: ${outgoing.name}`, | ||
@@ -92,3 +82,3 @@ ); | ||
method: 'GET', | ||
agent: this.agent, | ||
agent: this.#agent, | ||
uri: outgoing.fallbackUri, | ||
@@ -98,3 +88,3 @@ headers, | ||
const timer = this.histogram.timer({ | ||
const timer = this.#histogram.timer({ | ||
labels: { | ||
@@ -106,3 +96,3 @@ podlet: outgoing.name, | ||
request(reqOptions, (error, res, body) => { | ||
this.log.debug( | ||
this.#log.debug( | ||
`start reading fallback content from remote resource - resource: ${outgoing.name} - url: ${outgoing.fallbackUri}`, | ||
@@ -119,3 +109,3 @@ ); | ||
this.log.warn( | ||
this.#log.warn( | ||
`could not create network connection to remote resource for fallback content - resource: ${outgoing.name} - url: ${outgoing.fallbackUri}`, | ||
@@ -138,3 +128,3 @@ ); | ||
this.log.warn( | ||
this.#log.warn( | ||
`remote resource responded with non 200 http status code for fallback content - code: ${res.statusCode} - resource: ${outgoing.name} - url: ${outgoing.fallbackUri}`, | ||
@@ -158,3 +148,3 @@ ); | ||
this.log.debug( | ||
this.#log.debug( | ||
`successfully read fallback from remote resource - resource: ${outgoing.name} - url: ${outgoing.fallbackUri}`, | ||
@@ -166,2 +156,6 @@ ); | ||
} | ||
get [Symbol.toStringTag]() { | ||
return 'PodletClientFallbackResolver'; | ||
} | ||
}; |
@@ -12,2 +12,7 @@ 'use strict'; | ||
module.exports = class PodletClientResolver { | ||
#cache; | ||
#manifest; | ||
#fallback; | ||
#content; | ||
#metrics; | ||
constructor(registry, options = {}) { | ||
@@ -19,29 +24,11 @@ assert( | ||
Object.defineProperty(this, 'log', { | ||
value: abslog(options.logger), | ||
}); | ||
const log = abslog(options.logger); | ||
this.#cache = new Cache(registry, options); | ||
this.#manifest = new Manifest(options); | ||
this.#fallback = new Fallback(options); | ||
this.#content = new Content(options); | ||
this.#metrics = new Metrics(); | ||
Object.defineProperty(this, 'cache', { | ||
value: new Cache(registry, options), | ||
}); | ||
Object.defineProperty(this, 'manifest', { | ||
value: new Manifest(options), | ||
}); | ||
Object.defineProperty(this, 'fallback', { | ||
value: new Fallback(options), | ||
}); | ||
Object.defineProperty(this, 'content', { | ||
value: new Content(options), | ||
}); | ||
Object.defineProperty(this, 'metrics', { | ||
enumerable: true, | ||
value: new Metrics(), | ||
}); | ||
this.metrics.on('error', error => { | ||
this.log.error( | ||
this.#metrics.on('error', error => { | ||
log.error( | ||
'Error emitted by metric stream in @podium/client module', | ||
@@ -52,18 +39,18 @@ error, | ||
this.content.metrics.pipe(this.metrics); | ||
this.fallback.metrics.pipe(this.metrics); | ||
this.manifest.metrics.pipe(this.metrics); | ||
this.#content.metrics.pipe(this.#metrics); | ||
this.#fallback.metrics.pipe(this.#metrics); | ||
this.#manifest.metrics.pipe(this.#metrics); | ||
} | ||
get [Symbol.toStringTag]() { | ||
return 'PodletClientResolver'; | ||
get metrics() { | ||
return this.#metrics; | ||
} | ||
resolve(outgoing) { | ||
return this.cache | ||
return this.#cache | ||
.load(outgoing) | ||
.then(obj => this.manifest.resolve(obj)) | ||
.then(obj => this.fallback.resolve(obj)) | ||
.then(obj => this.content.resolve(obj)) | ||
.then(obj => this.cache.save(obj)) | ||
.then(obj => this.#manifest.resolve(obj)) | ||
.then(obj => this.#fallback.resolve(obj)) | ||
.then(obj => this.#content.resolve(obj)) | ||
.then(obj => this.#cache.save(obj)) | ||
.then(obj => { | ||
@@ -78,8 +65,12 @@ if (obj.success) { | ||
refresh(outgoing) { | ||
return this.manifest | ||
return this.#manifest | ||
.resolve(outgoing) | ||
.then(obj => this.fallback.resolve(obj)) | ||
.then(obj => this.cache.save(obj)) | ||
.then(obj => this.#fallback.resolve(obj)) | ||
.then(obj => this.#cache.save(obj)) | ||
.then(obj => !!obj.manifest.name); | ||
} | ||
get [Symbol.toStringTag]() { | ||
return 'PodletClientResolver'; | ||
} | ||
}; |
@@ -16,33 +16,16 @@ /* eslint-disable no-param-reassign */ | ||
module.exports = class PodletClientManifestResolver { | ||
#log; | ||
#agent; | ||
#metrics; | ||
#histogram; | ||
constructor(options = {}) { | ||
Object.defineProperty(this, 'log', { | ||
value: abslog(options.logger), | ||
}); | ||
Object.defineProperty(this, 'clientName', { | ||
enumerable: false, | ||
value: options.clientName, | ||
}); | ||
Object.defineProperty(this, 'metrics', { | ||
enumerable: true, | ||
value: new Metrics(), | ||
}); | ||
Object.defineProperty(this, 'agent', { | ||
value: options.agent, | ||
}); | ||
this.metrics.on('error', error => { | ||
this.log.error( | ||
'Error emitted by metric stream in @podium/client module', | ||
error, | ||
); | ||
}); | ||
this.histogram = this.metrics.histogram({ | ||
const name = options.clientName; | ||
this.#log = abslog(options.logger); | ||
this.#agent = options.agent; | ||
this.#metrics = new Metrics(); | ||
this.#histogram = this.#metrics.histogram({ | ||
name: 'podium_client_resolver_manifest_resolve', | ||
description: 'Time taken for success/failure of manifest request', | ||
labels: { | ||
name: this.clientName, | ||
name, | ||
status: null, | ||
@@ -53,6 +36,13 @@ podlet: null, | ||
}); | ||
this.#metrics.on('error', error => { | ||
this.#log.error( | ||
'Error emitted by metric stream in @podium/client module', | ||
error, | ||
); | ||
}); | ||
} | ||
get [Symbol.toStringTag]() { | ||
return 'PodletClientManifestResolver'; | ||
get metrics() { | ||
return this.#metrics; | ||
} | ||
@@ -74,3 +64,3 @@ | ||
method: 'GET', | ||
agent: this.agent, | ||
agent: this.#agent, | ||
json: true, | ||
@@ -81,3 +71,3 @@ uri: outgoing.manifestUri, | ||
const timer = this.histogram.timer({ | ||
const timer = this.#histogram.timer({ | ||
labels: { | ||
@@ -89,3 +79,3 @@ podlet: outgoing.name, | ||
request(reqOptions, (error, res, body) => { | ||
this.log.debug( | ||
this.#log.debug( | ||
`start reading manifest from remote resource - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
@@ -102,3 +92,3 @@ ); | ||
this.log.warn( | ||
this.#log.warn( | ||
`could not create network connection to remote manifest - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
@@ -119,3 +109,3 @@ ); | ||
this.log.warn( | ||
this.#log.warn( | ||
`remote resource responded with non 200 http status code for manifest - code: ${res.statusCode} - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
@@ -137,3 +127,3 @@ ); | ||
this.log.warn( | ||
this.#log.warn( | ||
`could not parse manifest - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
@@ -162,3 +152,3 @@ ); | ||
if (maxAge !== 0) { | ||
this.log.debug( | ||
this.#log.debug( | ||
`remote resource has cache header which yelds a max age of ${maxAge}ms, using this as cache ttl - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
@@ -210,3 +200,3 @@ ); | ||
this.log.debug( | ||
this.#log.debug( | ||
`successfully read manifest from remote resource - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`, | ||
@@ -218,2 +208,6 @@ ); | ||
} | ||
get [Symbol.toStringTag]() { | ||
return 'PodletClientManifestResolver'; | ||
} | ||
}; |
@@ -1,2 +0,1 @@ | ||
/* eslint-disable no-underscore-dangle */ | ||
/* eslint-disable no-param-reassign */ | ||
@@ -10,3 +9,2 @@ | ||
const assert = require('assert'); | ||
const util = require('util'); | ||
@@ -18,8 +16,9 @@ const HttpOutgoing = require('./http-outgoing'); | ||
const _resolver = Symbol('podium:client:resource:resolver'); | ||
const _options = Symbol('podium:client:resource:options'); | ||
const _metrics = Symbol('podium:client:resource:metrics'); | ||
const _state = Symbol('podium:client:resource:state'); | ||
const inspect = Symbol.for('nodejs.util.inspect.custom'); | ||
const PodiumClientResource = class PodiumClientResource { | ||
#resolver; | ||
#options; | ||
#metrics; | ||
#state; | ||
constructor(registry, state, options = {}) { | ||
@@ -38,8 +37,8 @@ assert( | ||
this[_resolver] = new Resolver(registry, options); | ||
this[_options] = options; | ||
this[_metrics] = new Metrics(); | ||
this[_state] = state; | ||
this.#resolver = new Resolver(registry, options); | ||
this.#options = options; | ||
this.#metrics = new Metrics(); | ||
this.#state = state; | ||
this[_metrics].on('error', error => { | ||
this.#metrics.on('error', error => { | ||
log.error( | ||
@@ -51,15 +50,15 @@ 'Error emitted by metric stream in @podium/client module', | ||
this[_resolver].metrics.pipe(this[_metrics]); | ||
this.#resolver.metrics.pipe(this.#metrics); | ||
} | ||
get metrics() { | ||
return this[_metrics]; | ||
return this.#metrics; | ||
} | ||
get name() { | ||
return this[_options].name; | ||
return this.#options.name; | ||
} | ||
get uri() { | ||
return this[_options].uri; | ||
return this.#options.uri; | ||
} | ||
@@ -69,5 +68,5 @@ | ||
if (!utils.validateIncoming(incoming)) throw new TypeError('you must pass an instance of "HttpIncoming" as the first argument to the .fetch() method'); | ||
const outgoing = new HttpOutgoing(this[_options], reqOptions, incoming); | ||
const outgoing = new HttpOutgoing(this.#options, reqOptions, incoming); | ||
this[_state].setInitializingState(); | ||
this.#state.setInitializingState(); | ||
@@ -84,3 +83,3 @@ const chunks = []; | ||
const { manifest, headers, redirect } = await this[_resolver].resolve( | ||
const { manifest, headers, redirect } = await this.#resolver.resolve( | ||
outgoing, | ||
@@ -104,5 +103,5 @@ ); | ||
if (!utils.validateIncoming(incoming)) throw new TypeError('you must pass an instance of "HttpIncoming" as the first argument to the .stream() method'); | ||
const outgoing = new HttpOutgoing(this[_options], reqOptions, incoming); | ||
this[_state].setInitializingState(); | ||
this[_resolver].resolve(outgoing); | ||
const outgoing = new HttpOutgoing(this.#options, reqOptions, incoming); | ||
this.#state.setInitializingState(); | ||
this.#resolver.resolve(outgoing); | ||
return outgoing; | ||
@@ -112,23 +111,19 @@ } | ||
refresh(incoming = {}, reqOptions = {}) { | ||
const outgoing = new HttpOutgoing(this[_options], reqOptions, incoming); | ||
this[_state].setInitializingState(); | ||
return this[_resolver].refresh(outgoing).then(obj => obj); | ||
const outgoing = new HttpOutgoing(this.#options, reqOptions, incoming); | ||
this.#state.setInitializingState(); | ||
return this.#resolver.refresh(outgoing).then(obj => obj); | ||
} | ||
[inspect]() { | ||
return { | ||
metrics: this.metrics, | ||
name: this.name, | ||
uri: this.uri, | ||
}; | ||
} | ||
get [Symbol.toStringTag]() { | ||
return 'PodiumClientResource'; | ||
} | ||
[util.inspect.custom](depth, options) { | ||
return util.inspect( | ||
{ | ||
metrics: this.metrics, | ||
name: this.name, | ||
uri: this.uri, | ||
}, | ||
depth, | ||
options, | ||
); | ||
} | ||
}; | ||
module.exports = PodiumClientResource; |
@@ -1,15 +0,11 @@ | ||
/* eslint-disable no-underscore-dangle */ | ||
/* eslint-disable import/order */ | ||
'use strict'; | ||
const util = require('util'); | ||
const inspect = Symbol.for('nodejs.util.inspect.custom'); | ||
const _redirect = Symbol('podium:client:response:redirect'); | ||
const _content = Symbol('podium:client:response:content'); | ||
const _headers = Symbol('podium:client:response:headers'); | ||
const _css = Symbol('podium:client:response:css'); | ||
const _js = Symbol('podium:client:response:js'); | ||
const PodiumClientResponse = class PodiumClientResponse { | ||
#redirect; | ||
#content; | ||
#headers; | ||
#css; | ||
#js; | ||
constructor({ | ||
@@ -22,40 +18,36 @@ content = '', | ||
} = {}) { | ||
this[_redirect] = redirect; | ||
this[_content] = content; | ||
this[_headers] = headers; | ||
this[_css] = css; | ||
this[_js] = js; | ||
this.#redirect = redirect; | ||
this.#content = content; | ||
this.#headers = headers; | ||
this.#css = css; | ||
this.#js = js; | ||
} | ||
get content() { | ||
return this[_content]; | ||
return this.#content; | ||
} | ||
get headers() { | ||
return this[_headers]; | ||
return this.#headers; | ||
} | ||
get css() { | ||
return this[_css]; | ||
return this.#css; | ||
} | ||
get js() { | ||
return this[_js]; | ||
return this.#js; | ||
} | ||
get redirect() { | ||
return this[_redirect]; | ||
return this.#redirect; | ||
} | ||
get [Symbol.toStringTag]() { | ||
return 'PodiumClientResponse'; | ||
} | ||
toJSON() { | ||
return { | ||
redirect: this[_redirect], | ||
content: this[_content], | ||
headers: this[_headers], | ||
css: this[_css], | ||
js: this[_js], | ||
redirect: this.redirect, | ||
content: this.content, | ||
headers: this.headers, | ||
css: this.css, | ||
js: this.js, | ||
}; | ||
@@ -65,13 +57,27 @@ } | ||
toString() { | ||
return this[_content]; | ||
return this.content; | ||
} | ||
[Symbol.toPrimitive]() { | ||
return this[_content]; | ||
return this.content; | ||
} | ||
[util.inspect.custom](depth, options) { | ||
return util.inspect(this.toJSON(), depth, options); | ||
[inspect]() { | ||
return { | ||
referrerpolicy: this.referrerpolicy, | ||
crossorigin: this.crossorigin, | ||
integrity: this.integrity, | ||
nomodule: this.nomodule, | ||
value: this.value, | ||
async: this.async, | ||
defer: this.defer, | ||
type: this.type, | ||
data: this.data, | ||
}; | ||
} | ||
get [Symbol.toStringTag]() { | ||
return 'PodiumClientResponse'; | ||
} | ||
}; | ||
module.exports = PodiumClientResponse; |
@@ -1,16 +0,13 @@ | ||
/* eslint-disable no-underscore-dangle */ | ||
/* eslint-disable import/order */ | ||
'use strict'; | ||
const EventEmitter = require('events'); | ||
const util = require('util'); | ||
const _thresholdTimer = Symbol('podium:client:state:threshold:timer'); | ||
const _threshold = Symbol('podium:client:state:threshold'); | ||
const _maxTimer = Symbol('podium:client:state:max:timer'); | ||
const _state = Symbol('podium:client:state:state'); | ||
const _max = Symbol('podium:client:state:max'); | ||
const inspect = Symbol.for('nodejs.util.inspect.custom'); | ||
const PodiumClientState = class PodiumClientState extends EventEmitter { | ||
#thresholdTimer; | ||
#threshold; | ||
#maxTimer; | ||
#state; | ||
#max; | ||
constructor({ | ||
@@ -26,11 +23,11 @@ resolveThreshold = 10 * 1000, | ||
this[_thresholdTimer] = undefined; | ||
this[_threshold] = resolveThreshold; | ||
this[_maxTimer] = undefined; | ||
this[_state] = 'instantiated'; | ||
this[_max] = resolveMax; | ||
this.#thresholdTimer = undefined; | ||
this.#threshold = resolveThreshold; | ||
this.#maxTimer = undefined; | ||
this.#state = 'instantiated'; | ||
this.#max = resolveMax; | ||
} | ||
get status() { | ||
return this[_state]; | ||
return this.#state; | ||
} | ||
@@ -41,4 +38,4 @@ | ||
if (this[_state] === 'instantiated') { | ||
this[_state] = state; | ||
if (this.#state === 'instantiated') { | ||
this.#state = state; | ||
this.emit('state', state); | ||
@@ -51,6 +48,6 @@ } | ||
clearTimeout(this[_maxTimer]); | ||
clearTimeout(this.#maxTimer); | ||
if (this[_state] !== state) { | ||
this[_state] = state; | ||
if (this.#state !== state) { | ||
this.#state = state; | ||
this.emit('state', state); | ||
@@ -63,4 +60,4 @@ } | ||
if (this[_state] !== state) { | ||
this[_state] = state; | ||
if (this.#state !== state) { | ||
this.#state = state; | ||
this.emit('state', state); | ||
@@ -75,19 +72,19 @@ } | ||
// reached. | ||
if (this[_state] === 'stable' || this[_state] === 'initializing') { | ||
this[_maxTimer] = setTimeout( | ||
if (this.#state === 'stable' || this.#state === 'initializing') { | ||
this.#maxTimer = setTimeout( | ||
this.setUnhealthyState.bind(this), | ||
this[_max], | ||
this.#max, | ||
); | ||
this[_maxTimer].unref(); | ||
this.#maxTimer.unref(); | ||
} | ||
// If state is unhealthy, continue keeping it unhealthy. | ||
const state = this[_state] === 'unhealthy' ? 'unhealthy' : 'unstable'; | ||
const state = this.#state === 'unhealthy' ? 'unhealthy' : 'unstable'; | ||
// Clear any possible previous threshold timeouts in case because we are | ||
// there was a call to this method within the threshold time. | ||
clearTimeout(this[_thresholdTimer]); | ||
clearTimeout(this.#thresholdTimer); | ||
if (this[_state] !== state) { | ||
this[_state] = state; | ||
if (this.#state !== state) { | ||
this.#state = state; | ||
this.emit('state', state); | ||
@@ -98,20 +95,22 @@ } | ||
// the timeout will call method for setting stable state. | ||
this[_thresholdTimer] = setTimeout( | ||
this.#thresholdTimer = setTimeout( | ||
this.setStableState.bind(this), | ||
this[_threshold], | ||
this.#threshold, | ||
); | ||
this[_thresholdTimer].unref(); | ||
this.#thresholdTimer.unref(); | ||
} | ||
reset() { | ||
clearTimeout(this[_thresholdTimer]); | ||
clearTimeout(this[_maxTimer]); | ||
this[_state] = 'instantiated'; | ||
clearTimeout(this.#thresholdTimer); | ||
clearTimeout(this.#maxTimer); | ||
this.#state = 'instantiated'; | ||
} | ||
get [Symbol.toStringTag]() { | ||
return 'PodiumClientState'; | ||
toJSON() { | ||
return { | ||
status: this.status, | ||
}; | ||
} | ||
toJSON() { | ||
[inspect]() { | ||
return { | ||
@@ -122,6 +121,6 @@ status: this.status, | ||
[util.inspect.custom](depth, options) { | ||
return util.inspect(this.toJSON(), depth, options); | ||
get [Symbol.toStringTag]() { | ||
return 'PodiumClientState'; | ||
} | ||
}; | ||
module.exports = PodiumClientState; |
{ | ||
"name": "@podium/client", | ||
"version": "5.0.0-next.4", | ||
"version": "5.0.0-next.5", | ||
"main": "lib/client.js", | ||
@@ -33,4 +33,3 @@ "license": "MIT", | ||
"lint:fix": "eslint --fix .", | ||
"test": "tap test/*.js", | ||
"lint:format": "eslint --fix .", | ||
"test": "tap --no-esm --no-cov --no-ts --no-jsx test/*.js", | ||
"precommit": "lint-staged" | ||
@@ -61,3 +60,3 @@ }, | ||
"benchmark": "2.1.4", | ||
"eslint": "7.3.1", | ||
"eslint": "7.5.0", | ||
"eslint-config-airbnb-base": "14.2.0", | ||
@@ -67,2 +66,3 @@ "eslint-config-prettier": "6.11.0", | ||
"eslint-plugin-prettier": "3.1.4", | ||
"babel-eslint": "10.1.0", | ||
"express": "4.17.1", | ||
@@ -69,0 +69,0 @@ "get-stream": "5.1.0", |
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
79016
22
1365