@uniformdev/redirect 19.1.1-alpha.1
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/LICENSE.txt +2 -0
- package/README.md +3 -0
- package/dist/index.d.ts +364 -0
- package/dist/index.esm.js +431 -0
- package/dist/index.js +462 -0
- package/dist/index.mjs +431 -0
- package/package.json +46 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
// src/cache/redirectClientCache.ts
|
|
2
|
+
var RedirectClientCache = class {
|
|
3
|
+
constructor(options) {
|
|
4
|
+
this.options = options;
|
|
5
|
+
}
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// src/cache/withMemoryCache.ts
|
|
9
|
+
var _WithMemoryCache = class extends RedirectClientCache {
|
|
10
|
+
constructor(options) {
|
|
11
|
+
super(options);
|
|
12
|
+
}
|
|
13
|
+
/* Get data from the cache and debounce pausing refresh */
|
|
14
|
+
get(key) {
|
|
15
|
+
var _a, _b;
|
|
16
|
+
return (_b = (_a = _WithMemoryCache.trieCache[key]) == null ? void 0 : _a.data) != null ? _b : void 0;
|
|
17
|
+
}
|
|
18
|
+
/* Set new data to the cache and reset the refresh method */
|
|
19
|
+
set(key, data, refresh) {
|
|
20
|
+
var _a;
|
|
21
|
+
if (!data)
|
|
22
|
+
return;
|
|
23
|
+
const setCache = () => {
|
|
24
|
+
_WithMemoryCache.trieCache[key] = {
|
|
25
|
+
..._WithMemoryCache.trieCache[key],
|
|
26
|
+
data,
|
|
27
|
+
refresh
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
if (!((_a = _WithMemoryCache.trieCache[key]) == null ? void 0 : _a.data)) {
|
|
31
|
+
setCache();
|
|
32
|
+
} else {
|
|
33
|
+
data.then(() => {
|
|
34
|
+
setCache();
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
refresh() {
|
|
39
|
+
var _a, _b;
|
|
40
|
+
for (const key in _WithMemoryCache.trieCache) {
|
|
41
|
+
const p = (_b = (_a = _WithMemoryCache.trieCache[key]) == null ? void 0 : _a.refresh) == null ? void 0 : _b.call(_a);
|
|
42
|
+
if (p) {
|
|
43
|
+
_WithMemoryCache.trieCache[key] = { ..._WithMemoryCache.trieCache[key], data: p };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return Promise.all([]);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var WithMemoryCache = _WithMemoryCache;
|
|
50
|
+
/* Memory static class level variable to store data across multiple instances of the redirect client */
|
|
51
|
+
WithMemoryCache.trieCache = {};
|
|
52
|
+
|
|
53
|
+
// src/data/pathTrie.ts
|
|
54
|
+
if (!global.structuredClone) {
|
|
55
|
+
global.structuredClone = function structuredClone2(objectToClone) {
|
|
56
|
+
const stringified = JSON.stringify(objectToClone);
|
|
57
|
+
const parsed = JSON.parse(stringified);
|
|
58
|
+
return parsed;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
var dataProp = "~~data~~";
|
|
62
|
+
var PathTrie = class {
|
|
63
|
+
constructor(initialData) {
|
|
64
|
+
this.map = new PathTrieData();
|
|
65
|
+
if (initialData) {
|
|
66
|
+
if (Object.hasOwn(initialData, "map")) {
|
|
67
|
+
this.map = initialData.map;
|
|
68
|
+
} else {
|
|
69
|
+
this.map = initialData;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
convertManyInsert(data, key, value) {
|
|
74
|
+
data == null ? void 0 : data.forEach((d) => {
|
|
75
|
+
if (value) {
|
|
76
|
+
this.insert(key(d), value(d));
|
|
77
|
+
} else {
|
|
78
|
+
this.insert(key(d), d);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
insertMany(data, key) {
|
|
83
|
+
data.forEach((d) => {
|
|
84
|
+
this.insert(key(d), d);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
insert(path, data) {
|
|
88
|
+
let cur = this.map;
|
|
89
|
+
const segments = path.split("/");
|
|
90
|
+
for (let i = 0; i < segments.length; i++) {
|
|
91
|
+
const segment = segments[i];
|
|
92
|
+
if (i === 0 && segment === "")
|
|
93
|
+
continue;
|
|
94
|
+
if (!Object.hasOwn(cur, segment)) {
|
|
95
|
+
cur[segment] = new PathTrieData();
|
|
96
|
+
}
|
|
97
|
+
cur = cur[segment];
|
|
98
|
+
if (i == segments.length - 1) {
|
|
99
|
+
if (!cur[dataProp]) {
|
|
100
|
+
cur[dataProp] = [];
|
|
101
|
+
}
|
|
102
|
+
cur[dataProp].push(data);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
find(path, bestMatch = true) {
|
|
107
|
+
let cur = structuredClone(this.map);
|
|
108
|
+
const segments = path.split("/").filter((s) => s !== "");
|
|
109
|
+
const wildcards = [];
|
|
110
|
+
const ret = [];
|
|
111
|
+
const processed = /* @__PURE__ */ new Set();
|
|
112
|
+
const getVariables = () => {
|
|
113
|
+
return wildcards.map((wildcard) => {
|
|
114
|
+
if (wildcard.active)
|
|
115
|
+
return { key: wildcard.name, value: segments[wildcard.start] };
|
|
116
|
+
return void 0;
|
|
117
|
+
}).filter((wildcard) => Boolean(wildcard));
|
|
118
|
+
};
|
|
119
|
+
const getPropsStartingWithColon = (obj) => {
|
|
120
|
+
const result = [];
|
|
121
|
+
for (const prop in obj) {
|
|
122
|
+
if (prop.startsWith(":")) {
|
|
123
|
+
result.push(prop);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
127
|
+
};
|
|
128
|
+
const scanWildcards = () => {
|
|
129
|
+
let wildcard = void 0;
|
|
130
|
+
while ((!wildcard || wildcard.active) && wildcards.length) {
|
|
131
|
+
wildcard = wildcards.pop();
|
|
132
|
+
}
|
|
133
|
+
if (!wildcard || wildcard.active)
|
|
134
|
+
return void 0;
|
|
135
|
+
wildcard.active = true;
|
|
136
|
+
cur = wildcard == null ? void 0 : wildcard.startTrie;
|
|
137
|
+
wildcards.push(wildcard);
|
|
138
|
+
return wildcard == null ? void 0 : wildcard.start;
|
|
139
|
+
};
|
|
140
|
+
for (let i = 0; i < segments.length; i++) {
|
|
141
|
+
const segment = segments[i];
|
|
142
|
+
getPropsStartingWithColon(cur).forEach((wildcard) => {
|
|
143
|
+
if (!processed.has(wildcard)) {
|
|
144
|
+
wildcards.push({
|
|
145
|
+
startTrie: cur[wildcard],
|
|
146
|
+
start: i,
|
|
147
|
+
active: false,
|
|
148
|
+
name: wildcard
|
|
149
|
+
});
|
|
150
|
+
processed.add(wildcard);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
if (Object.hasOwn(cur, segment)) {
|
|
154
|
+
cur = cur[segment];
|
|
155
|
+
if (i === segments.length - 1) {
|
|
156
|
+
if (cur[dataProp]) {
|
|
157
|
+
cur[dataProp].forEach((d) => ret.push({ data: d, variables: getVariables() }));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
} else if (i === segments.length - 1) {
|
|
161
|
+
const more = scanWildcards();
|
|
162
|
+
if (typeof more === "undefined")
|
|
163
|
+
return ret;
|
|
164
|
+
i = more;
|
|
165
|
+
if (i === segments.length - 1 && wildcards.length && wildcards[wildcards.length - 1].active && wildcards[wildcards.length - 1].startTrie[dataProp]) {
|
|
166
|
+
wildcards[wildcards.length - 1].startTrie[dataProp].forEach(
|
|
167
|
+
(d) => ret.push({ data: d, variables: getVariables() })
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
} else {
|
|
171
|
+
const more = scanWildcards();
|
|
172
|
+
if (typeof more === "undefined")
|
|
173
|
+
return ret;
|
|
174
|
+
i = more;
|
|
175
|
+
}
|
|
176
|
+
if (ret.length > 0 && bestMatch)
|
|
177
|
+
return ret;
|
|
178
|
+
}
|
|
179
|
+
return ret;
|
|
180
|
+
}
|
|
181
|
+
processChar(char) {
|
|
182
|
+
return char;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
var PathTrieData = class {
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// src/redirectClient.ts
|
|
189
|
+
import { ApiClient } from "@uniformdev/context/api";
|
|
190
|
+
var _RedirectClient = class extends ApiClient {
|
|
191
|
+
constructor(options) {
|
|
192
|
+
super(options);
|
|
193
|
+
this.getRedirect = async (options) => {
|
|
194
|
+
var _a;
|
|
195
|
+
const { projectId } = this.options;
|
|
196
|
+
const fetchUri = this.createUrl("/api/v1/redirect", { ...options, projectId });
|
|
197
|
+
const results = await this.apiClient(fetchUri);
|
|
198
|
+
return (_a = results.redirects) == null ? void 0 : _a[0];
|
|
199
|
+
};
|
|
200
|
+
this.getRedirects = async (options) => {
|
|
201
|
+
const { projectId } = this.options;
|
|
202
|
+
const fetchUri = this.createUrl("/api/v1/redirect", { ...options, projectId });
|
|
203
|
+
const results = await this.apiClient(fetchUri);
|
|
204
|
+
return results;
|
|
205
|
+
};
|
|
206
|
+
this.getRedirectTrie = async (options) => {
|
|
207
|
+
var _a, _b;
|
|
208
|
+
const { projectId } = this.options;
|
|
209
|
+
const key = `${projectId}${(options == null ? void 0 : options.reverse) ? "r" : "f"}`;
|
|
210
|
+
const cachePromise = (options == null ? void 0 : options.bypassDataCache) ? void 0 : (_a = this.options.dataCache) == null ? void 0 : _a.get(key);
|
|
211
|
+
if (cachePromise) {
|
|
212
|
+
const result = await cachePromise;
|
|
213
|
+
if (result)
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
const ret = this.assembleTrie(projectId, options);
|
|
217
|
+
(_b = this.options.dataCache) == null ? void 0 : _b.set(key, ret, () => this.assembleTrie(projectId, options));
|
|
218
|
+
return ret;
|
|
219
|
+
};
|
|
220
|
+
this.resetRedirectTrieDataCache = async () => {
|
|
221
|
+
var _a;
|
|
222
|
+
await ((_a = this.options.dataCache) == null ? void 0 : _a.refresh());
|
|
223
|
+
};
|
|
224
|
+
this.upsertRedirect = async (redirect) => {
|
|
225
|
+
const {
|
|
226
|
+
id,
|
|
227
|
+
sourceUrl,
|
|
228
|
+
targetStatusCode,
|
|
229
|
+
targetUrl,
|
|
230
|
+
labelAsSystem,
|
|
231
|
+
projectMapId,
|
|
232
|
+
sourceMustMatchDomain,
|
|
233
|
+
sourceProjectMapNodeId,
|
|
234
|
+
sourceRetainQuerystring,
|
|
235
|
+
stopExecutingAfter,
|
|
236
|
+
targetMergeQuerystring,
|
|
237
|
+
targetPreserveIncomingDomain,
|
|
238
|
+
targetPreserveIncomingProtocol,
|
|
239
|
+
targetProjectMapNodeId
|
|
240
|
+
} = redirect;
|
|
241
|
+
const fetchUri = this.createUrl("/api/v1/redirect");
|
|
242
|
+
const result = await this.apiClient(fetchUri, {
|
|
243
|
+
method: "PUT",
|
|
244
|
+
body: JSON.stringify({
|
|
245
|
+
redirect: {
|
|
246
|
+
id,
|
|
247
|
+
sourceUrl,
|
|
248
|
+
targetStatusCode,
|
|
249
|
+
targetUrl,
|
|
250
|
+
labelAsSystem,
|
|
251
|
+
projectMapId,
|
|
252
|
+
sourceMustMatchDomain,
|
|
253
|
+
sourceProjectMapNodeId,
|
|
254
|
+
sourceRetainQuerystring,
|
|
255
|
+
stopExecutingAfter,
|
|
256
|
+
targetMergeQuerystring,
|
|
257
|
+
targetPreserveIncomingDomain,
|
|
258
|
+
targetPreserveIncomingProtocol,
|
|
259
|
+
targetProjectMapNodeId
|
|
260
|
+
},
|
|
261
|
+
projectId: this.options.projectId
|
|
262
|
+
})
|
|
263
|
+
});
|
|
264
|
+
this.resetRedirectTrieDataCache();
|
|
265
|
+
return result.id;
|
|
266
|
+
};
|
|
267
|
+
this.deleteRedirect = async (id) => {
|
|
268
|
+
const fetchUri = this.createUrl("/api/v1/redirect");
|
|
269
|
+
const result = await this.apiClient(fetchUri, {
|
|
270
|
+
method: "DELETE",
|
|
271
|
+
body: JSON.stringify({ id, projectId: this.options.projectId })
|
|
272
|
+
});
|
|
273
|
+
this.resetRedirectTrieDataCache();
|
|
274
|
+
return result.id;
|
|
275
|
+
};
|
|
276
|
+
this.processUrlBestMatch = async (url, options) => {
|
|
277
|
+
var _a;
|
|
278
|
+
const trie = await this.getRedirectTrie({ reverse: Boolean(options == null ? void 0 : options.reverse) });
|
|
279
|
+
return (_a = _RedirectClient.processHops(url, trie, true, options)) == null ? void 0 : _a[0];
|
|
280
|
+
};
|
|
281
|
+
this.processUrlAllMatches = async (url, options) => {
|
|
282
|
+
const trie = await this.getRedirectTrie({ reverse: Boolean(options == null ? void 0 : options.reverse) });
|
|
283
|
+
return _RedirectClient.processHops(url, trie, false, options);
|
|
284
|
+
};
|
|
285
|
+
if (options.dataCache && options.dataCache.options.prePopulate) {
|
|
286
|
+
if (!options.dataCache.get(`${options.projectId}f`)) {
|
|
287
|
+
options.dataCache.set(
|
|
288
|
+
`${options.projectId}f`,
|
|
289
|
+
this.getRedirectTrie(),
|
|
290
|
+
() => this.getRedirectTrie({ bypassDataCache: true })
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
async assembleTrie(projectId, options) {
|
|
296
|
+
var _a;
|
|
297
|
+
const trie = new PathTrie();
|
|
298
|
+
let offset = 0;
|
|
299
|
+
let total = 0;
|
|
300
|
+
do {
|
|
301
|
+
const fetchUri = this.createUrl("/api/v1/redirect", {
|
|
302
|
+
projectId,
|
|
303
|
+
limit: 50,
|
|
304
|
+
offset
|
|
305
|
+
});
|
|
306
|
+
const redirects = await this.apiClient(fetchUri);
|
|
307
|
+
trie.insertMany(
|
|
308
|
+
redirects.redirects,
|
|
309
|
+
(r) => (options == null ? void 0 : options.reverse) ? r.redirect.targetUrl : r.redirect.sourceUrl
|
|
310
|
+
);
|
|
311
|
+
total = (_a = redirects.total) != null ? _a : 0;
|
|
312
|
+
offset += 50;
|
|
313
|
+
} while (offset < total);
|
|
314
|
+
return trie;
|
|
315
|
+
}
|
|
316
|
+
static processHops(url, trie, bestMatch, options) {
|
|
317
|
+
var _a;
|
|
318
|
+
const isCycle = (id, result) => {
|
|
319
|
+
var _a2, _b, _c;
|
|
320
|
+
if (!id || !result.lastHop)
|
|
321
|
+
return false;
|
|
322
|
+
const set = /* @__PURE__ */ new Set([id]);
|
|
323
|
+
const cycleStack = [result];
|
|
324
|
+
while (cycleStack.length > 0) {
|
|
325
|
+
const cur = cycleStack.pop();
|
|
326
|
+
if (!((_a2 = cur == null ? void 0 : cur.definition) == null ? void 0 : _a2.redirect.id))
|
|
327
|
+
continue;
|
|
328
|
+
if (set.has((_b = cur == null ? void 0 : cur.definition) == null ? void 0 : _b.redirect.id))
|
|
329
|
+
return true;
|
|
330
|
+
set.add((_c = cur == null ? void 0 : cur.definition) == null ? void 0 : _c.redirect.id);
|
|
331
|
+
if (cur == null ? void 0 : cur.lastHop) {
|
|
332
|
+
cycleStack.push(cur.lastHop);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return false;
|
|
336
|
+
};
|
|
337
|
+
const stack = this.processHop(url, trie, bestMatch, options);
|
|
338
|
+
const ret = [];
|
|
339
|
+
while (stack.length > 0) {
|
|
340
|
+
const result = stack.pop();
|
|
341
|
+
if (!(result == null ? void 0 : result.url))
|
|
342
|
+
continue;
|
|
343
|
+
const hop = ((_a = result.definition) == null ? void 0 : _a.redirect.stopExecutingAfter) ? [] : this.processHop(result == null ? void 0 : result.url, trie, bestMatch, options).filter(
|
|
344
|
+
(h) => {
|
|
345
|
+
var _a2, _b;
|
|
346
|
+
return ((_a2 = h.definition) == null ? void 0 : _a2.redirect.id) && !isCycle((_b = h.definition) == null ? void 0 : _b.redirect.id, result);
|
|
347
|
+
}
|
|
348
|
+
);
|
|
349
|
+
if (hop.length === 0) {
|
|
350
|
+
ret.unshift(result);
|
|
351
|
+
}
|
|
352
|
+
hop.forEach((h) => {
|
|
353
|
+
stack.push({ ...h, lastHop: result });
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
return ret;
|
|
357
|
+
}
|
|
358
|
+
static processHop(url, trie, bestMatch, options) {
|
|
359
|
+
const processedUrl = this.processUrl(url);
|
|
360
|
+
let definition = trie.find(url, false);
|
|
361
|
+
if (!(definition == null ? void 0 : definition.length)) {
|
|
362
|
+
definition = trie.find(processedUrl.path + processedUrl.query, bestMatch);
|
|
363
|
+
}
|
|
364
|
+
if (!(definition == null ? void 0 : definition.length)) {
|
|
365
|
+
definition = trie.find(processedUrl.path, bestMatch);
|
|
366
|
+
}
|
|
367
|
+
if (definition == null ? void 0 : definition.length) {
|
|
368
|
+
return definition.map(
|
|
369
|
+
(def) => this.processDefinitionToResults(processedUrl, def.data, def.variables, options)
|
|
370
|
+
).filter((r) => Boolean(r));
|
|
371
|
+
}
|
|
372
|
+
return [];
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Taking the url, found definition and variables and returning a redirect result object
|
|
376
|
+
* @param processedUrl - Propertly formatted url input
|
|
377
|
+
* @param definition - Redirect definition found to match the processed url
|
|
378
|
+
* @param variables - Wildcard variables found during definition discovery
|
|
379
|
+
* @param options - Different options available to the redirect engine
|
|
380
|
+
*/
|
|
381
|
+
static processDefinitionToResults(processedUrl, definition, variables, options) {
|
|
382
|
+
var _a, _b, _c, _d, _e;
|
|
383
|
+
const resultUrl = (options == null ? void 0 : options.reverse) ? definition.redirect.sourceUrl : definition.redirect.targetUrl;
|
|
384
|
+
const processedResult = this.processUrl(resultUrl);
|
|
385
|
+
if ((definition == null ? void 0 : definition.redirect.sourceMustMatchDomain) && processedUrl.domain !== processedResult.domain)
|
|
386
|
+
return void 0;
|
|
387
|
+
const protocol = (definition == null ? void 0 : definition.redirect.targetPreserveIncomingProtocol) ? processedUrl.protocol : (_b = (_a = processedResult.protocol) != null ? _a : processedUrl.protocol) != null ? _b : "";
|
|
388
|
+
const domain = (definition == null ? void 0 : definition.redirect.targetPreserveIncomingDomain) ? processedUrl.domain : (_d = (_c = processedResult.domain) != null ? _c : processedUrl.domain) != null ? _d : "";
|
|
389
|
+
const queryString = (definition == null ? void 0 : definition.redirect.sourceRetainQuerystring) && processedResult.query ? (_e = processedResult.query) != null ? _e : "" : definition.redirect.sourceRetainQuerystring ? processedUrl.query : "";
|
|
390
|
+
const finalUrl = `${protocol}${domain}${processedResult.port}${processedResult.path}${queryString}`;
|
|
391
|
+
return {
|
|
392
|
+
url: variables.reduce((cur, o) => {
|
|
393
|
+
return cur.replace(o.key, o.value);
|
|
394
|
+
}, finalUrl),
|
|
395
|
+
definition,
|
|
396
|
+
label: (options == null ? void 0 : options.label) ? variables.reduce((cur, o) => {
|
|
397
|
+
return cur.replace(o.key, `<em>${o.value}</em>`);
|
|
398
|
+
}, finalUrl) : void 0
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
static processUrl(url) {
|
|
402
|
+
var _a, _b, _c, _d, _e, _f;
|
|
403
|
+
const matches = url.match(/^(https?:\/\/)?(([^:/?#]*)(?::([0-9]+))?)?([/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/);
|
|
404
|
+
return {
|
|
405
|
+
url,
|
|
406
|
+
protocol: (_a = matches == null ? void 0 : matches[1]) != null ? _a : "",
|
|
407
|
+
domain: (_b = matches == null ? void 0 : matches[3]) != null ? _b : "",
|
|
408
|
+
port: (_c = matches == null ? void 0 : matches[4]) != null ? _c : "",
|
|
409
|
+
path: (_d = matches == null ? void 0 : matches[5]) != null ? _d : "",
|
|
410
|
+
query: (_e = matches == null ? void 0 : matches[6]) != null ? _e : "",
|
|
411
|
+
hash: (_f = matches == null ? void 0 : matches[7]) != null ? _f : ""
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
var RedirectClient = _RedirectClient;
|
|
416
|
+
RedirectClient.processUrlBestMatch = async (url, trie, options) => {
|
|
417
|
+
var _a;
|
|
418
|
+
return (_a = _RedirectClient.processHops(url, trie, true, options)) == null ? void 0 : _a[0];
|
|
419
|
+
};
|
|
420
|
+
var UncachedRedirectClient = class extends RedirectClient {
|
|
421
|
+
constructor(options) {
|
|
422
|
+
super({ ...options, bypassCache: true });
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
export {
|
|
426
|
+
PathTrie,
|
|
427
|
+
PathTrieData,
|
|
428
|
+
RedirectClient,
|
|
429
|
+
UncachedRedirectClient,
|
|
430
|
+
WithMemoryCache
|
|
431
|
+
};
|