Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

fluent-url

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

fluent-url - npm Package Compare versions

Comparing version 1.1.0 to 1.2.0

9

CHANGELOG.md
## Change Log
### v1.2.0 on 2022-09-11
- Add support for relative URLs and empty URLs
- New methods searchObject, import, export, clone, qsToString, qsToObject
- New FluentURLSearchParams methods toObject
- Allow FluentURLSearchParams#set to accept an object
- Use new codecov uploader
- Update npm dev dependencies
### v1.1.0 on 2022-05-12

@@ -4,0 +13,0 @@

8

package.json
{
"name": "fluent-url",
"version": "1.1.0",
"version": "1.2.0",
"description": "A chainable version of the global URL class",

@@ -34,7 +34,7 @@ "keywords": [

"devDependencies": {
"eslint": "^8.15.0",
"eslint": "^8.23.0",
"eslint-config-prettier": "^8.5.0",
"jest": "^28.1.0",
"prettier": "^2.6.2"
"jest": "^29.0.2",
"prettier": "^2.7.1"
}
}
# fluent-url
[![Build status](https://ci.appveyor.com/api/projects/status/3q83wy4x2fu34p58?svg=true)](https://ci.appveyor.com/project/kensnyder/fluent-url)
[![Code Coverage](https://codecov.io/gh/kensnyder/fluent-url/branch/master/graph/badge.svg?v=1.1.0)](https://codecov.io/gh/kensnyder/fluent-url)
[![ISC License](https://img.shields.io/npm/l/fluent-url.svg?v=1.1.0)](https://opensource.org/licenses/ISC)
[![Code Coverage](https://codecov.io/gh/kensnyder/fluent-url/branch/master/graph/badge.svg?v=1.2.0)](https://codecov.io/gh/kensnyder/fluent-url)
[![ISC License](https://img.shields.io/npm/l/fluent-url.svg?v=1.2.0)](https://opensource.org/licenses/ISC)

@@ -25,3 +25,4 @@ A chainable version of the global URL class

const endpoint = url('https://api.example.com/search', { term: 'Foo' });
endpoint.href(); // https://api.example.com/search?term=Foo
endpoint.href();
// => "https://api.example.com/search?term=Foo"

@@ -39,3 +40,4 @@ // chainable example

.hash('#username')
.href(); // https://api.example.com:8080/login?a=one&b=two#username
.href();
// => "https://api.example.com:8080/login?a=one&b=two#username"

@@ -48,3 +50,8 @@ // extend search query instead of overwriting it

});
endpoint.href(); // https://api.example.com/search?term=Foo&limit=10&sort=created_at
endpoint.href();
// => "https://api.example.com/search?term=Foo&limit=10&sort=created_at"
// manipulate relative URLs
url('/search?term=Foo').qsAppend('limit', 10).href();
// => "/search?term=Foo&limit=10"
```

@@ -59,26 +66,34 @@

url(myUrl, searchParams); // searchParams is a plain object or instance of URLSearchParams
url(relative, baseUrl); // relative is the path and baseUrl is the domain
url(relative, baseUrl, searchParams); // searchParams is a string, plain object or instance of URLSearchParams
url(relative, base); // relative is the path and base is a string with protocol, port, domain
url(relative, base, searchParams); // searchParams is a string, plain object or instance of URLSearchParams
```
## Main Methods
## URL Built-in Methods
| URL built-in | FluentURL get | FluentURL set | Description | Example |
| ------------ | ------------- | ---------------------- | -------------------------------------------- | ------------------------------ |
| hash | .hash() | .hash(newHash) | The hash string including "#" | #foo |
| host | .host() | .host(newHost) | The domain name including port if applicable | example.com |
| hostname | .hostname() | .hostname(newHost) | The domain name excluding port | example.com |
| href | .href() | .href(newHref) | The entire URL - same as .toString() | http://example.com/foo?bar#baz |
| origin | .origin() | N/A | The protocol, domain and port | http://example.com:8443 |
| password | .password() | .password(newPassword) | The password | ftp-password |
| pathname | .pathname() | .pathname(newPathname) | The path including leading slash | /admin |
| port | .port() | .port(newPort) | The port number as a string | 8080 |
| protocol | .protocol() | .protocol(newProtocol) | The scheme | https: |
| search | .search() | .search(newSearch†) | The search string†, including "?" | ?a=one&b=two |
| searchParams | .searchParams | .searchParams = params | The URLSearchParams object for this URL | new URLSearchParams('a=1') |
| username | .username() | .username(newUsername) | The username string | ftpuser |
| toString() | .toString() | N/A | The entire URL | http://example.com/foo?bar#baz |
| URL built-in | FluentURL get | FluentURL set | Description | Example |
| ------------ | ------------- | ---------------------- | ---------------------------------------------------------- | ------------------------------ |
| .hash | .hash() | .hash(newHash) | The hash string including "#" | #foo |
| .host | .host() | .host(newHost) | The domain name including port if applicable | sub.example.com:8443 |
| .hostname | .hostname() | .hostname(newHost) | The domain name excluding port | sub.example.com |
| .href | .href() | .href(newHref) | The entire URL - same as .toString() | http://example.com/foo?bar#baz |
| .origin | .origin() | N/A | The protocol, domain and port | http://example.com:8443 |
| .password | .password() | .password(newPassword) | The password | ftp-password |
| .pathname | .pathname() | .pathname(newPathname) | The path including leading slash | /admin |
| .port | .port() | .port(newPort) | The port number as a string | 8080 |
| .protocol | .protocol() | .protocol(newProtocol) | The scheme | https: |
| .search | .search() | .search(newSearch) | The search string, plain object, or URLSearchParams object | ?a=one&b=two |
| .toString() | .toString() | N/A | The entire URL | http://example.com/foo?bar#baz |
| .username | .username() | .username(newUsername) | The username string | ftpuser |
† _newSearch can be a string, URLSearchParams object, or plain object._
## Custom Methods
| Method | Description |
| -------------------- | ------------------------------------------------------------- |
| .clone() | Create a new URL with the same values |
| .export(props) | Export all or the given subset of url attributes |
| .import(values) | Import the given pieces |
| .isRelative() | True if the URL is relative (e.g. starts with a dot or slash) |
| .makeRelative() | Control whether URL is relative |
| .searchObject([obj]) | Get or set query string params as object |
## Query String Methods

@@ -85,0 +100,0 @@

const FluentURLSearchParams = require('./FluentURLSearchParams.js');
class FluentURL {
constructor(url, base, searchParams) {
constructor(url = undefined, base = undefined, searchParams = undefined) {
if (typeof base === 'object' && arguments.length === 2) {

@@ -9,8 +9,10 @@ searchParams = base;

}
if (url instanceof URL) {
this.URL = url;
if (url) {
this.href(url, base);
this.fluentParams = new FluentURLSearchParams(this.URL.searchParams);
} else {
this.URL = new URL(url, base);
// created with no arguments or all empty arguments
this.href('/');
this.fluentParams = new FluentURLSearchParams({});
}
this.fluentParams = new FluentURLSearchParams(this.URL.searchParams);
if (searchParams) {

@@ -20,2 +22,10 @@ this.qsExtend(searchParams);

}
isRelative() {
return !!this._ignoreBase;
}
makeRelative() {
this.port('').username('').password('');
this._ignoreBase = this.protocol() + '//' + this.hostname();
return this;
}
hash(newHash) {

@@ -54,2 +64,5 @@ if (arguments.length === 0) {

if (arguments.length === 0) {
if (this._ignoreBase) {
return undefined;
}
return this.URL.host;

@@ -62,2 +75,5 @@ }

if (arguments.length === 0) {
if (this._ignoreBase) {
return undefined;
}
return this.URL.hostname;

@@ -68,10 +84,33 @@ }

}
href(newHref) {
href(url, base = undefined) {
if (arguments.length === 0) {
return this.URL.href;
this.URL.search = this.fluentParams.toString();
const href = this.URL.href;
if (this.isRelative()) {
return href.replace(this._ignoreBase, '');
}
return href;
}
this.URL.href = newHref;
if (url instanceof URL) {
this.URL = url;
} else {
const match = String(url).match(/^\.{0,2}\//);
if (match && !base) {
this._ignoreBase = 'http://fluent-url-base';
if (match[0].startsWith('.')) {
this._ignoreBase += '/relative';
}
url = this._ignoreBase + url;
} else {
this._ignoreBase = undefined;
}
this.URL = new URL(url, base);
}
this.fluentParams = new FluentURLSearchParams(this.URL.searchParams);
return this;
}
origin() {
if (this._ignoreBase) {
return undefined;
}
return this.URL.origin;

@@ -81,2 +120,5 @@ }

if (arguments.length === 0) {
if (this._ignoreBase) {
return undefined;
}
return this.URL.password;

@@ -96,2 +138,5 @@ }

if (arguments.length === 0) {
if (this._ignoreBase) {
return undefined;
}
return this.URL.port;

@@ -104,2 +149,5 @@ }

if (arguments.length === 0) {
if (this._ignoreBase) {
return undefined;
}
return this.URL.protocol;

@@ -112,3 +160,4 @@ }

if (arguments.length === 0) {
return this.URL.search;
const search = this.fluentParams.toString();
return search ? '?' + search : '';
}

@@ -122,7 +171,54 @@ if (typeof newSearch === 'string') {

}
searchObject(object) {
if (arguments.length === 0) {
return this.qsToObject();
}
this.qsSetAll(object);
return this;
}
toString() {
return this.URL.href;
return this.href();
}
export(
withProps = [
'hash',
'host',
'hostname',
'href',
'isRelative',
'origin',
'password',
'pathname',
'port',
'protocol',
'search',
'searchObject',
'username',
]
) {
const obj = {};
for (const prop of withProps) {
if (typeof this[prop] === 'function') {
obj[prop] = this[prop]();
}
}
return obj;
}
import(values) {
for (const [prop, value] of Object.entries(values)) {
if (typeof this[prop] === 'function') {
this[prop](value);
}
}
return this;
}
clone() {
this.URL.search = this.fluentParams.toString();
return new FluentURL(this.URL);
}
username(newUsername) {
if (arguments.length === 0) {
if (this._ignoreBase) {
return undefined;
}
return this.URL.username;

@@ -145,3 +241,3 @@ }

}
qsDeleteAll(name) {
qsDeleteAll() {
this.fluentParams.reset();

@@ -191,2 +287,8 @@ return this;

}
qsToString() {
return this.fluentParams.toString();
}
qsToObject() {
return this.fluentParams.toObject();
}
}

@@ -193,0 +295,0 @@

const FluentURL = require('./FluentURL.js');
const FluentURLSearchParams = require('./FluentURLSearchParams.js');
describe('FluentURL', () => {
describe('FluentURL constructor', () => {
const testUrl = 'https://user:pass@sub.example.com:8443/foo/?q=hello#hash';

@@ -30,2 +30,59 @@

});
it('should construct with no arguments', () => {
const url = new FluentURL();
url
.protocol('https')
.hostname('example.com')
.pathname('/foo')
.searchObject({ a: 'one' });
expect(url.toString()).toBe('https://example.com/foo?a=one');
});
});
describe('FluentURL methods', () => {
const testUrl = 'https://user:pass@sub.example.com:8443/foo/?q=hello#hash';
it('should export all details', () => {
const url = new FluentURL(testUrl);
expect(url.export()).toEqual({
hash: '#hash',
host: 'sub.example.com:8443',
hostname: 'sub.example.com',
href: testUrl,
isRelative: false,
origin: 'https://sub.example.com:8443',
password: 'pass',
pathname: '/foo/',
port: '8443',
protocol: 'https:',
search: '?q=hello',
searchObject: { q: 'hello' },
username: 'user',
});
});
it('should export only valid requested details', () => {
const url = new FluentURL(testUrl);
expect(url.export(['host', 'protocol', 'notAThing'])).toEqual({
host: 'sub.example.com:8443',
protocol: 'https:',
});
});
it('should import requested values', () => {
const url = new FluentURL();
url.import({
hash: '#hash',
hostname: 'sub.example.com',
password: 'pass',
pathname: '/foo/',
port: '8443',
protocol: 'https:',
searchObject: { q: 'hello' },
username: 'user',
notAThing: 42,
});
expect(url.href()).toBe(testUrl);
});
it('should clone ok', () => {
const url1 = new FluentURL(testUrl).searchObject({ a: 'one' });
const url2 = url1.clone();
expect(url1.href()).toBe(url2.href());
});
it('should get and set hash', () => {

@@ -52,4 +109,5 @@ const url = new FluentURL(testUrl);

expect(url.href()).toBe(testUrl);
expect(url.href('http://httpbin.org/foo/?bar#baz')).toBe(url);
expect(url.href()).toBe('http://httpbin.org/foo/?bar#baz');
const newHref = 'http://httpbin.org/foo/?bar=1#baz';
expect(url.href(newHref)).toBe(url);
expect(url.href()).toBe(newHref);
});

@@ -113,2 +171,11 @@ it('should get origin', () => {

});
it('should get search as object', () => {
const url = new FluentURL('http://example.com/search?q=hello');
expect(url.searchObject()).toEqual({ q: 'hello' });
});
it('should set search from object', () => {
const url = new FluentURL('http://example.com/search?q=hello');
url.searchObject({ a: 'one', b: 'two' });
expect(url.search()).toEqual('?a=one&b=two');
});
it('should serialize with toString', () => {

@@ -242,3 +309,61 @@ const url = new FluentURL(testUrl);

});
it('should support qsToString', () => {
const url = new FluentURL('https://example.com/foo/?a=1&b=2');
expect(url.qsToString()).toEqual('a=1&b=2');
});
});
describe('FluentURL relative URLs', () => {
const testUrl = '/foo/?q=hello#hash';
it('should return relative path', () => {
const url = new FluentURL(testUrl);
expect(url.href()).toBe('/foo/?q=hello#hash');
expect(url.isRelative()).toBe(true);
});
it('should return relative path with ../', () => {
const url = new FluentURL('../foo/?q=hello#hash');
expect(url.href()).toBe('../foo/?q=hello#hash');
expect(url.isRelative()).toBe(true);
});
it('should return relative path with ./', () => {
const url = new FluentURL('./foo/?q=hello#hash');
expect(url.href()).toBe('./foo/?q=hello#hash');
expect(url.isRelative()).toBe(true);
});
it('should return null for protocol', () => {
const url = new FluentURL(testUrl);
expect(url.protocol()).toBe(undefined);
});
it('should return null for port', () => {
const url = new FluentURL(testUrl);
expect(url.port()).toBe(undefined);
});
it('should return null for host', () => {
const url = new FluentURL(testUrl);
expect(url.host()).toBe(undefined);
});
it('should return null for hostname', () => {
const url = new FluentURL(testUrl);
expect(url.hostname()).toBe(undefined);
});
it('should return null for username', () => {
const url = new FluentURL(testUrl);
expect(url.username()).toBe(undefined);
});
it('should return null for password', () => {
const url = new FluentURL(testUrl);
expect(url.password()).toBe(undefined);
});
it('should return null for origin', () => {
const url = new FluentURL(testUrl);
expect(url.origin()).toBe(undefined);
});
it('should allowing making absolute URL relative', () => {
const url = new FluentURL(
'https://user:password@example.com:8443' + testUrl
);
url.makeRelative();
expect(url.href()).toBe(testUrl);
});
});
describe('FluentURL hash routing', () => {

@@ -245,0 +370,0 @@ const testUrl = 'https://example.com/home#/dashboard?msg=Hello';

@@ -66,3 +66,8 @@ class FluentURLSearchParams {

set(name, value) {
this.searchParams.set(name, value);
if (typeof name === 'object' && arguments.length === 1) {
this.reset();
this.extend(name);
} else {
this.searchParams.set(name, value);
}
return this;

@@ -81,2 +86,5 @@ }

}
toObject() {
return Object.fromEntries(this.searchParams.entries());
}
values() {

@@ -83,0 +91,0 @@ return this.searchParams.values();

@@ -15,2 +15,7 @@ const FluentURLSearchParams = require('./FluentURLSearchParams.js');

});
it('should support setting with object', () => {
const params = new FluentURLSearchParams(sample);
expect(params.set({ b: '2', c: '3' })).toBe(params);
expect(params.toString()).toBe('b=2&c=3');
});
});

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc