@webqit/webflo 0.11.61-0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/.gitignore +7 -7
  2. package/LICENSE +20 -20
  3. package/README.md +2079 -2074
  4. package/docker/Dockerfile +42 -42
  5. package/docker/README.md +91 -91
  6. package/docker/package.json +2 -2
  7. package/package.json +80 -81
  8. package/src/{Context.js → AbstractContext.js} +71 -79
  9. package/src/config-pi/deployment/Env.js +68 -68
  10. package/src/config-pi/deployment/Layout.js +63 -63
  11. package/src/config-pi/deployment/Origins.js +139 -139
  12. package/src/config-pi/deployment/Proxy.js +74 -74
  13. package/src/config-pi/deployment/index.js +17 -17
  14. package/src/config-pi/index.js +15 -15
  15. package/src/config-pi/runtime/Client.js +116 -98
  16. package/src/config-pi/runtime/Server.js +125 -125
  17. package/src/config-pi/runtime/client/Worker.js +109 -134
  18. package/src/config-pi/runtime/client/index.js +11 -11
  19. package/src/config-pi/runtime/index.js +17 -17
  20. package/src/config-pi/runtime/server/Headers.js +74 -74
  21. package/src/config-pi/runtime/server/Redirects.js +69 -69
  22. package/src/config-pi/runtime/server/index.js +13 -13
  23. package/src/config-pi/static/Manifest.js +319 -319
  24. package/src/config-pi/static/Ssg.js +49 -49
  25. package/src/config-pi/static/index.js +13 -13
  26. package/src/deployment-pi/index.js +10 -10
  27. package/src/deployment-pi/origins/index.js +216 -216
  28. package/src/index.js +11 -19
  29. package/src/runtime-pi/HttpEvent.js +126 -106
  30. package/src/runtime-pi/HttpUser.js +126 -0
  31. package/src/runtime-pi/MessagingOverBroadcast.js +9 -0
  32. package/src/runtime-pi/MessagingOverChannel.js +85 -0
  33. package/src/runtime-pi/MessagingOverSocket.js +106 -0
  34. package/src/runtime-pi/MultiportMessagingAPI.js +81 -0
  35. package/src/runtime-pi/WebfloCookieStorage.js +27 -0
  36. package/src/runtime-pi/WebfloEventTarget.js +39 -0
  37. package/src/runtime-pi/WebfloMessageEvent.js +58 -0
  38. package/src/runtime-pi/WebfloMessagingAPI.js +69 -0
  39. package/src/runtime-pi/{Router.js → WebfloRouter.js} +99 -130
  40. package/src/runtime-pi/WebfloRuntime.js +52 -0
  41. package/src/runtime-pi/WebfloStorage.js +109 -0
  42. package/src/runtime-pi/client/ClientMessaging.js +5 -0
  43. package/src/runtime-pi/client/Context.js +3 -7
  44. package/src/runtime-pi/client/CookieStorage.js +17 -0
  45. package/src/runtime-pi/client/Router.js +38 -48
  46. package/src/runtime-pi/client/SessionStorage.js +33 -0
  47. package/src/runtime-pi/client/Url.js +156 -205
  48. package/src/runtime-pi/client/WebfloClient.js +544 -0
  49. package/src/runtime-pi/client/WebfloRootClient1.js +179 -0
  50. package/src/runtime-pi/client/WebfloRootClient2.js +109 -0
  51. package/src/runtime-pi/client/WebfloSubClient.js +165 -0
  52. package/src/runtime-pi/client/Workport.js +118 -178
  53. package/src/runtime-pi/client/generate.js +480 -471
  54. package/src/runtime-pi/client/index.js +16 -21
  55. package/src/runtime-pi/client/worker/ClientMessaging.js +5 -0
  56. package/src/runtime-pi/client/worker/Context.js +3 -7
  57. package/src/runtime-pi/client/worker/CookieStorage.js +17 -0
  58. package/src/runtime-pi/client/worker/SessionStorage.js +13 -0
  59. package/src/runtime-pi/client/worker/WebfloWorker.js +294 -0
  60. package/src/runtime-pi/client/worker/Workport.js +17 -85
  61. package/src/runtime-pi/client/worker/index.js +10 -21
  62. package/src/runtime-pi/index.js +6 -13
  63. package/src/runtime-pi/server/ClientMessaging.js +18 -0
  64. package/src/runtime-pi/server/ClientMessagingRegistry.js +57 -0
  65. package/src/runtime-pi/server/Context.js +11 -15
  66. package/src/runtime-pi/server/CookieStorage.js +17 -0
  67. package/src/runtime-pi/server/Router.js +93 -159
  68. package/src/runtime-pi/server/SessionStorage.js +53 -0
  69. package/src/runtime-pi/server/WebfloServer.js +755 -0
  70. package/src/runtime-pi/server/index.js +10 -21
  71. package/src/runtime-pi/util-http.js +322 -86
  72. package/src/runtime-pi/util-url.js +146 -146
  73. package/src/runtime-pi/xURL.js +108 -105
  74. package/src/runtime-pi/xfetch.js +22 -22
  75. package/src/services-pi/cert/http-auth-hook.js +22 -22
  76. package/src/services-pi/cert/http-cleanup-hook.js +22 -22
  77. package/src/services-pi/cert/index.js +79 -79
  78. package/src/services-pi/index.js +8 -8
  79. package/src/static-pi/index.js +10 -10
  80. package/src/webflo.js +30 -30
  81. package/test/index.test.js +26 -26
  82. package/test/site/package.json +9 -9
  83. package/test/site/public/bundle.html +5 -5
  84. package/test/site/public/bundle.html.json +3 -3
  85. package/test/site/public/bundle.js +2 -2
  86. package/test/site/public/bundle.webflo.js +15 -15
  87. package/test/site/public/index.html +29 -29
  88. package/test/site/public/index1.html +34 -34
  89. package/test/site/public/page-2/bundle.html +4 -4
  90. package/test/site/public/page-2/bundle.js +2 -2
  91. package/test/site/public/page-2/index.html +45 -45
  92. package/test/site/public/page-2/main.html +2 -2
  93. package/test/site/public/page-4/subpage/bundle.js +2 -2
  94. package/test/site/public/page-4/subpage/index.html +30 -30
  95. package/test/site/public/sparoots.json +4 -4
  96. package/test/site/public/worker.js +3 -3
  97. package/test/site/server/index.js +15 -15
  98. package/src/runtime-pi/Application.js +0 -29
  99. package/src/runtime-pi/Cookies.js +0 -82
  100. package/src/runtime-pi/Runtime.js +0 -21
  101. package/src/runtime-pi/client/Application.js +0 -100
  102. package/src/runtime-pi/client/Runtime.js +0 -332
  103. package/src/runtime-pi/client/createStorage.js +0 -57
  104. package/src/runtime-pi/client/oohtml/full.js +0 -7
  105. package/src/runtime-pi/client/oohtml/namespacing.js +0 -7
  106. package/src/runtime-pi/client/oohtml/scripting.js +0 -8
  107. package/src/runtime-pi/client/oohtml/templating.js +0 -8
  108. package/src/runtime-pi/client/worker/Application.js +0 -44
  109. package/src/runtime-pi/client/worker/Runtime.js +0 -269
  110. package/src/runtime-pi/server/Application.js +0 -116
  111. package/src/runtime-pi/server/Runtime.js +0 -557
  112. package/src/runtime-pi/xFormData.js +0 -24
  113. package/src/runtime-pi/xHeaders.js +0 -146
  114. package/src/runtime-pi/xRequest.js +0 -46
  115. package/src/runtime-pi/xRequestHeaders.js +0 -109
  116. package/src/runtime-pi/xResponse.js +0 -33
  117. package/src/runtime-pi/xResponseHeaders.js +0 -117
  118. package/src/runtime-pi/xxHttpMessage.js +0 -102
