@uniformdev/redirect 19.27.0 → 19.29.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.
package/dist/index.d.ts CHANGED
@@ -98,6 +98,8 @@ declare class RedirectClient extends ApiClient<RedirectClientOptions> {
98
98
  static processUrlBestMatch: (url: string, trie: PathTrie<DirectionAwareRedirectDefinition>, options?: RedirectOptions) => Promise<RedirectResult | undefined>;
99
99
  processUrlBestMatch: (url: string, options?: RedirectOptions) => Promise<RedirectResult | undefined>;
100
100
  processUrlAllMatches: (url: string, options?: RedirectOptions) => Promise<RedirectResult[]>;
101
+ private static assembling;
102
+ private static assemblingPromise;
101
103
  private assembleTrie;
102
104
  private static processHops;
103
105
  private static processHop;
@@ -391,6 +393,7 @@ type RedirectClientGetRedirect = Pick<RedirectClientGetRequest, 'id' | 'projectM
391
393
  type RedirectClientGetRedirects = Pick<RedirectClientGetRequest, 'ids' | 'limit' | 'offset' | 'orderBy' | 'search'>;
392
394
 
393
395
  declare class WithMemoryCache extends RedirectClientCache<RedirectClientCacheOptions> {
396
+ private static refresher?;
394
397
  constructor(options: RedirectClientCacheOptions);
395
398
  private static trieCache;
396
399
  get(key: string): Promise<PathTrie<DirectionAwareRedirectDefinition>> | undefined;
package/dist/index.esm.js CHANGED
@@ -1,5 +1,43 @@
1
1
  import "./chunk-FFYIGW52.mjs";
2
2
 
3
+ // src/cache/data/refresher.ts
4
+ var Refresher = class {
5
+ /**
6
+ * Create new refresher
7
+ * @param refreshRate - How often the refresher will refresh. Cannot be less than 20000 ms or 20 seconds
8
+ * @param refreshDuration - How many times it should refresh before sleeps until something accesses the cache. Cannot be more than 5
9
+ * @param refresh - Refresh method to run at the refresh rate intervals
10
+ */
11
+ constructor({ refreshRate, refreshDuration, refresh }) {
12
+ this.refreshCounter = 0;
13
+ this.finalRate = refreshRate >= 2e4 ? refreshRate : 2e4;
14
+ this.finalDuration = refreshDuration <= 5 ? refreshDuration : 5;
15
+ this.refreshCounter = 1;
16
+ this.refresh = refresh;
17
+ this.start();
18
+ }
19
+ /**
20
+ * Start async updating process again, will run for the same duration as the original. refreshRate * refreshDuration milliseconds
21
+ */
22
+ kick() {
23
+ if (this.refreshCounter === 0) {
24
+ this.start();
25
+ }
26
+ this.refreshCounter = 1;
27
+ }
28
+ start() {
29
+ this.interval = setTimeout(async () => {
30
+ if (this.refreshCounter > this.finalDuration) {
31
+ this.refreshCounter = 0;
32
+ } else {
33
+ await this.refresh();
34
+ this.refreshCounter++;
35
+ this.start();
36
+ }
37
+ }, this.finalRate);
38
+ }
39
+ };
40
+
3
41
  // src/cache/redirectClientCache.ts
4
42
  var RedirectClientCache = class {
5
43
  constructor(options) {
@@ -11,11 +49,19 @@ var RedirectClientCache = class {
11
49
  var _WithMemoryCache = class extends RedirectClientCache {
12
50
  constructor(options) {
13
51
  super(options);
52
+ if (options.refreshRate && !_WithMemoryCache.refresher) {
53
+ _WithMemoryCache.refresher = new Refresher({
54
+ refreshRate: options.refreshRate,
55
+ refreshDuration: 5,
56
+ refresh: this.refresh
57
+ });
58
+ }
14
59
  }
15
60
  /* Get data from the cache and debounce pausing refresh */
16
61
  get(key) {
17
- var _a, _b;
18
- return (_b = (_a = _WithMemoryCache.trieCache[key]) == null ? void 0 : _a.data) != null ? _b : void 0;
62
+ var _a, _b, _c;
63
+ (_a = _WithMemoryCache.refresher) == null ? void 0 : _a.kick();
64
+ return (_c = (_b = _WithMemoryCache.trieCache[key]) == null ? void 0 : _b.data) != null ? _c : void 0;
19
65
  }
20
66
  /* Set new data to the cache and reset the refresh method */
21
67
  set(key, data, refresh) {
@@ -39,13 +85,15 @@ var _WithMemoryCache = class extends RedirectClientCache {
39
85
  }
40
86
  refresh() {
41
87
  var _a, _b;
88
+ const caches = [];
42
89
  for (const key in _WithMemoryCache.trieCache) {
43
90
  const p = (_b = (_a = _WithMemoryCache.trieCache[key]) == null ? void 0 : _a.refresh) == null ? void 0 : _b.call(_a);
44
91
  if (p) {
45
92
  _WithMemoryCache.trieCache[key] = { ..._WithMemoryCache.trieCache[key], data: p };
93
+ caches.push(p.then());
46
94
  }
47
95
  }
48
- return Promise.all([]);
96
+ return Promise.all(caches);
49
97
  }
50
98
  };
51
99
  var WithMemoryCache = _WithMemoryCache;
@@ -130,7 +178,7 @@ var PathTrie = class {
130
178
  }
131
179
  return result;
132
180
  };
133
- const scanWildcards = () => {
181
+ const scanWildcards = (i) => {
134
182
  let wildcard = void 0;
135
183
  while ((!wildcard || wildcard.active) && wildcards.length) {
136
184
  wildcard = wildcards.pop();
@@ -140,6 +188,11 @@ var PathTrie = class {
140
188
  wildcard.active = true;
141
189
  cur = wildcard == null ? void 0 : wildcard.startTrie;
142
190
  wildcards.push(wildcard);
191
+ if (i === segments.length - 1 && wildcards.length && wildcards[wildcards.length - 1].active && wildcards[wildcards.length - 1].startTrie[dataProp]) {
192
+ wildcards[wildcards.length - 1].startTrie[dataProp].forEach(
193
+ (d) => ret.push({ data: d, variables: getVariables() })
194
+ );
195
+ }
143
196
  return wildcard == null ? void 0 : wildcard.start;
144
197
  };
145
198
  for (let i = 0; i < segments.length; i++) {
@@ -168,21 +221,22 @@ var PathTrie = class {
168
221
  if (i === segments.length - 1) {
169
222
  if (cur[dataProp]) {
170
223
  cur[dataProp].forEach((d) => ret.push({ data: d, variables: getVariables() }));
224
+ } else {
225
+ const more = scanWildcards(i);
226
+ if (typeof more === "undefined" || ret.length > 0 && bestMatch)
227
+ return [...ret, ...splats];
228
+ i = more;
171
229
  }
172
230
  }
173
- } else if (i === segments.length - 1) {
174
- const more = scanWildcards();
175
- if (typeof more === "undefined")
231
+ } else {
232
+ const more = scanWildcards(i);
233
+ if (typeof more === "undefined" || ret.length > 0 && bestMatch)
176
234
  return [...ret, ...splats];
177
235
  i = more;
178
- if (i === segments.length - 1 && wildcards.length && wildcards[wildcards.length - 1].active && wildcards[wildcards.length - 1].startTrie[dataProp]) {
179
- wildcards[wildcards.length - 1].startTrie[dataProp].forEach(
180
- (d) => ret.push({ data: d, variables: getVariables() })
181
- );
182
- }
183
- } else {
184
- const more = scanWildcards();
185
- if (typeof more === "undefined")
236
+ }
237
+ if (i === segments.length - 1 && wildcards.length !== 0) {
238
+ const more = scanWildcards(i);
239
+ if (typeof more === "undefined" || ret.length > 0 && bestMatch)
186
240
  return [...ret, ...splats];
187
241
  i = more;
188
242
  }
@@ -342,22 +396,29 @@ var _RedirectClient = class extends ApiClient {
342
396
  }
343
397
  }
344
398
  async assembleTrie() {
345
- const trie = new PathTrie();
346
- for await (const redirect of this.getAllRedirects()) {
347
- const source = processUrl(redirect.redirect.sourceUrl);
348
- const target = processUrl(redirect.redirect.targetUrl);
349
- trie.insert(source.path, {
350
- redirect: redirect.redirect,
351
- metadata: redirect.metadata,
352
- reverse: false
353
- });
354
- trie.insert(target.path, {
355
- redirect: redirect.redirect,
356
- metadata: redirect.metadata,
357
- reverse: true
358
- });
399
+ if (!_RedirectClient.assembling) {
400
+ _RedirectClient.assembling = true;
401
+ _RedirectClient.assemblingPromise = (async () => {
402
+ const trie = new PathTrie();
403
+ for await (const redirect of this.getAllRedirects()) {
404
+ const source = processUrl(redirect.redirect.sourceUrl);
405
+ const target = processUrl(redirect.redirect.targetUrl);
406
+ trie.insert(source.path, {
407
+ redirect: redirect.redirect,
408
+ metadata: redirect.metadata,
409
+ reverse: false
410
+ });
411
+ trie.insert(target.path, {
412
+ redirect: redirect.redirect,
413
+ metadata: redirect.metadata,
414
+ reverse: true
415
+ });
416
+ }
417
+ _RedirectClient.assembling = false;
418
+ return trie;
419
+ })();
359
420
  }
360
- return trie;
421
+ return await _RedirectClient.assemblingPromise;
361
422
  }
362
423
  static processHops(url, trie, bestMatch, options) {
363
424
  var _a;
@@ -429,20 +490,14 @@ var _RedirectClient = class extends ApiClient {
429
490
  * @param options - Different options available to the redirect engine
430
491
  */
431
492
  static processDefinitionToResults(processedUrl, definition, variables, options) {
432
- var _a, _b, _c, _d, _e;
433
493
  const resultUrl = (options == null ? void 0 : options.reverse) ? definition.redirect.sourceUrl : definition.redirect.targetUrl;
434
494
  const processedResult = processUrl(resultUrl);
435
495
  const redirect = definition == null ? void 0 : definition.redirect;
436
496
  if (redirect.sourceMustMatchDomain && processedUrl.domain !== processedResult.domain)
437
497
  return void 0;
438
- const protocol = redirect.targetPreserveIncomingProtocol ? processedUrl.protocol : (_b = (_a = processedResult.protocol) != null ? _a : processedUrl.protocol) != null ? _b : "";
439
- const domain = redirect.targetPreserveIncomingDomain ? processedUrl.domain : (_d = (_c = processedResult.domain) != null ? _c : processedUrl.domain) != null ? _d : "";
440
- const queryString = redirect.sourceRetainQuerystring && processedResult.query ? (_e = processedResult.query) != null ? _e : "" : definition.redirect.sourceRetainQuerystring ? processedUrl.query : "";
441
- const finalUrl = `${protocol}${domain}${processedResult.port}${processedResult.path}${queryString}`;
498
+ const finalUrl = _RedirectClient.getTargetVariableExpandedUrl(processedUrl.url, redirect);
442
499
  return {
443
- url: variables.reduce((cur, o) => {
444
- return cur.replace(o.key, o.value);
445
- }, finalUrl),
500
+ url: finalUrl,
446
501
  definition,
447
502
  label: (options == null ? void 0 : options.label) ? variables.reduce((cur, o) => {
448
503
  return cur.replace(o.key, `<em>${o.value}</em>`);
@@ -477,7 +532,7 @@ var _RedirectClient = class extends ApiClient {
477
532
  }
478
533
  const protocol = redirectDefinition.targetPreserveIncomingProtocol ? processedUrl.protocol : processedTarget.protocol;
479
534
  const domain = redirectDefinition.targetPreserveIncomingDomain ? processedUrl.domain : processedTarget.domain;
480
- const port = processedTarget.port;
535
+ const port = redirectDefinition.targetPreserveIncomingDomain ? processedUrl.port : processedTarget.port;
481
536
  const query = redirectDefinition.sourceRetainQuerystring && redirectDefinition.targetMergeQuerystring ? this.mergeQueryStrings(processedUrl.query, processedTarget.query) : !redirectDefinition.targetMergeQuerystring && redirectDefinition.sourceRetainQuerystring ? processedUrl.query : processedTarget.query;
482
537
  const fragment = redirectDefinition.sourceRetainQuerystring && redirectDefinition.targetMergeQuerystring ? this.mergeQueryStrings(processedUrl.fragment, processedTarget.fragment) : !redirectDefinition.targetMergeQuerystring && redirectDefinition.sourceRetainQuerystring ? processedUrl.fragment : processedTarget.fragment;
483
538
  return `${protocol}${domain}${port}${finalUrlPath}${query}${fragment}`;
@@ -499,15 +554,15 @@ var _RedirectClient = class extends ApiClient {
499
554
  return (fragment ? "#" : "?") + merged;
500
555
  return "";
501
556
  }
502
- static getSourceVariables(path, source, isVariable = (pathSegment) => pathSegment.startsWith(":")) {
557
+ static getSourceVariables(path, source, isVariable = (pathSegment, isLast) => pathSegment.startsWith(":") || pathSegment === "*" && isLast) {
503
558
  const variables = {};
504
559
  const pathSegments = path.split("/");
505
560
  const sourceSegments = source.split("/");
506
- if (pathSegments.length !== sourceSegments.length) {
561
+ if (pathSegments.length !== sourceSegments.length && !source.endsWith("/*")) {
507
562
  throw new Error("Path and source have different numbers of path segments, must be the same");
508
563
  }
509
564
  sourceSegments.forEach((sourceSegment, i) => {
510
- if (isVariable(sourceSegment)) {
565
+ if (isVariable(sourceSegment, i === sourceSegments.length - 1)) {
511
566
  variables[sourceSegment] = pathSegments[i];
512
567
  }
513
568
  });
@@ -519,6 +574,10 @@ RedirectClient.processUrlBestMatch = async (url, trie, options) => {
519
574
  var _a;
520
575
  return (_a = _RedirectClient.processHops(url, trie, true, options)) == null ? void 0 : _a[0];
521
576
  };
577
+ RedirectClient.assembling = false;
578
+ RedirectClient.assemblingPromise = new Promise(
579
+ () => new PathTrie()
580
+ );
522
581
  var UncachedRedirectClient = class extends RedirectClient {
523
582
  constructor(options) {
524
583
  super({ ...options, bypassCache: true });
package/dist/index.js CHANGED
@@ -326,6 +326,44 @@ __export(src_exports, {
326
326
  });
327
327
  module.exports = __toCommonJS(src_exports);
328
328
 
329
+ // src/cache/data/refresher.ts
330
+ var Refresher = class {
331
+ /**
332
+ * Create new refresher
333
+ * @param refreshRate - How often the refresher will refresh. Cannot be less than 20000 ms or 20 seconds
334
+ * @param refreshDuration - How many times it should refresh before sleeps until something accesses the cache. Cannot be more than 5
335
+ * @param refresh - Refresh method to run at the refresh rate intervals
336
+ */
337
+ constructor({ refreshRate, refreshDuration, refresh }) {
338
+ this.refreshCounter = 0;
339
+ this.finalRate = refreshRate >= 2e4 ? refreshRate : 2e4;
340
+ this.finalDuration = refreshDuration <= 5 ? refreshDuration : 5;
341
+ this.refreshCounter = 1;
342
+ this.refresh = refresh;
343
+ this.start();
344
+ }
345
+ /**
346
+ * Start async updating process again, will run for the same duration as the original. refreshRate * refreshDuration milliseconds
347
+ */
348
+ kick() {
349
+ if (this.refreshCounter === 0) {
350
+ this.start();
351
+ }
352
+ this.refreshCounter = 1;
353
+ }
354
+ start() {
355
+ this.interval = setTimeout(async () => {
356
+ if (this.refreshCounter > this.finalDuration) {
357
+ this.refreshCounter = 0;
358
+ } else {
359
+ await this.refresh();
360
+ this.refreshCounter++;
361
+ this.start();
362
+ }
363
+ }, this.finalRate);
364
+ }
365
+ };
366
+
329
367
  // src/cache/redirectClientCache.ts
330
368
  var RedirectClientCache = class {
331
369
  constructor(options) {
@@ -337,11 +375,19 @@ var RedirectClientCache = class {
337
375
  var _WithMemoryCache = class extends RedirectClientCache {
338
376
  constructor(options) {
339
377
  super(options);
378
+ if (options.refreshRate && !_WithMemoryCache.refresher) {
379
+ _WithMemoryCache.refresher = new Refresher({
380
+ refreshRate: options.refreshRate,
381
+ refreshDuration: 5,
382
+ refresh: this.refresh
383
+ });
384
+ }
340
385
  }
341
386
  /* Get data from the cache and debounce pausing refresh */
342
387
  get(key) {
343
- var _a, _b;
344
- return (_b = (_a = _WithMemoryCache.trieCache[key]) == null ? void 0 : _a.data) != null ? _b : void 0;
388
+ var _a, _b, _c;
389
+ (_a = _WithMemoryCache.refresher) == null ? void 0 : _a.kick();
390
+ return (_c = (_b = _WithMemoryCache.trieCache[key]) == null ? void 0 : _b.data) != null ? _c : void 0;
345
391
  }
346
392
  /* Set new data to the cache and reset the refresh method */
347
393
  set(key, data, refresh) {
@@ -365,13 +411,15 @@ var _WithMemoryCache = class extends RedirectClientCache {
365
411
  }
366
412
  refresh() {
367
413
  var _a, _b;
414
+ const caches = [];
368
415
  for (const key in _WithMemoryCache.trieCache) {
369
416
  const p = (_b = (_a = _WithMemoryCache.trieCache[key]) == null ? void 0 : _a.refresh) == null ? void 0 : _b.call(_a);
370
417
  if (p) {
371
418
  _WithMemoryCache.trieCache[key] = { ..._WithMemoryCache.trieCache[key], data: p };
419
+ caches.push(p.then());
372
420
  }
373
421
  }
374
- return Promise.all([]);
422
+ return Promise.all(caches);
375
423
  }
376
424
  };
377
425
  var WithMemoryCache = _WithMemoryCache;
@@ -456,7 +504,7 @@ var PathTrie = class {
456
504
  }
457
505
  return result;
458
506
  };
459
- const scanWildcards = () => {
507
+ const scanWildcards = (i) => {
460
508
  let wildcard = void 0;
461
509
  while ((!wildcard || wildcard.active) && wildcards.length) {
462
510
  wildcard = wildcards.pop();
@@ -466,6 +514,11 @@ var PathTrie = class {
466
514
  wildcard.active = true;
467
515
  cur = wildcard == null ? void 0 : wildcard.startTrie;
468
516
  wildcards.push(wildcard);
517
+ if (i === segments.length - 1 && wildcards.length && wildcards[wildcards.length - 1].active && wildcards[wildcards.length - 1].startTrie[dataProp]) {
518
+ wildcards[wildcards.length - 1].startTrie[dataProp].forEach(
519
+ (d) => ret.push({ data: d, variables: getVariables() })
520
+ );
521
+ }
469
522
  return wildcard == null ? void 0 : wildcard.start;
470
523
  };
471
524
  for (let i = 0; i < segments.length; i++) {
@@ -494,21 +547,22 @@ var PathTrie = class {
494
547
  if (i === segments.length - 1) {
495
548
  if (cur[dataProp]) {
496
549
  cur[dataProp].forEach((d) => ret.push({ data: d, variables: getVariables() }));
550
+ } else {
551
+ const more = scanWildcards(i);
552
+ if (typeof more === "undefined" || ret.length > 0 && bestMatch)
553
+ return [...ret, ...splats];
554
+ i = more;
497
555
  }
498
556
  }
499
- } else if (i === segments.length - 1) {
500
- const more = scanWildcards();
501
- if (typeof more === "undefined")
557
+ } else {
558
+ const more = scanWildcards(i);
559
+ if (typeof more === "undefined" || ret.length > 0 && bestMatch)
502
560
  return [...ret, ...splats];
503
561
  i = more;
504
- if (i === segments.length - 1 && wildcards.length && wildcards[wildcards.length - 1].active && wildcards[wildcards.length - 1].startTrie[dataProp]) {
505
- wildcards[wildcards.length - 1].startTrie[dataProp].forEach(
506
- (d) => ret.push({ data: d, variables: getVariables() })
507
- );
508
- }
509
- } else {
510
- const more = scanWildcards();
511
- if (typeof more === "undefined")
562
+ }
563
+ if (i === segments.length - 1 && wildcards.length !== 0) {
564
+ const more = scanWildcards(i);
565
+ if (typeof more === "undefined" || ret.length > 0 && bestMatch)
512
566
  return [...ret, ...splats];
513
567
  i = more;
514
568
  }
@@ -668,22 +722,29 @@ var _RedirectClient = class extends import_api.ApiClient {
668
722
  }
669
723
  }
670
724
  async assembleTrie() {
671
- const trie = new PathTrie();
672
- for await (const redirect of this.getAllRedirects()) {
673
- const source = processUrl(redirect.redirect.sourceUrl);
674
- const target = processUrl(redirect.redirect.targetUrl);
675
- trie.insert(source.path, {
676
- redirect: redirect.redirect,
677
- metadata: redirect.metadata,
678
- reverse: false
679
- });
680
- trie.insert(target.path, {
681
- redirect: redirect.redirect,
682
- metadata: redirect.metadata,
683
- reverse: true
684
- });
725
+ if (!_RedirectClient.assembling) {
726
+ _RedirectClient.assembling = true;
727
+ _RedirectClient.assemblingPromise = (async () => {
728
+ const trie = new PathTrie();
729
+ for await (const redirect of this.getAllRedirects()) {
730
+ const source = processUrl(redirect.redirect.sourceUrl);
731
+ const target = processUrl(redirect.redirect.targetUrl);
732
+ trie.insert(source.path, {
733
+ redirect: redirect.redirect,
734
+ metadata: redirect.metadata,
735
+ reverse: false
736
+ });
737
+ trie.insert(target.path, {
738
+ redirect: redirect.redirect,
739
+ metadata: redirect.metadata,
740
+ reverse: true
741
+ });
742
+ }
743
+ _RedirectClient.assembling = false;
744
+ return trie;
745
+ })();
685
746
  }
686
- return trie;
747
+ return await _RedirectClient.assemblingPromise;
687
748
  }
688
749
  static processHops(url, trie, bestMatch, options) {
689
750
  var _a;
@@ -755,20 +816,14 @@ var _RedirectClient = class extends import_api.ApiClient {
755
816
  * @param options - Different options available to the redirect engine
756
817
  */
757
818
  static processDefinitionToResults(processedUrl, definition, variables, options) {
758
- var _a, _b, _c, _d, _e;
759
819
  const resultUrl = (options == null ? void 0 : options.reverse) ? definition.redirect.sourceUrl : definition.redirect.targetUrl;
760
820
  const processedResult = processUrl(resultUrl);
761
821
  const redirect = definition == null ? void 0 : definition.redirect;
762
822
  if (redirect.sourceMustMatchDomain && processedUrl.domain !== processedResult.domain)
763
823
  return void 0;
764
- const protocol = redirect.targetPreserveIncomingProtocol ? processedUrl.protocol : (_b = (_a = processedResult.protocol) != null ? _a : processedUrl.protocol) != null ? _b : "";
765
- const domain = redirect.targetPreserveIncomingDomain ? processedUrl.domain : (_d = (_c = processedResult.domain) != null ? _c : processedUrl.domain) != null ? _d : "";
766
- const queryString = redirect.sourceRetainQuerystring && processedResult.query ? (_e = processedResult.query) != null ? _e : "" : definition.redirect.sourceRetainQuerystring ? processedUrl.query : "";
767
- const finalUrl = `${protocol}${domain}${processedResult.port}${processedResult.path}${queryString}`;
824
+ const finalUrl = _RedirectClient.getTargetVariableExpandedUrl(processedUrl.url, redirect);
768
825
  return {
769
- url: variables.reduce((cur, o) => {
770
- return cur.replace(o.key, o.value);
771
- }, finalUrl),
826
+ url: finalUrl,
772
827
  definition,
773
828
  label: (options == null ? void 0 : options.label) ? variables.reduce((cur, o) => {
774
829
  return cur.replace(o.key, `<em>${o.value}</em>`);
@@ -803,7 +858,7 @@ var _RedirectClient = class extends import_api.ApiClient {
803
858
  }
804
859
  const protocol = redirectDefinition.targetPreserveIncomingProtocol ? processedUrl.protocol : processedTarget.protocol;
805
860
  const domain = redirectDefinition.targetPreserveIncomingDomain ? processedUrl.domain : processedTarget.domain;
806
- const port = processedTarget.port;
861
+ const port = redirectDefinition.targetPreserveIncomingDomain ? processedUrl.port : processedTarget.port;
807
862
  const query = redirectDefinition.sourceRetainQuerystring && redirectDefinition.targetMergeQuerystring ? this.mergeQueryStrings(processedUrl.query, processedTarget.query) : !redirectDefinition.targetMergeQuerystring && redirectDefinition.sourceRetainQuerystring ? processedUrl.query : processedTarget.query;
808
863
  const fragment = redirectDefinition.sourceRetainQuerystring && redirectDefinition.targetMergeQuerystring ? this.mergeQueryStrings(processedUrl.fragment, processedTarget.fragment) : !redirectDefinition.targetMergeQuerystring && redirectDefinition.sourceRetainQuerystring ? processedUrl.fragment : processedTarget.fragment;
809
864
  return `${protocol}${domain}${port}${finalUrlPath}${query}${fragment}`;
@@ -825,15 +880,15 @@ var _RedirectClient = class extends import_api.ApiClient {
825
880
  return (fragment ? "#" : "?") + merged;
826
881
  return "";
827
882
  }
828
- static getSourceVariables(path, source, isVariable = (pathSegment) => pathSegment.startsWith(":")) {
883
+ static getSourceVariables(path, source, isVariable = (pathSegment, isLast) => pathSegment.startsWith(":") || pathSegment === "*" && isLast) {
829
884
  const variables = {};
830
885
  const pathSegments = path.split("/");
831
886
  const sourceSegments = source.split("/");
832
- if (pathSegments.length !== sourceSegments.length) {
887
+ if (pathSegments.length !== sourceSegments.length && !source.endsWith("/*")) {
833
888
  throw new Error("Path and source have different numbers of path segments, must be the same");
834
889
  }
835
890
  sourceSegments.forEach((sourceSegment, i) => {
836
- if (isVariable(sourceSegment)) {
891
+ if (isVariable(sourceSegment, i === sourceSegments.length - 1)) {
837
892
  variables[sourceSegment] = pathSegments[i];
838
893
  }
839
894
  });
@@ -845,6 +900,10 @@ RedirectClient.processUrlBestMatch = async (url, trie, options) => {
845
900
  var _a;
846
901
  return (_a = _RedirectClient.processHops(url, trie, true, options)) == null ? void 0 : _a[0];
847
902
  };
903
+ RedirectClient.assembling = false;
904
+ RedirectClient.assemblingPromise = new Promise(
905
+ () => new PathTrie()
906
+ );
848
907
  var UncachedRedirectClient = class extends RedirectClient {
849
908
  constructor(options) {
850
909
  super({ ...options, bypassCache: true });
package/dist/index.mjs CHANGED
@@ -1,5 +1,43 @@
1
1
  import "./chunk-FFYIGW52.mjs";
2
2
 
3
+ // src/cache/data/refresher.ts
4
+ var Refresher = class {
5
+ /**
6
+ * Create new refresher
7
+ * @param refreshRate - How often the refresher will refresh. Cannot be less than 20000 ms or 20 seconds
8
+ * @param refreshDuration - How many times it should refresh before sleeps until something accesses the cache. Cannot be more than 5
9
+ * @param refresh - Refresh method to run at the refresh rate intervals
10
+ */
11
+ constructor({ refreshRate, refreshDuration, refresh }) {
12
+ this.refreshCounter = 0;
13
+ this.finalRate = refreshRate >= 2e4 ? refreshRate : 2e4;
14
+ this.finalDuration = refreshDuration <= 5 ? refreshDuration : 5;
15
+ this.refreshCounter = 1;
16
+ this.refresh = refresh;
17
+ this.start();
18
+ }
19
+ /**
20
+ * Start async updating process again, will run for the same duration as the original. refreshRate * refreshDuration milliseconds
21
+ */
22
+ kick() {
23
+ if (this.refreshCounter === 0) {
24
+ this.start();
25
+ }
26
+ this.refreshCounter = 1;
27
+ }
28
+ start() {
29
+ this.interval = setTimeout(async () => {
30
+ if (this.refreshCounter > this.finalDuration) {
31
+ this.refreshCounter = 0;
32
+ } else {
33
+ await this.refresh();
34
+ this.refreshCounter++;
35
+ this.start();
36
+ }
37
+ }, this.finalRate);
38
+ }
39
+ };
40
+
3
41
  // src/cache/redirectClientCache.ts
4
42
  var RedirectClientCache = class {
5
43
  constructor(options) {
@@ -11,11 +49,19 @@ var RedirectClientCache = class {
11
49
  var _WithMemoryCache = class extends RedirectClientCache {
12
50
  constructor(options) {
13
51
  super(options);
52
+ if (options.refreshRate && !_WithMemoryCache.refresher) {
53
+ _WithMemoryCache.refresher = new Refresher({
54
+ refreshRate: options.refreshRate,
55
+ refreshDuration: 5,
56
+ refresh: this.refresh
57
+ });
58
+ }
14
59
  }
15
60
  /* Get data from the cache and debounce pausing refresh */
16
61
  get(key) {
17
- var _a, _b;
18
- return (_b = (_a = _WithMemoryCache.trieCache[key]) == null ? void 0 : _a.data) != null ? _b : void 0;
62
+ var _a, _b, _c;
63
+ (_a = _WithMemoryCache.refresher) == null ? void 0 : _a.kick();
64
+ return (_c = (_b = _WithMemoryCache.trieCache[key]) == null ? void 0 : _b.data) != null ? _c : void 0;
19
65
  }
20
66
  /* Set new data to the cache and reset the refresh method */
21
67
  set(key, data, refresh) {
@@ -39,13 +85,15 @@ var _WithMemoryCache = class extends RedirectClientCache {
39
85
  }
40
86
  refresh() {
41
87
  var _a, _b;
88
+ const caches = [];
42
89
  for (const key in _WithMemoryCache.trieCache) {
43
90
  const p = (_b = (_a = _WithMemoryCache.trieCache[key]) == null ? void 0 : _a.refresh) == null ? void 0 : _b.call(_a);
44
91
  if (p) {
45
92
  _WithMemoryCache.trieCache[key] = { ..._WithMemoryCache.trieCache[key], data: p };
93
+ caches.push(p.then());
46
94
  }
47
95
  }
48
- return Promise.all([]);
96
+ return Promise.all(caches);
49
97
  }
50
98
  };
51
99
  var WithMemoryCache = _WithMemoryCache;
@@ -130,7 +178,7 @@ var PathTrie = class {
130
178
  }
131
179
  return result;
132
180
  };
133
- const scanWildcards = () => {
181
+ const scanWildcards = (i) => {
134
182
  let wildcard = void 0;
135
183
  while ((!wildcard || wildcard.active) && wildcards.length) {
136
184
  wildcard = wildcards.pop();
@@ -140,6 +188,11 @@ var PathTrie = class {
140
188
  wildcard.active = true;
141
189
  cur = wildcard == null ? void 0 : wildcard.startTrie;
142
190
  wildcards.push(wildcard);
191
+ if (i === segments.length - 1 && wildcards.length && wildcards[wildcards.length - 1].active && wildcards[wildcards.length - 1].startTrie[dataProp]) {
192
+ wildcards[wildcards.length - 1].startTrie[dataProp].forEach(
193
+ (d) => ret.push({ data: d, variables: getVariables() })
194
+ );
195
+ }
143
196
  return wildcard == null ? void 0 : wildcard.start;
144
197
  };
145
198
  for (let i = 0; i < segments.length; i++) {
@@ -168,21 +221,22 @@ var PathTrie = class {
168
221
  if (i === segments.length - 1) {
169
222
  if (cur[dataProp]) {
170
223
  cur[dataProp].forEach((d) => ret.push({ data: d, variables: getVariables() }));
224
+ } else {
225
+ const more = scanWildcards(i);
226
+ if (typeof more === "undefined" || ret.length > 0 && bestMatch)
227
+ return [...ret, ...splats];
228
+ i = more;
171
229
  }
172
230
  }
173
- } else if (i === segments.length - 1) {
174
- const more = scanWildcards();
175
- if (typeof more === "undefined")
231
+ } else {
232
+ const more = scanWildcards(i);
233
+ if (typeof more === "undefined" || ret.length > 0 && bestMatch)
176
234
  return [...ret, ...splats];
177
235
  i = more;
178
- if (i === segments.length - 1 && wildcards.length && wildcards[wildcards.length - 1].active && wildcards[wildcards.length - 1].startTrie[dataProp]) {
179
- wildcards[wildcards.length - 1].startTrie[dataProp].forEach(
180
- (d) => ret.push({ data: d, variables: getVariables() })
181
- );
182
- }
183
- } else {
184
- const more = scanWildcards();
185
- if (typeof more === "undefined")
236
+ }
237
+ if (i === segments.length - 1 && wildcards.length !== 0) {
238
+ const more = scanWildcards(i);
239
+ if (typeof more === "undefined" || ret.length > 0 && bestMatch)
186
240
  return [...ret, ...splats];
187
241
  i = more;
188
242
  }
@@ -342,22 +396,29 @@ var _RedirectClient = class extends ApiClient {
342
396
  }
343
397
  }
344
398
  async assembleTrie() {
345
- const trie = new PathTrie();
346
- for await (const redirect of this.getAllRedirects()) {
347
- const source = processUrl(redirect.redirect.sourceUrl);
348
- const target = processUrl(redirect.redirect.targetUrl);
349
- trie.insert(source.path, {
350
- redirect: redirect.redirect,
351
- metadata: redirect.metadata,
352
- reverse: false
353
- });
354
- trie.insert(target.path, {
355
- redirect: redirect.redirect,
356
- metadata: redirect.metadata,
357
- reverse: true
358
- });
399
+ if (!_RedirectClient.assembling) {
400
+ _RedirectClient.assembling = true;
401
+ _RedirectClient.assemblingPromise = (async () => {
402
+ const trie = new PathTrie();
403
+ for await (const redirect of this.getAllRedirects()) {
404
+ const source = processUrl(redirect.redirect.sourceUrl);
405
+ const target = processUrl(redirect.redirect.targetUrl);
406
+ trie.insert(source.path, {
407
+ redirect: redirect.redirect,
408
+ metadata: redirect.metadata,
409
+ reverse: false
410
+ });
411
+ trie.insert(target.path, {
412
+ redirect: redirect.redirect,
413
+ metadata: redirect.metadata,
414
+ reverse: true
415
+ });
416
+ }
417
+ _RedirectClient.assembling = false;
418
+ return trie;
419
+ })();
359
420
  }
360
- return trie;
421
+ return await _RedirectClient.assemblingPromise;
361
422
  }
362
423
  static processHops(url, trie, bestMatch, options) {
363
424
  var _a;
@@ -429,20 +490,14 @@ var _RedirectClient = class extends ApiClient {
429
490
  * @param options - Different options available to the redirect engine
430
491
  */
431
492
  static processDefinitionToResults(processedUrl, definition, variables, options) {
432
- var _a, _b, _c, _d, _e;
433
493
  const resultUrl = (options == null ? void 0 : options.reverse) ? definition.redirect.sourceUrl : definition.redirect.targetUrl;
434
494
  const processedResult = processUrl(resultUrl);
435
495
  const redirect = definition == null ? void 0 : definition.redirect;
436
496
  if (redirect.sourceMustMatchDomain && processedUrl.domain !== processedResult.domain)
437
497
  return void 0;
438
- const protocol = redirect.targetPreserveIncomingProtocol ? processedUrl.protocol : (_b = (_a = processedResult.protocol) != null ? _a : processedUrl.protocol) != null ? _b : "";
439
- const domain = redirect.targetPreserveIncomingDomain ? processedUrl.domain : (_d = (_c = processedResult.domain) != null ? _c : processedUrl.domain) != null ? _d : "";
440
- const queryString = redirect.sourceRetainQuerystring && processedResult.query ? (_e = processedResult.query) != null ? _e : "" : definition.redirect.sourceRetainQuerystring ? processedUrl.query : "";
441
- const finalUrl = `${protocol}${domain}${processedResult.port}${processedResult.path}${queryString}`;
498
+ const finalUrl = _RedirectClient.getTargetVariableExpandedUrl(processedUrl.url, redirect);
442
499
  return {
443
- url: variables.reduce((cur, o) => {
444
- return cur.replace(o.key, o.value);
445
- }, finalUrl),
500
+ url: finalUrl,
446
501
  definition,
447
502
  label: (options == null ? void 0 : options.label) ? variables.reduce((cur, o) => {
448
503
  return cur.replace(o.key, `<em>${o.value}</em>`);
@@ -477,7 +532,7 @@ var _RedirectClient = class extends ApiClient {
477
532
  }
478
533
  const protocol = redirectDefinition.targetPreserveIncomingProtocol ? processedUrl.protocol : processedTarget.protocol;
479
534
  const domain = redirectDefinition.targetPreserveIncomingDomain ? processedUrl.domain : processedTarget.domain;
480
- const port = processedTarget.port;
535
+ const port = redirectDefinition.targetPreserveIncomingDomain ? processedUrl.port : processedTarget.port;
481
536
  const query = redirectDefinition.sourceRetainQuerystring && redirectDefinition.targetMergeQuerystring ? this.mergeQueryStrings(processedUrl.query, processedTarget.query) : !redirectDefinition.targetMergeQuerystring && redirectDefinition.sourceRetainQuerystring ? processedUrl.query : processedTarget.query;
482
537
  const fragment = redirectDefinition.sourceRetainQuerystring && redirectDefinition.targetMergeQuerystring ? this.mergeQueryStrings(processedUrl.fragment, processedTarget.fragment) : !redirectDefinition.targetMergeQuerystring && redirectDefinition.sourceRetainQuerystring ? processedUrl.fragment : processedTarget.fragment;
483
538
  return `${protocol}${domain}${port}${finalUrlPath}${query}${fragment}`;
@@ -499,15 +554,15 @@ var _RedirectClient = class extends ApiClient {
499
554
  return (fragment ? "#" : "?") + merged;
500
555
  return "";
501
556
  }
502
- static getSourceVariables(path, source, isVariable = (pathSegment) => pathSegment.startsWith(":")) {
557
+ static getSourceVariables(path, source, isVariable = (pathSegment, isLast) => pathSegment.startsWith(":") || pathSegment === "*" && isLast) {
503
558
  const variables = {};
504
559
  const pathSegments = path.split("/");
505
560
  const sourceSegments = source.split("/");
506
- if (pathSegments.length !== sourceSegments.length) {
561
+ if (pathSegments.length !== sourceSegments.length && !source.endsWith("/*")) {
507
562
  throw new Error("Path and source have different numbers of path segments, must be the same");
508
563
  }
509
564
  sourceSegments.forEach((sourceSegment, i) => {
510
- if (isVariable(sourceSegment)) {
565
+ if (isVariable(sourceSegment, i === sourceSegments.length - 1)) {
511
566
  variables[sourceSegment] = pathSegments[i];
512
567
  }
513
568
  });
@@ -519,6 +574,10 @@ RedirectClient.processUrlBestMatch = async (url, trie, options) => {
519
574
  var _a;
520
575
  return (_a = _RedirectClient.processHops(url, trie, true, options)) == null ? void 0 : _a[0];
521
576
  };
577
+ RedirectClient.assembling = false;
578
+ RedirectClient.assemblingPromise = new Promise(
579
+ () => new PathTrie()
580
+ );
522
581
  var UncachedRedirectClient = class extends RedirectClient {
523
582
  constructor(options) {
524
583
  super({ ...options, bypassCache: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniformdev/redirect",
3
- "version": "19.27.0",
3
+ "version": "19.29.0",
4
4
  "description": "Uniform redirect client",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "main": "./dist/index.js",
@@ -32,12 +32,12 @@
32
32
  "/dist"
33
33
  ],
34
34
  "dependencies": {
35
- "@uniformdev/context": "19.27.0",
35
+ "@uniformdev/context": "19.29.0",
36
36
  "p-limit": "^3.1.0",
37
37
  "rfdc": "^1.3.0"
38
38
  },
39
39
  "publishConfig": {
40
40
  "access": "public"
41
41
  },
42
- "gitHead": "8041631f93aca51a236597c1aea606c6dc8732e8"
42
+ "gitHead": "f8e9d5f6283fb3d72ba095c0e307907dc591ce4a"
43
43
  }