@uniformdev/redirect 19.27.1-alpha.3 → 19.29.1-alpha.19

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
@@ -86,6 +86,7 @@ declare class RedirectClient extends ApiClient<RedirectClientOptions> {
86
86
  updatedBy?: string | undefined;
87
87
  createdAt?: string | undefined;
88
88
  createdBy?: string | undefined;
89
+ createdByName?: string | undefined;
89
90
  };
90
91
  total: number | undefined;
91
92
  }, void, unknown>;
@@ -98,6 +99,8 @@ declare class RedirectClient extends ApiClient<RedirectClientOptions> {
98
99
  static processUrlBestMatch: (url: string, trie: PathTrie<DirectionAwareRedirectDefinition>, options?: RedirectOptions) => Promise<RedirectResult | undefined>;
99
100
  processUrlBestMatch: (url: string, options?: RedirectOptions) => Promise<RedirectResult | undefined>;
100
101
  processUrlAllMatches: (url: string, options?: RedirectOptions) => Promise<RedirectResult[]>;
102
+ private static assembling;
103
+ private static assemblingPromise;
101
104
  private assembleTrie;
102
105
  private static processHops;
103
106
  private static processHop;
@@ -268,8 +271,10 @@ interface components {
268
271
  updatedBy?: string;
269
272
  /** @description The time the redirect was created in UTC */
270
273
  createdAt?: string;
271
- /** @description The user who created this redirect or system */
274
+ /** @description The user who created this redirect */
272
275
  createdBy?: string;
276
+ /** @description The user who created this redirect's name */
277
+ createdByName?: string;
273
278
  };
274
279
  Redirect: {
275
280
  /**
@@ -391,6 +396,7 @@ type RedirectClientGetRedirect = Pick<RedirectClientGetRequest, 'id' | 'projectM
391
396
  type RedirectClientGetRedirects = Pick<RedirectClientGetRequest, 'ids' | 'limit' | 'offset' | 'orderBy' | 'search'>;
392
397
 
393
398
  declare class WithMemoryCache extends RedirectClientCache<RedirectClientCacheOptions> {
399
+ private static refresher?;
394
400
  constructor(options: RedirectClientCacheOptions);
395
401
  private static trieCache;
396
402
  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) {
@@ -8,14 +46,22 @@ var RedirectClientCache = class {
8
46
  };
9
47
 
10
48
  // src/cache/withMemoryCache.ts
11
- var _WithMemoryCache = class extends RedirectClientCache {
49
+ var _WithMemoryCache = class _WithMemoryCache 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,18 +85,23 @@ 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
- _WithMemoryCache.trieCache[key] = { ..._WithMemoryCache.trieCache[key], data: p };
92
+ caches.push(
93
+ p.then(() => {
94
+ _WithMemoryCache.trieCache[key] = { ..._WithMemoryCache.trieCache[key], data: p };
95
+ })
96
+ );
46
97
  }
47
98
  }
48
- return Promise.all([]);
99
+ return Promise.all(caches);
49
100
  }
50
101
  };
51
- var WithMemoryCache = _WithMemoryCache;
52
102
  /* Memory static class level variable to store data across multiple instances of the redirect client */
53
- WithMemoryCache.trieCache = {};
103
+ _WithMemoryCache.trieCache = {};
104
+ var WithMemoryCache = _WithMemoryCache;
54
105
 
55
106
  // src/data/pathTrie.ts
56
107
  import rfdc from "rfdc";
@@ -140,6 +191,11 @@ var PathTrie = class {
140
191
  wildcard.active = true;
141
192
  cur = wildcard == null ? void 0 : wildcard.startTrie;
142
193
  wildcards.push(wildcard);
194
+ if (wildcards.length && wildcards[wildcards.length - 1].start === segments.length - 1 && wildcards[wildcards.length - 1].active && wildcards[wildcards.length - 1].startTrie[dataProp]) {
195
+ wildcards[wildcards.length - 1].startTrie[dataProp].forEach(
196
+ (d) => ret.push({ data: d, variables: getVariables() })
197
+ );
198
+ }
143
199
  return wildcard == null ? void 0 : wildcard.start;
144
200
  };
145
201
  for (let i = 0; i < segments.length; i++) {
@@ -168,21 +224,25 @@ var PathTrie = class {
168
224
  if (i === segments.length - 1) {
169
225
  if (cur[dataProp]) {
170
226
  cur[dataProp].forEach((d) => ret.push({ data: d, variables: getVariables() }));
227
+ } else {
228
+ const more = scanWildcards();
229
+ if (typeof more === "undefined" || ret.length > 0 && bestMatch)
230
+ return [...ret, ...splats];
231
+ i = more;
171
232
  }
172
233
  }
173
- } else if (i === segments.length - 1) {
234
+ } else {
174
235
  const more = scanWildcards();
175
- if (typeof more === "undefined")
236
+ if (typeof more === "undefined" || ret.length > 0 && bestMatch)
176
237
  return [...ret, ...splats];
177
238
  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 {
239
+ if (ret.length > 0 && bestMatch)
240
+ return [...ret, ...splats];
241
+ continue;
242
+ }
243
+ if (i === segments.length - 1 && wildcards.length !== 0) {
184
244
  const more = scanWildcards();
185
- if (typeof more === "undefined")
245
+ if (typeof more === "undefined" || ret.length > 0 && bestMatch)
186
246
  return [...ret, ...splats];
187
247
  i = more;
188
248
  }
@@ -214,7 +274,7 @@ function processUrl(url) {
214
274
  }
215
275
 
216
276
  // src/redirectClient.ts
217
- var _RedirectClient = class extends ApiClient {
277
+ var _RedirectClient = class _RedirectClient extends ApiClient {
218
278
  constructor(options) {
219
279
  super(options);
220
280
  this.getRedirect = async (options) => {
@@ -342,22 +402,29 @@ var _RedirectClient = class extends ApiClient {
342
402
  }
343
403
  }
344
404
  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
- });
405
+ if (!_RedirectClient.assembling) {
406
+ _RedirectClient.assembling = true;
407
+ _RedirectClient.assemblingPromise = (async () => {
408
+ const trie = new PathTrie();
409
+ for await (const redirect of this.getAllRedirects()) {
410
+ const source = processUrl(redirect.redirect.sourceUrl);
411
+ const target = processUrl(redirect.redirect.targetUrl);
412
+ trie.insert(source.path, {
413
+ redirect: redirect.redirect,
414
+ metadata: redirect.metadata,
415
+ reverse: false
416
+ });
417
+ trie.insert(target.path, {
418
+ redirect: redirect.redirect,
419
+ metadata: redirect.metadata,
420
+ reverse: true
421
+ });
422
+ }
423
+ _RedirectClient.assembling = false;
424
+ return trie;
425
+ })();
359
426
  }
360
- return trie;
427
+ return await _RedirectClient.assemblingPromise;
361
428
  }
362
429
  static processHops(url, trie, bestMatch, options) {
363
430
  var _a;
@@ -429,20 +496,14 @@ var _RedirectClient = class extends ApiClient {
429
496
  * @param options - Different options available to the redirect engine
430
497
  */
431
498
  static processDefinitionToResults(processedUrl, definition, variables, options) {
432
- var _a, _b, _c, _d, _e;
433
499
  const resultUrl = (options == null ? void 0 : options.reverse) ? definition.redirect.sourceUrl : definition.redirect.targetUrl;
434
500
  const processedResult = processUrl(resultUrl);
435
501
  const redirect = definition == null ? void 0 : definition.redirect;
436
502
  if (redirect.sourceMustMatchDomain && processedUrl.domain !== processedResult.domain)
437
503
  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}`;
504
+ const finalUrl = _RedirectClient.getTargetVariableExpandedUrl(processedUrl.url, redirect);
442
505
  return {
443
- url: variables.reduce((cur, o) => {
444
- return cur.replace(o.key, o.value);
445
- }, finalUrl),
506
+ url: finalUrl,
446
507
  definition,
447
508
  label: (options == null ? void 0 : options.label) ? variables.reduce((cur, o) => {
448
509
  return cur.replace(o.key, `<em>${o.value}</em>`);
@@ -477,7 +538,7 @@ var _RedirectClient = class extends ApiClient {
477
538
  }
478
539
  const protocol = redirectDefinition.targetPreserveIncomingProtocol ? processedUrl.protocol : processedTarget.protocol;
479
540
  const domain = redirectDefinition.targetPreserveIncomingDomain ? processedUrl.domain : processedTarget.domain;
480
- const port = processedTarget.port;
541
+ const port = redirectDefinition.targetPreserveIncomingDomain ? processedUrl.port : processedTarget.port;
481
542
  const query = redirectDefinition.sourceRetainQuerystring && redirectDefinition.targetMergeQuerystring ? this.mergeQueryStrings(processedUrl.query, processedTarget.query) : !redirectDefinition.targetMergeQuerystring && redirectDefinition.sourceRetainQuerystring ? processedUrl.query : processedTarget.query;
482
543
  const fragment = redirectDefinition.sourceRetainQuerystring && redirectDefinition.targetMergeQuerystring ? this.mergeQueryStrings(processedUrl.fragment, processedTarget.fragment) : !redirectDefinition.targetMergeQuerystring && redirectDefinition.sourceRetainQuerystring ? processedUrl.fragment : processedTarget.fragment;
483
544
  return `${protocol}${domain}${port}${finalUrlPath}${query}${fragment}`;
@@ -499,26 +560,30 @@ var _RedirectClient = class extends ApiClient {
499
560
  return (fragment ? "#" : "?") + merged;
500
561
  return "";
501
562
  }
502
- static getSourceVariables(path, source, isVariable = (pathSegment) => pathSegment.startsWith(":")) {
563
+ static getSourceVariables(path, source, isVariable = (pathSegment, isLast) => pathSegment.startsWith(":") || pathSegment === "*" && isLast) {
503
564
  const variables = {};
504
565
  const pathSegments = path.split("/");
505
566
  const sourceSegments = source.split("/");
506
- if (pathSegments.length !== sourceSegments.length) {
567
+ if (pathSegments.length !== sourceSegments.length && !source.endsWith("/*")) {
507
568
  throw new Error("Path and source have different numbers of path segments, must be the same");
508
569
  }
509
570
  sourceSegments.forEach((sourceSegment, i) => {
510
- if (isVariable(sourceSegment)) {
571
+ if (isVariable(sourceSegment, i === sourceSegments.length - 1)) {
511
572
  variables[sourceSegment] = pathSegments[i];
512
573
  }
513
574
  });
514
575
  return variables;
515
576
  }
516
577
  };
517
- var RedirectClient = _RedirectClient;
518
- RedirectClient.processUrlBestMatch = async (url, trie, options) => {
578
+ _RedirectClient.processUrlBestMatch = async (url, trie, options) => {
519
579
  var _a;
520
580
  return (_a = _RedirectClient.processHops(url, trie, true, options)) == null ? void 0 : _a[0];
521
581
  };
582
+ _RedirectClient.assembling = false;
583
+ _RedirectClient.assemblingPromise = new Promise(
584
+ () => new PathTrie()
585
+ );
586
+ var RedirectClient = _RedirectClient;
522
587
  var UncachedRedirectClient = class extends RedirectClient {
523
588
  constructor(options) {
524
589
  super({ ...options, bypassCache: true });
package/dist/index.js CHANGED
@@ -103,6 +103,7 @@ var require_package = __commonJS({
103
103
  // ../../node_modules/.pnpm/dotenv@16.1.3/node_modules/dotenv/lib/main.js
104
104
  var require_main = __commonJS({
105
105
  "../../node_modules/.pnpm/dotenv@16.1.3/node_modules/dotenv/lib/main.js"(exports, module2) {
106
+ "use strict";
106
107
  var fs = require("fs");
107
108
  var path = require("path");
108
109
  var os = require("os");
@@ -326,6 +327,44 @@ __export(src_exports, {
326
327
  });
327
328
  module.exports = __toCommonJS(src_exports);
328
329
 
330
+ // src/cache/data/refresher.ts
331
+ var Refresher = class {
332
+ /**
333
+ * Create new refresher
334
+ * @param refreshRate - How often the refresher will refresh. Cannot be less than 20000 ms or 20 seconds
335
+ * @param refreshDuration - How many times it should refresh before sleeps until something accesses the cache. Cannot be more than 5
336
+ * @param refresh - Refresh method to run at the refresh rate intervals
337
+ */
338
+ constructor({ refreshRate, refreshDuration, refresh }) {
339
+ this.refreshCounter = 0;
340
+ this.finalRate = refreshRate >= 2e4 ? refreshRate : 2e4;
341
+ this.finalDuration = refreshDuration <= 5 ? refreshDuration : 5;
342
+ this.refreshCounter = 1;
343
+ this.refresh = refresh;
344
+ this.start();
345
+ }
346
+ /**
347
+ * Start async updating process again, will run for the same duration as the original. refreshRate * refreshDuration milliseconds
348
+ */
349
+ kick() {
350
+ if (this.refreshCounter === 0) {
351
+ this.start();
352
+ }
353
+ this.refreshCounter = 1;
354
+ }
355
+ start() {
356
+ this.interval = setTimeout(async () => {
357
+ if (this.refreshCounter > this.finalDuration) {
358
+ this.refreshCounter = 0;
359
+ } else {
360
+ await this.refresh();
361
+ this.refreshCounter++;
362
+ this.start();
363
+ }
364
+ }, this.finalRate);
365
+ }
366
+ };
367
+
329
368
  // src/cache/redirectClientCache.ts
330
369
  var RedirectClientCache = class {
331
370
  constructor(options) {
@@ -334,14 +373,22 @@ var RedirectClientCache = class {
334
373
  };
335
374
 
336
375
  // src/cache/withMemoryCache.ts
337
- var _WithMemoryCache = class extends RedirectClientCache {
376
+ var _WithMemoryCache = class _WithMemoryCache extends RedirectClientCache {
338
377
  constructor(options) {
339
378
  super(options);
379
+ if (options.refreshRate && !_WithMemoryCache.refresher) {
380
+ _WithMemoryCache.refresher = new Refresher({
381
+ refreshRate: options.refreshRate,
382
+ refreshDuration: 5,
383
+ refresh: this.refresh
384
+ });
385
+ }
340
386
  }
341
387
  /* Get data from the cache and debounce pausing refresh */
342
388
  get(key) {
343
- var _a, _b;
344
- return (_b = (_a = _WithMemoryCache.trieCache[key]) == null ? void 0 : _a.data) != null ? _b : void 0;
389
+ var _a, _b, _c;
390
+ (_a = _WithMemoryCache.refresher) == null ? void 0 : _a.kick();
391
+ return (_c = (_b = _WithMemoryCache.trieCache[key]) == null ? void 0 : _b.data) != null ? _c : void 0;
345
392
  }
346
393
  /* Set new data to the cache and reset the refresh method */
347
394
  set(key, data, refresh) {
@@ -365,18 +412,23 @@ var _WithMemoryCache = class extends RedirectClientCache {
365
412
  }
366
413
  refresh() {
367
414
  var _a, _b;
415
+ const caches = [];
368
416
  for (const key in _WithMemoryCache.trieCache) {
369
417
  const p = (_b = (_a = _WithMemoryCache.trieCache[key]) == null ? void 0 : _a.refresh) == null ? void 0 : _b.call(_a);
370
418
  if (p) {
371
- _WithMemoryCache.trieCache[key] = { ..._WithMemoryCache.trieCache[key], data: p };
419
+ caches.push(
420
+ p.then(() => {
421
+ _WithMemoryCache.trieCache[key] = { ..._WithMemoryCache.trieCache[key], data: p };
422
+ })
423
+ );
372
424
  }
373
425
  }
374
- return Promise.all([]);
426
+ return Promise.all(caches);
375
427
  }
376
428
  };
377
- var WithMemoryCache = _WithMemoryCache;
378
429
  /* Memory static class level variable to store data across multiple instances of the redirect client */
379
- WithMemoryCache.trieCache = {};
430
+ _WithMemoryCache.trieCache = {};
431
+ var WithMemoryCache = _WithMemoryCache;
380
432
 
381
433
  // src/data/pathTrie.ts
382
434
  var import_rfdc = __toESM(require("rfdc"));
@@ -466,6 +518,11 @@ var PathTrie = class {
466
518
  wildcard.active = true;
467
519
  cur = wildcard == null ? void 0 : wildcard.startTrie;
468
520
  wildcards.push(wildcard);
521
+ if (wildcards.length && wildcards[wildcards.length - 1].start === segments.length - 1 && wildcards[wildcards.length - 1].active && wildcards[wildcards.length - 1].startTrie[dataProp]) {
522
+ wildcards[wildcards.length - 1].startTrie[dataProp].forEach(
523
+ (d) => ret.push({ data: d, variables: getVariables() })
524
+ );
525
+ }
469
526
  return wildcard == null ? void 0 : wildcard.start;
470
527
  };
471
528
  for (let i = 0; i < segments.length; i++) {
@@ -494,21 +551,25 @@ var PathTrie = class {
494
551
  if (i === segments.length - 1) {
495
552
  if (cur[dataProp]) {
496
553
  cur[dataProp].forEach((d) => ret.push({ data: d, variables: getVariables() }));
554
+ } else {
555
+ const more = scanWildcards();
556
+ if (typeof more === "undefined" || ret.length > 0 && bestMatch)
557
+ return [...ret, ...splats];
558
+ i = more;
497
559
  }
498
560
  }
499
- } else if (i === segments.length - 1) {
561
+ } else {
500
562
  const more = scanWildcards();
501
- if (typeof more === "undefined")
563
+ if (typeof more === "undefined" || ret.length > 0 && bestMatch)
502
564
  return [...ret, ...splats];
503
565
  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 {
566
+ if (ret.length > 0 && bestMatch)
567
+ return [...ret, ...splats];
568
+ continue;
569
+ }
570
+ if (i === segments.length - 1 && wildcards.length !== 0) {
510
571
  const more = scanWildcards();
511
- if (typeof more === "undefined")
572
+ if (typeof more === "undefined" || ret.length > 0 && bestMatch)
512
573
  return [...ret, ...splats];
513
574
  i = more;
514
575
  }
@@ -540,7 +601,7 @@ function processUrl(url) {
540
601
  }
541
602
 
542
603
  // src/redirectClient.ts
543
- var _RedirectClient = class extends import_api.ApiClient {
604
+ var _RedirectClient = class _RedirectClient extends import_api.ApiClient {
544
605
  constructor(options) {
545
606
  super(options);
546
607
  this.getRedirect = async (options) => {
@@ -668,22 +729,29 @@ var _RedirectClient = class extends import_api.ApiClient {
668
729
  }
669
730
  }
670
731
  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
- });
732
+ if (!_RedirectClient.assembling) {
733
+ _RedirectClient.assembling = true;
734
+ _RedirectClient.assemblingPromise = (async () => {
735
+ const trie = new PathTrie();
736
+ for await (const redirect of this.getAllRedirects()) {
737
+ const source = processUrl(redirect.redirect.sourceUrl);
738
+ const target = processUrl(redirect.redirect.targetUrl);
739
+ trie.insert(source.path, {
740
+ redirect: redirect.redirect,
741
+ metadata: redirect.metadata,
742
+ reverse: false
743
+ });
744
+ trie.insert(target.path, {
745
+ redirect: redirect.redirect,
746
+ metadata: redirect.metadata,
747
+ reverse: true
748
+ });
749
+ }
750
+ _RedirectClient.assembling = false;
751
+ return trie;
752
+ })();
685
753
  }
686
- return trie;
754
+ return await _RedirectClient.assemblingPromise;
687
755
  }
688
756
  static processHops(url, trie, bestMatch, options) {
689
757
  var _a;
@@ -755,20 +823,14 @@ var _RedirectClient = class extends import_api.ApiClient {
755
823
  * @param options - Different options available to the redirect engine
756
824
  */
757
825
  static processDefinitionToResults(processedUrl, definition, variables, options) {
758
- var _a, _b, _c, _d, _e;
759
826
  const resultUrl = (options == null ? void 0 : options.reverse) ? definition.redirect.sourceUrl : definition.redirect.targetUrl;
760
827
  const processedResult = processUrl(resultUrl);
761
828
  const redirect = definition == null ? void 0 : definition.redirect;
762
829
  if (redirect.sourceMustMatchDomain && processedUrl.domain !== processedResult.domain)
763
830
  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}`;
831
+ const finalUrl = _RedirectClient.getTargetVariableExpandedUrl(processedUrl.url, redirect);
768
832
  return {
769
- url: variables.reduce((cur, o) => {
770
- return cur.replace(o.key, o.value);
771
- }, finalUrl),
833
+ url: finalUrl,
772
834
  definition,
773
835
  label: (options == null ? void 0 : options.label) ? variables.reduce((cur, o) => {
774
836
  return cur.replace(o.key, `<em>${o.value}</em>`);
@@ -803,7 +865,7 @@ var _RedirectClient = class extends import_api.ApiClient {
803
865
  }
804
866
  const protocol = redirectDefinition.targetPreserveIncomingProtocol ? processedUrl.protocol : processedTarget.protocol;
805
867
  const domain = redirectDefinition.targetPreserveIncomingDomain ? processedUrl.domain : processedTarget.domain;
806
- const port = processedTarget.port;
868
+ const port = redirectDefinition.targetPreserveIncomingDomain ? processedUrl.port : processedTarget.port;
807
869
  const query = redirectDefinition.sourceRetainQuerystring && redirectDefinition.targetMergeQuerystring ? this.mergeQueryStrings(processedUrl.query, processedTarget.query) : !redirectDefinition.targetMergeQuerystring && redirectDefinition.sourceRetainQuerystring ? processedUrl.query : processedTarget.query;
808
870
  const fragment = redirectDefinition.sourceRetainQuerystring && redirectDefinition.targetMergeQuerystring ? this.mergeQueryStrings(processedUrl.fragment, processedTarget.fragment) : !redirectDefinition.targetMergeQuerystring && redirectDefinition.sourceRetainQuerystring ? processedUrl.fragment : processedTarget.fragment;
809
871
  return `${protocol}${domain}${port}${finalUrlPath}${query}${fragment}`;
@@ -825,26 +887,30 @@ var _RedirectClient = class extends import_api.ApiClient {
825
887
  return (fragment ? "#" : "?") + merged;
826
888
  return "";
827
889
  }
828
- static getSourceVariables(path, source, isVariable = (pathSegment) => pathSegment.startsWith(":")) {
890
+ static getSourceVariables(path, source, isVariable = (pathSegment, isLast) => pathSegment.startsWith(":") || pathSegment === "*" && isLast) {
829
891
  const variables = {};
830
892
  const pathSegments = path.split("/");
831
893
  const sourceSegments = source.split("/");
832
- if (pathSegments.length !== sourceSegments.length) {
894
+ if (pathSegments.length !== sourceSegments.length && !source.endsWith("/*")) {
833
895
  throw new Error("Path and source have different numbers of path segments, must be the same");
834
896
  }
835
897
  sourceSegments.forEach((sourceSegment, i) => {
836
- if (isVariable(sourceSegment)) {
898
+ if (isVariable(sourceSegment, i === sourceSegments.length - 1)) {
837
899
  variables[sourceSegment] = pathSegments[i];
838
900
  }
839
901
  });
840
902
  return variables;
841
903
  }
842
904
  };
843
- var RedirectClient = _RedirectClient;
844
- RedirectClient.processUrlBestMatch = async (url, trie, options) => {
905
+ _RedirectClient.processUrlBestMatch = async (url, trie, options) => {
845
906
  var _a;
846
907
  return (_a = _RedirectClient.processHops(url, trie, true, options)) == null ? void 0 : _a[0];
847
908
  };
909
+ _RedirectClient.assembling = false;
910
+ _RedirectClient.assemblingPromise = new Promise(
911
+ () => new PathTrie()
912
+ );
913
+ var RedirectClient = _RedirectClient;
848
914
  var UncachedRedirectClient = class extends RedirectClient {
849
915
  constructor(options) {
850
916
  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) {
@@ -8,14 +46,22 @@ var RedirectClientCache = class {
8
46
  };
9
47
 
10
48
  // src/cache/withMemoryCache.ts
11
- var _WithMemoryCache = class extends RedirectClientCache {
49
+ var _WithMemoryCache = class _WithMemoryCache 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,18 +85,23 @@ 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
- _WithMemoryCache.trieCache[key] = { ..._WithMemoryCache.trieCache[key], data: p };
92
+ caches.push(
93
+ p.then(() => {
94
+ _WithMemoryCache.trieCache[key] = { ..._WithMemoryCache.trieCache[key], data: p };
95
+ })
96
+ );
46
97
  }
47
98
  }
48
- return Promise.all([]);
99
+ return Promise.all(caches);
49
100
  }
50
101
  };
51
- var WithMemoryCache = _WithMemoryCache;
52
102
  /* Memory static class level variable to store data across multiple instances of the redirect client */
53
- WithMemoryCache.trieCache = {};
103
+ _WithMemoryCache.trieCache = {};
104
+ var WithMemoryCache = _WithMemoryCache;
54
105
 
55
106
  // src/data/pathTrie.ts
56
107
  import rfdc from "rfdc";
@@ -140,6 +191,11 @@ var PathTrie = class {
140
191
  wildcard.active = true;
141
192
  cur = wildcard == null ? void 0 : wildcard.startTrie;
142
193
  wildcards.push(wildcard);
194
+ if (wildcards.length && wildcards[wildcards.length - 1].start === segments.length - 1 && wildcards[wildcards.length - 1].active && wildcards[wildcards.length - 1].startTrie[dataProp]) {
195
+ wildcards[wildcards.length - 1].startTrie[dataProp].forEach(
196
+ (d) => ret.push({ data: d, variables: getVariables() })
197
+ );
198
+ }
143
199
  return wildcard == null ? void 0 : wildcard.start;
144
200
  };
145
201
  for (let i = 0; i < segments.length; i++) {
@@ -168,21 +224,25 @@ var PathTrie = class {
168
224
  if (i === segments.length - 1) {
169
225
  if (cur[dataProp]) {
170
226
  cur[dataProp].forEach((d) => ret.push({ data: d, variables: getVariables() }));
227
+ } else {
228
+ const more = scanWildcards();
229
+ if (typeof more === "undefined" || ret.length > 0 && bestMatch)
230
+ return [...ret, ...splats];
231
+ i = more;
171
232
  }
172
233
  }
173
- } else if (i === segments.length - 1) {
234
+ } else {
174
235
  const more = scanWildcards();
175
- if (typeof more === "undefined")
236
+ if (typeof more === "undefined" || ret.length > 0 && bestMatch)
176
237
  return [...ret, ...splats];
177
238
  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 {
239
+ if (ret.length > 0 && bestMatch)
240
+ return [...ret, ...splats];
241
+ continue;
242
+ }
243
+ if (i === segments.length - 1 && wildcards.length !== 0) {
184
244
  const more = scanWildcards();
185
- if (typeof more === "undefined")
245
+ if (typeof more === "undefined" || ret.length > 0 && bestMatch)
186
246
  return [...ret, ...splats];
187
247
  i = more;
188
248
  }
@@ -214,7 +274,7 @@ function processUrl(url) {
214
274
  }
215
275
 
216
276
  // src/redirectClient.ts
217
- var _RedirectClient = class extends ApiClient {
277
+ var _RedirectClient = class _RedirectClient extends ApiClient {
218
278
  constructor(options) {
219
279
  super(options);
220
280
  this.getRedirect = async (options) => {
@@ -342,22 +402,29 @@ var _RedirectClient = class extends ApiClient {
342
402
  }
343
403
  }
344
404
  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
- });
405
+ if (!_RedirectClient.assembling) {
406
+ _RedirectClient.assembling = true;
407
+ _RedirectClient.assemblingPromise = (async () => {
408
+ const trie = new PathTrie();
409
+ for await (const redirect of this.getAllRedirects()) {
410
+ const source = processUrl(redirect.redirect.sourceUrl);
411
+ const target = processUrl(redirect.redirect.targetUrl);
412
+ trie.insert(source.path, {
413
+ redirect: redirect.redirect,
414
+ metadata: redirect.metadata,
415
+ reverse: false
416
+ });
417
+ trie.insert(target.path, {
418
+ redirect: redirect.redirect,
419
+ metadata: redirect.metadata,
420
+ reverse: true
421
+ });
422
+ }
423
+ _RedirectClient.assembling = false;
424
+ return trie;
425
+ })();
359
426
  }
360
- return trie;
427
+ return await _RedirectClient.assemblingPromise;
361
428
  }
362
429
  static processHops(url, trie, bestMatch, options) {
363
430
  var _a;
@@ -429,20 +496,14 @@ var _RedirectClient = class extends ApiClient {
429
496
  * @param options - Different options available to the redirect engine
430
497
  */
431
498
  static processDefinitionToResults(processedUrl, definition, variables, options) {
432
- var _a, _b, _c, _d, _e;
433
499
  const resultUrl = (options == null ? void 0 : options.reverse) ? definition.redirect.sourceUrl : definition.redirect.targetUrl;
434
500
  const processedResult = processUrl(resultUrl);
435
501
  const redirect = definition == null ? void 0 : definition.redirect;
436
502
  if (redirect.sourceMustMatchDomain && processedUrl.domain !== processedResult.domain)
437
503
  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}`;
504
+ const finalUrl = _RedirectClient.getTargetVariableExpandedUrl(processedUrl.url, redirect);
442
505
  return {
443
- url: variables.reduce((cur, o) => {
444
- return cur.replace(o.key, o.value);
445
- }, finalUrl),
506
+ url: finalUrl,
446
507
  definition,
447
508
  label: (options == null ? void 0 : options.label) ? variables.reduce((cur, o) => {
448
509
  return cur.replace(o.key, `<em>${o.value}</em>`);
@@ -477,7 +538,7 @@ var _RedirectClient = class extends ApiClient {
477
538
  }
478
539
  const protocol = redirectDefinition.targetPreserveIncomingProtocol ? processedUrl.protocol : processedTarget.protocol;
479
540
  const domain = redirectDefinition.targetPreserveIncomingDomain ? processedUrl.domain : processedTarget.domain;
480
- const port = processedTarget.port;
541
+ const port = redirectDefinition.targetPreserveIncomingDomain ? processedUrl.port : processedTarget.port;
481
542
  const query = redirectDefinition.sourceRetainQuerystring && redirectDefinition.targetMergeQuerystring ? this.mergeQueryStrings(processedUrl.query, processedTarget.query) : !redirectDefinition.targetMergeQuerystring && redirectDefinition.sourceRetainQuerystring ? processedUrl.query : processedTarget.query;
482
543
  const fragment = redirectDefinition.sourceRetainQuerystring && redirectDefinition.targetMergeQuerystring ? this.mergeQueryStrings(processedUrl.fragment, processedTarget.fragment) : !redirectDefinition.targetMergeQuerystring && redirectDefinition.sourceRetainQuerystring ? processedUrl.fragment : processedTarget.fragment;
483
544
  return `${protocol}${domain}${port}${finalUrlPath}${query}${fragment}`;
@@ -499,26 +560,30 @@ var _RedirectClient = class extends ApiClient {
499
560
  return (fragment ? "#" : "?") + merged;
500
561
  return "";
501
562
  }
502
- static getSourceVariables(path, source, isVariable = (pathSegment) => pathSegment.startsWith(":")) {
563
+ static getSourceVariables(path, source, isVariable = (pathSegment, isLast) => pathSegment.startsWith(":") || pathSegment === "*" && isLast) {
503
564
  const variables = {};
504
565
  const pathSegments = path.split("/");
505
566
  const sourceSegments = source.split("/");
506
- if (pathSegments.length !== sourceSegments.length) {
567
+ if (pathSegments.length !== sourceSegments.length && !source.endsWith("/*")) {
507
568
  throw new Error("Path and source have different numbers of path segments, must be the same");
508
569
  }
509
570
  sourceSegments.forEach((sourceSegment, i) => {
510
- if (isVariable(sourceSegment)) {
571
+ if (isVariable(sourceSegment, i === sourceSegments.length - 1)) {
511
572
  variables[sourceSegment] = pathSegments[i];
512
573
  }
513
574
  });
514
575
  return variables;
515
576
  }
516
577
  };
517
- var RedirectClient = _RedirectClient;
518
- RedirectClient.processUrlBestMatch = async (url, trie, options) => {
578
+ _RedirectClient.processUrlBestMatch = async (url, trie, options) => {
519
579
  var _a;
520
580
  return (_a = _RedirectClient.processHops(url, trie, true, options)) == null ? void 0 : _a[0];
521
581
  };
582
+ _RedirectClient.assembling = false;
583
+ _RedirectClient.assemblingPromise = new Promise(
584
+ () => new PathTrie()
585
+ );
586
+ var RedirectClient = _RedirectClient;
522
587
  var UncachedRedirectClient = class extends RedirectClient {
523
588
  constructor(options) {
524
589
  super({ ...options, bypassCache: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniformdev/redirect",
3
- "version": "19.27.1-alpha.3+9f5adae97",
3
+ "version": "19.29.1-alpha.19+838467072",
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.1-alpha.3+9f5adae97",
35
+ "@uniformdev/context": "19.29.1-alpha.19+838467072",
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": "9f5adae974295b306b539dea3657d728fed7f527"
42
+ "gitHead": "8384670725623d7fab7ef04837c79491e7e45442"
43
43
  }