@@ -1,48 +1,38 @@
1
-
2
- /**
3
- * @imports
4
- */
5
- import { path as Path } from '../util-url.js';
6
- import _Router from '../Router.js';
7
-
8
- /**
9
- * ---------------------------
10
- * The Router class
11
- * ---------------------------
12
- */
13
-
14
- export default class Router extends _Router {
15
-
16
- async readTick(thisTick) {
17
- thisTick = { ...thisTick };
18
- var routeTree = this.cx.layout;
19
- var routePaths = Object.keys(this.cx.layout);
20
- if (thisTick.trail) {
21
- thisTick.currentSegment = thisTick.destination[thisTick.trail.length];
22
- thisTick.currentSegmentOnFile = [ thisTick.currentSegment, '-' ].reduce((_segmentOnFile, _seg) => {
23
- if (_segmentOnFile.index) return _segmentOnFile;
24
- var _currentPath = `/${thisTick.trailOnFile.concat(_seg).join('/')}`;
25
- return routeTree[_currentPath] ? { seg: _seg, index: _currentPath } : (
26
- routePaths.filter(p => p.startsWith(`${_currentPath}/`)).length ? { seg: _seg, dirExists: true } : _segmentOnFile
27
- );
28
- }, { seg: null });
29
- thisTick.trail = thisTick.trail.concat(thisTick.currentSegment);
30
- thisTick.trailOnFile = thisTick.trailOnFile.concat(thisTick.currentSegmentOnFile.seg);
31
- thisTick.exports = routeTree[thisTick.currentSegmentOnFile.index];
32
- } else {
33
- thisTick.trail = [];
34
- thisTick.trailOnFile = [];
35
- thisTick.currentSegmentOnFile = { index: '/' };
36
- thisTick.exports = routeTree['/'];
37
- }
38
- return thisTick;
39
- }
40
-
41
- finalizeHandlerContext(context, thisTick) {
42
- return context.dirname = thisTick.currentSegmentOnFile.index;
43
- }
44
-
45
- pathJoin(...args) {
46
- return Path.join(...args);
47
- }
48
- };
1
+ import { path as Path } from '../util-url.js';
2
+ import { WebfloRouter } from '../WebfloRouter.js';
3
+
4
+ export class Router extends WebfloRouter {
5
+
6
+ async readTick(thisTick) {
7
+ thisTick = { ...thisTick };
8
+ var routeTree = this.cx.layout;
9
+ var routePaths = Object.keys(this.cx.layout);
10
+ if (thisTick.trail) {
11
+ thisTick.currentSegment = thisTick.destination[thisTick.trail.length];
12
+ thisTick.currentSegmentOnFile = [ thisTick.currentSegment, '-' ].reduce((_segmentOnFile, _seg) => {
13
+ if (_segmentOnFile.index) return _segmentOnFile;
14
+ var _currentPath = `/${thisTick.trailOnFile.concat(_seg).join('/')}`;
15
+ return routeTree[_currentPath] ? { seg: _seg, index: _currentPath } : (
16
+ routePaths.filter(p => p.startsWith(`${_currentPath}/`)).length ? { seg: _seg, dirExists: true } : _segmentOnFile
17
+ );
18
+ }, { seg: null });
19
+ thisTick.trail = thisTick.trail.concat(thisTick.currentSegment);
20
+ thisTick.trailOnFile = thisTick.trailOnFile.concat(thisTick.currentSegmentOnFile.seg);
21
+ thisTick.exports = routeTree[thisTick.currentSegmentOnFile.index];
22
+ } else {
23
+ thisTick.trail = [];
24
+ thisTick.trailOnFile = [];
25
+ thisTick.currentSegmentOnFile = { index: '/' };
26
+ thisTick.exports = routeTree['/'];
27
+ }
28
+ return thisTick;
29
+ }
30
+
31
+ finalizeHandlerContext(context, thisTick) {
32
+ return context.dirname = thisTick.currentSegmentOnFile.index;
33
+ }
34
+
35
+ pathJoin(...args) {
36
+ return Path.join(...args);
37
+ }
38
+ }
@@ -0,0 +1,33 @@
1
+ import { WebfloStorage } from '../WebfloStorage.js';
2
+
3
+ export class SessionStorage extends WebfloStorage {
4
+ static get type() { return 'session'; }
5
+
6
+ static create(request) {
7
+ const keys = [];
8
+ const storeType = this.type === 'user' ? 'localStorage' : 'sessionStorage';
9
+ for(let i = 0; i < window[storeType].length; i ++){
10
+ keys.push(window[storeType].key(i));
11
+ };
12
+ const instance = new this(
13
+ request,
14
+ keys.map((key) => [key, window[storeType].getItem(key)])
15
+ );
16
+ return instance;
17
+ }
18
+
19
+ constructor(request, iterable) {
20
+ super(request, true, iterable);
21
+ }
22
+
23
+ commit() {
24
+ const storeType = this.constructor.type === 'user' ? 'localStorage' : 'sessionStorage';
25
+ for (const key of this.getAdded()) {
26
+ window[storeType].setItem(key, this.get(key));
27
+ }
28
+ for (const key of this.getDeleted()) {
29
+ window[storeType].removeItem(key);
30
+ }
31
+ super.commit();
32
+ }
33
+ }
@@ -1,206 +1,157 @@
1
-
2
- /**
3
- * @imports
4
- */
5
- import { _with } from '@webqit/util/obj/index.js';
6
- import { _isArray, _isObject, _isTypeObject, _isString, _isEmpty } from '@webqit/util/js/index.js';
7
- import { Observer } from './Runtime.js';
8
- import { params } from '../util-url.js';
9
-
10
- /**
11
- * ---------------------------
12
- * The Url class
13
- * ---------------------------
14
- */
15
-
16
- export default class Url {
17
-
18
- /**
19
- * Constructs a new Url instance.
20
- *
21
- * @param object input
22
- * @param object pathMappingScheme
23
- *
24
- * @return void
25
- */
26
- constructor(input) {
27
- const Self = this.constructor;
28
- // -----------------------
29
- // Helpers
30
- var _strictEven = (a, b) => {
31
- if (_isObject(a) && _isObject(b)) {
32
- return _strictEven(Object.keys(a), Object.keys(b))
33
- && _strictEven(Object.values(a), Object.values(b));
34
- }
35
- if (_isArray(a) && _isArray(b)) {
36
- return a.length === b.length
37
- && a.reduce((recieved, item, i) => recieved && item === b[i], true);
38
- }
39
- return a === b;
40
- };
41
- Observer.intercept(this, 'set', (e, prev, next) => {
42
- if (e.name === 'hash' && e.value && !e.value.startsWith('#')) {
43
- return next('#' + e.value);
44
- }
45
- if (e.name === 'search' && e.value && !e.value.startsWith('?')) {
46
- return next('?' + e.value);
47
- }
48
- return next();
49
- });
50
- // -----------------------
51
- // When any one of these properties change,
52
- // the others are automatically derived
53
- Observer.observe(this, changes => {
54
- var urlObj = {};
55
- var onlyHrefChanged;
56
- for (var e of changes) {
57
- // ----------
58
- if (e.name === 'href' && e.related.length === 1) {
59
- var urlObj = Self.parseUrl(e.value);
60
- delete urlObj.query;
61
- delete urlObj.href;
62
- onlyHrefChanged = true;
63
- }
64
- // ----------
65
- if (e.name === 'query' && (e.path.length > 1 || !e.related.includes('search'))) {
66
- // "query" was updated. So we update "search"
67
- var search = Self.toSearch(this.query); // Not e.value, as that might be a subtree value
68
- if (search !== this.search) {
69
- urlObj.search = search;
70
- }
71
- }
72
- if (e.name === 'search') {
73
- // "search" was updated. So we update "query"
74
- var query = Self.toQuery(urlObj.search || this.search); // Not e.value, as that might be a href value
75
- if (!_strictEven(query, this.query)) {
76
- urlObj.query = query;
77
- }
78
- }
79
- }
80
- if (!onlyHrefChanged) {
81
- var fullOrigin = this.origin,
82
- usernamePassword = [ this.username, this.password ].filter(a => a);
83
- if (usernamePassword.length === 2) {
84
- fullOrigin = `${this.protocol}//${usernamePassword.join(':')}@${this.hostname}${(this.port ? `:${this.port}` : '')}`;
85
- }
86
- var href = [ fullOrigin, urlObj.pathname || this.pathname, urlObj.search || this.search, this.hash ].join('');
87
- if (href !== this.href) {
88
- urlObj.href = href;
89
- }
90
- }
91
- if (!_isEmpty(urlObj)) {
92
- return Observer.set(this, urlObj);
93
- }
94
- }, { subtree:true/*for pathmap/pathsplit/query updates*/, diff: true });
95
- // -----------------------
96
- // Validate e.detail
97
- Observer.observe(this, changes => {
98
- changes.forEach(e => {
99
- if (e && e.detail) {
100
- if (!_isTypeObject(e.detail)) {
101
- throw new Error('"e.detail" can only be of type object.');
102
- }
103
- if (e.detail.request && !_isObject(e.detail.request)) {
104
- throw new Error('"e.detail.request" can only be of type object.');
105
- }
106
- }
107
- });
108
- }, {diff: true});
109
- // -----------------------
110
- // Startup properties
111
- Observer.set(this, _isString(input) ? Self.parseUrl(input) : Url.copy(input));
112
- }
113
-
114
- /**
115
- * Converts the instance to string.
116
- *
117
- * @return string
118
- */
119
- toString() {
120
- return this.href;
121
- }
122
-
123
- /**
124
- * Creates an instance from parsing an URL string
125
- * or from a regular object.
126
- *
127
- * @param string|object href
128
- *
129
- * @return Url
130
- */
131
- static from(href) {
132
- return new this(_isObject(href) ? href : this.parseUrl(href));
133
- }
134
-
135
- /**
136
- * Copies URL properties off
137
- * the given object.
138
- *
139
- * @param object urlObj
140
- *
141
- * @return object
142
- */
143
- static copy(urlObj) {
144
- var url = urlProperties.reduce((obj, prop) => _with(obj, prop, urlObj[prop]), {});
145
- if (!('query' in urlObj)) {
146
- delete url.query;
147
- }
148
- return url;
149
- }
150
-
151
- /**
152
- * Parses an URL and returns its properties
153
- *
154
- * @param string href
155
- *
156
- * @return object
157
- */
158
- static parseUrl(href) {
159
- var a = document.createElement('a');
160
- a.href = href;
161
- return this.copy(a);
162
- }
163
-
164
- /**
165
- * Parses the input search string into a named map
166
- *
167
- * @param string search
168
- *
169
- * @return object
170
- */
171
- static toQuery(search) {
172
- return params.parse(search);
173
- }
174
-
175
- /**
176
- * Stringifies the input query to search string.
177
- *
178
- * @param object query
179
- *
180
- * @return string
181
- */
182
- static toSearch(query) {
183
- var search = params.stringify(query);
184
- return search ? '?' + search : '';
185
- }}
186
-
187
- /**
188
- * These are standard
189
- * and shouldnt'/can't be modified
190
- *
191
- * @array
192
- */
193
- const urlProperties = [
194
- 'protocol',
195
- 'username',
196
- 'password',
197
- 'host',
198
- 'hostname',
199
- 'port',
200
- 'origin',
201
- 'pathname',
202
- 'search',
203
- 'query',
204
- 'hash',
205
- 'href',
1
+ import { _with } from '@webqit/util/obj/index.js';
2
+ import { _isArray, _isObject, _isTypeObject, _isString, _isEmpty } from '@webqit/util/js/index.js';
3
+ import { params } from '../util-url.js';
4
+
5
+ const { Observer } = webqit;
6
+
7
+ export class Url {
8
+
9
+ constructor(input) {
10
+ const Self = this.constructor;
11
+ // -----------------------
12
+ // Helpers
13
+ var _strictEven = (a, b) => {
14
+ if (_isObject(a) && _isObject(b)) {
15
+ return _strictEven(Object.keys(a), Object.keys(b))
16
+ && _strictEven(Object.values(a), Object.values(b));
17
+ }
18
+ if (_isArray(a) && _isArray(b)) {
19
+ return a.length === b.length
20
+ && a.reduce((recieved, item, i) => recieved && item === b[i], true);
21
+ }
22
+ return a === b;
23
+ };
24
+ Observer.intercept(this, 'set', (e, prev, next) => {
25
+ if (e.key === 'hash' && e.value && !e.value.startsWith('#')) {
26
+ e.value = '#' + e.value;
27
+ } else if (e.key === 'search' && e.value && !e.value.startsWith('?')) {
28
+ e.value = '?' + e.value;
29
+ }
30
+ return next();
31
+ });
32
+ // -----------------------
33
+ // When any one of these properties change,
34
+ // the others are automatically derived
35
+ Observer.observe(this, changes => {
36
+ var urlObj = {};
37
+ var onlyHrefChanged;
38
+ for (var e of changes) {
39
+ // ----------
40
+ if (e.key === 'href' && e.related.length === 1) {
41
+ var urlObj = Self.parseUrl(e.value);
42
+ if (urlObj.pathname) {
43
+ urlObj.pathname = '/' + urlObj.pathname.split('/').filter(s => s.trim()).join('/');
44
+ }
45
+ delete urlObj.query;
46
+ delete urlObj.href;
47
+ onlyHrefChanged = true;
48
+ }
49
+ // ----------
50
+ if (e.key === 'query' && !e.related.includes('search')) {
51
+ // "query" was updated. So we update "search"
52
+ var search = Self.toSearch(this.query); // Not e.value, as that might be a subtree value
53
+ if (search !== this.search) {
54
+ urlObj.search = search;
55
+ }
56
+ }
57
+ if (e.key === 'search' && !e.related.includes('query')) {
58
+ // "search" was updated. So we update "query"
59
+ var query = Self.toQuery(urlObj.search || this.search); // Not e.value, as that might be a href value
60
+ if (!_strictEven(query, this.query)) {
61
+ urlObj.query = query;
62
+ }
63
+ }
64
+ if (e.key === 'pathname' && !e.related.includes('ancestorPathname')) {
65
+ // "pathname" was updated. So we update "ancestorPathname"
66
+ var ancestorPathname = (urlObj.pathname || this.pathname).replace(new RegExp('/[^/]+(?:/)?$'), '');
67
+ if (ancestorPathname !== this.ancestorPathname) {
68
+ urlObj.ancestorPathname = ancestorPathname;
69
+ }
70
+ }
71
+ if (e.key === 'ancestorPathname' && !e.related.includes('pathname')) {
72
+ // "ancestorPathname" was updated. So we update "pathname"
73
+ var pathname = '/' + (urlObj.ancestorPathname || this.ancestorPathname).split('/').filter(s => s).concat((urlObj.pathname || this.pathname).split('/').filter(s => s).pop()).join('/');
74
+ if (pathname !== this.pathname) {
75
+ urlObj.pathname = pathname;
76
+ }
77
+ }
78
+ }
79
+ if (!onlyHrefChanged) {
80
+ var fullOrigin = this.origin,
81
+ usernamePassword = [ this.username, this.password ].filter(a => a);
82
+ if (usernamePassword.length === 2) {
83
+ fullOrigin = `${this.protocol}//${usernamePassword.join(':')}@${this.hostname}${(this.port ? `:${this.port}` : '')}`;
84
+ }
85
+ var href = [ fullOrigin, urlObj.pathname || this.pathname, urlObj.search || this.search || (this.href.includes('?') ? '?' : ''), this.hash || (this.href.includes('#') ? '#' : '') ].join('');
86
+ if (href !== this.href) {
87
+ urlObj.href = href;
88
+ }
89
+ }
90
+ if (!_isEmpty(urlObj)) {
91
+ return Observer.set(this, urlObj);
92
+ }
93
+ }, { diff: true });
94
+ // -----------------------
95
+ // Validate e.detail
96
+ Observer.observe(this, changes => {
97
+ changes.forEach(e => {
98
+ if (e && e.detail) {
99
+ if (!_isTypeObject(e.detail)) {
100
+ throw new Error('"e.detail" can only be of type object.');
101
+ }
102
+ if (e.detail.request && !_isObject(e.detail.request)) {
103
+ throw new Error('"e.detail.request" can only be of type object.');
104
+ }
105
+ }
106
+ });
107
+ }, { diff: true });
108
+ // -----------------------
109
+ // Startup properties
110
+ Observer.set(this, _isString(input) ? Self.parseUrl(input) : Url.copy(input));
111
+ }
112
+
113
+ toString() {
114
+ return this.href;
115
+ }
116
+
117
+ static from(href) {
118
+ return new this(_isObject(href) ? href : this.parseUrl(href));
119
+ }
120
+
121
+ static copy(urlObj) {
122
+ var url = urlProperties.reduce((obj, prop) => _with(obj, prop, urlObj[prop] || ''), {});
123
+ if (!('query' in urlObj)) {
124
+ delete url.query;
125
+ }
126
+ return url;
127
+ }
128
+
129
+ static parseUrl(href) {
130
+ var a = new URL(href);
131
+ return this.copy(a);
132
+ }
133
+
134
+ static toQuery(search) {
135
+ return params.parse(search);
136
+ }
137
+
138
+ static toSearch(query) {
139
+ var search = params.stringify(query);
140
+ return search ? '?' + search : '';
141
+ }
142
+ }
143
+
144
+ const urlProperties = [
145
+ 'protocol',
146
+ 'username',
147
+ 'password',
148
+ 'host',
149
+ 'hostname',
150
+ 'port',
151
+ 'origin',
152
+ 'pathname',
153
+ 'search',
154
+ 'query',
155
+ 'hash',
156
+ 'href',
206
157
  ];