@se-studio/ab-testing 7.0.2 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/dist/codegen/cli.js +1 -0
- package/dist/codegen/cli.js.map +1 -1
- package/dist/components/AbTestReporter.d.ts.map +1 -1
- package/dist/components/AbTestReporter.js +2 -9
- package/dist/components/AbTestReporter.js.map +1 -1
- package/dist/components/AbTestUtmScript.d.ts +5 -2
- package/dist/components/AbTestUtmScript.d.ts.map +1 -1
- package/dist/components/AbTestUtmScript.js +15 -8
- package/dist/components/AbTestUtmScript.js.map +1 -1
- package/dist/hooks/useAbTestAssignments.d.ts +3 -2
- package/dist/hooks/useAbTestAssignments.d.ts.map +1 -1
- package/dist/hooks/useAbTestAssignments.js +3 -2
- package/dist/hooks/useAbTestAssignments.js.map +1 -1
- package/dist/index.d.ts +9 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/middleware/assignment.d.ts +0 -5
- package/dist/middleware/assignment.d.ts.map +1 -1
- package/dist/middleware/assignment.js +49 -22
- package/dist/middleware/assignment.js.map +1 -1
- package/dist/middleware/cache.d.ts.map +1 -1
- package/dist/middleware/cache.js +18 -6
- package/dist/middleware/cache.js.map +1 -1
- package/dist/middleware/handler.d.ts +9 -56
- package/dist/middleware/handler.d.ts.map +1 -1
- package/dist/middleware/handler.js +121 -214
- package/dist/middleware/handler.js.map +1 -1
- package/dist/middleware/index.d.ts +3 -0
- package/dist/middleware/index.d.ts.map +1 -1
- package/dist/middleware/index.js +3 -0
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/prune.d.ts +13 -0
- package/dist/middleware/prune.d.ts.map +1 -0
- package/dist/middleware/prune.js +29 -0
- package/dist/middleware/prune.js.map +1 -0
- package/dist/middleware/types.d.ts +18 -11
- package/dist/middleware/types.d.ts.map +1 -1
- package/dist/middleware/urlParams.d.ts +7 -0
- package/dist/middleware/urlParams.d.ts.map +1 -0
- package/dist/middleware/urlParams.js +20 -0
- package/dist/middleware/urlParams.js.map +1 -0
- package/dist/middleware/variantId.d.ts +9 -0
- package/dist/middleware/variantId.d.ts.map +1 -0
- package/dist/middleware/variantId.js +12 -0
- package/dist/middleware/variantId.js.map +1 -0
- package/dist/reporting/buildExperimentImpression.d.ts +6 -0
- package/dist/reporting/buildExperimentImpression.d.ts.map +1 -0
- package/dist/reporting/buildExperimentImpression.js +22 -0
- package/dist/reporting/buildExperimentImpression.js.map +1 -0
- package/dist/types.d.ts +23 -9
- package/dist/types.d.ts.map +1 -1
- package/dist/webhook/handler.d.ts +19 -26
- package/dist/webhook/handler.d.ts.map +1 -1
- package/dist/webhook/handler.js +27 -22
- package/dist/webhook/handler.js.map +1 -1
- package/dist/webhook/index.d.ts +1 -1
- package/dist/webhook/index.d.ts.map +1 -1
- package/dist/webhook/index.js +1 -1
- package/dist/webhook/index.js.map +1 -1
- package/package.json +7 -1
|
@@ -2,6 +2,57 @@ import { NextResponse } from 'next/server';
|
|
|
2
2
|
import { createAssignment, isValidAssignment, selectVariant } from './assignment';
|
|
3
3
|
import { getCachedTests, normalizePath } from './cache';
|
|
4
4
|
import { DEFAULT_COOKIE_MAX_AGE, DEFAULT_COOKIE_NAME, getAssignment, parseCookie, serializeCookie, setAssignment, } from './cookies';
|
|
5
|
+
import { getActiveTestIds, pruneStaleAssignments } from './prune';
|
|
6
|
+
import { applyUrlParams } from './urlParams';
|
|
7
|
+
function searchParamsMatch(test, searchParams) {
|
|
8
|
+
if (!test.searchParameters) {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
const testSearchParams = new URLSearchParams(test.searchParameters);
|
|
12
|
+
for (const [key, value] of testSearchParams.entries()) {
|
|
13
|
+
if (searchParams.get(key) !== value) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
function resolveAssignment(test, cookie, normalizedPathname, onValidationFailure) {
|
|
20
|
+
let assignmentData = getAssignment(cookie, test.id);
|
|
21
|
+
if (assignmentData && !isValidAssignment(test, assignmentData)) {
|
|
22
|
+
onValidationFailure?.(test.id, assignmentData);
|
|
23
|
+
assignmentData = undefined;
|
|
24
|
+
}
|
|
25
|
+
if (!assignmentData) {
|
|
26
|
+
const { selectedKey, variantSlug } = selectVariant(test);
|
|
27
|
+
assignmentData = createAssignment(test, selectedKey, variantSlug, normalizedPathname);
|
|
28
|
+
setAssignment(cookie, test.id, assignmentData);
|
|
29
|
+
}
|
|
30
|
+
return assignmentData;
|
|
31
|
+
}
|
|
32
|
+
function buildRewriteUrl(assignment, variantPathPrefix) {
|
|
33
|
+
const variantSlug = assignment.variant_slug;
|
|
34
|
+
if (!variantSlug || variantSlug === 'control') {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
const cleanSlug = variantSlug.startsWith('/') ? variantSlug.slice(1) : variantSlug;
|
|
38
|
+
return `${variantPathPrefix}/${cleanSlug}`;
|
|
39
|
+
}
|
|
40
|
+
function applyTestUrlParams(url, test, assignment, respectExistingUrlParams) {
|
|
41
|
+
if (!test.injectUrlParams) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
return applyUrlParams(url, assignment.url_params, respectExistingUrlParams);
|
|
45
|
+
}
|
|
46
|
+
function setCookieHeader(response, cookie, cookieName, cookieMaxAge) {
|
|
47
|
+
const cookieValue = serializeCookie(cookie);
|
|
48
|
+
if (cookieValue) {
|
|
49
|
+
response.cookies.set(cookieName, cookieValue, {
|
|
50
|
+
path: '/',
|
|
51
|
+
maxAge: cookieMaxAge,
|
|
52
|
+
sameSite: 'lax',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
5
56
|
/**
|
|
6
57
|
* Create an A/B test middleware handler.
|
|
7
58
|
*
|
|
@@ -10,74 +61,11 @@ import { DEFAULT_COOKIE_MAX_AGE, DEFAULT_COOKIE_NAME, getAssignment, parseCookie
|
|
|
10
61
|
*
|
|
11
62
|
* @param config - Middleware configuration
|
|
12
63
|
* @returns Async function that processes requests and returns NextResponse or null
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* ```typescript
|
|
16
|
-
* // middleware.ts
|
|
17
|
-
* import { createAbTestMiddleware } from '@se-studio/ab-testing/middleware';
|
|
18
|
-
* import { getAbTestStore } from './server/abTestStore';
|
|
19
|
-
*
|
|
20
|
-
* const abTestHandler = createAbTestMiddleware({
|
|
21
|
-
* getStore: getAbTestStore,
|
|
22
|
-
* cacheTtlMs: 60000,
|
|
23
|
-
* });
|
|
24
|
-
*
|
|
25
|
-
* export async function middleware(request: NextRequest) {
|
|
26
|
-
* const abResponse = await abTestHandler(request);
|
|
27
|
-
* if (abResponse) return abResponse;
|
|
28
|
-
* return NextResponse.next();
|
|
29
|
-
* }
|
|
30
|
-
* ```
|
|
31
64
|
*/
|
|
32
65
|
export function createAbTestMiddleware(config) {
|
|
33
|
-
const { getStore, cacheTtlMs = 60000, cookieName = DEFAULT_COOKIE_NAME, cookieMaxAge = DEFAULT_COOKIE_MAX_AGE, shouldProcess, devTestData, onValidationFailure, variantPathPrefix = '',
|
|
34
|
-
/**
|
|
35
|
-
* Process a single test against the request.
|
|
36
|
-
*/
|
|
37
|
-
function processTest(test, normalizedPathname, searchParams, cookie) {
|
|
38
|
-
// Check search parameters matching
|
|
39
|
-
if (test.searchParameters) {
|
|
40
|
-
const testSearchParams = new URLSearchParams(test.searchParameters);
|
|
41
|
-
for (const [key, value] of testSearchParams.entries()) {
|
|
42
|
-
if (searchParams.get(key) !== value) {
|
|
43
|
-
return null; // Params don't match
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
// Get existing assignment from cookie
|
|
48
|
-
let assignmentData = getAssignment(cookie, test.id);
|
|
49
|
-
// Validate existing assignment against current CMS config
|
|
50
|
-
if (assignmentData && !isValidAssignment(test, assignmentData)) {
|
|
51
|
-
onValidationFailure?.(test.id, assignmentData);
|
|
52
|
-
assignmentData = undefined; // Force re-assignment
|
|
53
|
-
}
|
|
54
|
-
// Create new assignment if needed
|
|
55
|
-
if (!assignmentData) {
|
|
56
|
-
const { selectedKey, variantSlug } = selectVariant(test);
|
|
57
|
-
assignmentData = createAssignment(test, selectedKey, variantSlug, normalizedPathname);
|
|
58
|
-
setAssignment(cookie, test.id, assignmentData);
|
|
59
|
-
}
|
|
60
|
-
// Determine if we need to rewrite
|
|
61
|
-
const variantSlug = assignmentData.test_path;
|
|
62
|
-
let rewriteUrl;
|
|
63
|
-
if (variantSlug && variantSlug !== 'control') {
|
|
64
|
-
const cleanSlug = variantSlug.startsWith('/') ? variantSlug.slice(1) : variantSlug;
|
|
65
|
-
rewriteUrl = `${variantPathPrefix}/${cleanSlug}`;
|
|
66
|
-
}
|
|
67
|
-
return {
|
|
68
|
-
matched: true,
|
|
69
|
-
test,
|
|
70
|
-
assignment: assignmentData,
|
|
71
|
-
rewriteUrl,
|
|
72
|
-
cookieValue: serializeCookie(cookie),
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Main middleware handler.
|
|
77
|
-
*/
|
|
66
|
+
const { getStore, cacheTtlMs = 60000, cookieName = DEFAULT_COOKIE_NAME, cookieMaxAge = DEFAULT_COOKIE_MAX_AGE, shouldProcess, devTestData, onValidationFailure, variantPathPrefix = '', pruneStaleAssignments: pruneStale = true, respectExistingUrlParams = true, } = config;
|
|
78
67
|
return async function abTestMiddleware(request) {
|
|
79
68
|
const pathname = request.nextUrl.pathname;
|
|
80
|
-
// Check if we should process this request
|
|
81
69
|
if (shouldProcess && !shouldProcess(pathname)) {
|
|
82
70
|
return null;
|
|
83
71
|
}
|
|
@@ -85,43 +73,36 @@ export function createAbTestMiddleware(config) {
|
|
|
85
73
|
const normalizedPathname = normalizePath(pathname);
|
|
86
74
|
const store = await Promise.resolve(getStore());
|
|
87
75
|
const testsByPath = await getCachedTests(store, cacheTtlMs, devTestData);
|
|
88
|
-
const
|
|
89
|
-
if (!tests || tests.length === 0) {
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
// Parse existing cookie
|
|
76
|
+
const activeTestIds = pruneStale ? getActiveTestIds(testsByPath) : null;
|
|
93
77
|
const cookieValue = request.cookies.get(cookieName)?.value;
|
|
94
78
|
const cookie = parseCookie(cookieValue);
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const url = request.nextUrl.clone();
|
|
102
|
-
if (result.rewriteUrl) {
|
|
103
|
-
url.pathname = result.rewriteUrl;
|
|
104
|
-
}
|
|
105
|
-
if (appendUtmContent && result.assignment) {
|
|
106
|
-
url.searchParams.set('utm_content', `${result.assignment.test_label}:${result.assignment.test_path}`);
|
|
107
|
-
}
|
|
108
|
-
if (result.rewriteUrl || appendUtmContent) {
|
|
109
|
-
response = NextResponse.rewrite(url);
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
response = NextResponse.next();
|
|
79
|
+
const prunedStale = activeTestIds ? pruneStaleAssignments(cookie, activeTestIds) : false;
|
|
80
|
+
const tests = testsByPath.get(normalizedPathname);
|
|
81
|
+
if (tests && tests.length > 0) {
|
|
82
|
+
for (const test of tests) {
|
|
83
|
+
if (!searchParamsMatch(test, request.nextUrl.searchParams)) {
|
|
84
|
+
continue;
|
|
113
85
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
sameSite: 'lax',
|
|
120
|
-
});
|
|
86
|
+
const assignment = resolveAssignment(test, cookie, normalizedPathname, onValidationFailure);
|
|
87
|
+
const rewriteUrl = buildRewriteUrl(assignment, variantPathPrefix);
|
|
88
|
+
const url = request.nextUrl.clone();
|
|
89
|
+
if (rewriteUrl) {
|
|
90
|
+
url.pathname = rewriteUrl;
|
|
121
91
|
}
|
|
92
|
+
const paramsChanged = applyTestUrlParams(url, test, assignment, respectExistingUrlParams);
|
|
93
|
+
const urlChanged = url.pathname !== request.nextUrl.pathname || url.search !== request.nextUrl.search;
|
|
94
|
+
const response = rewriteUrl || paramsChanged || urlChanged
|
|
95
|
+
? NextResponse.rewrite(url)
|
|
96
|
+
: NextResponse.next();
|
|
97
|
+
setCookieHeader(response, cookie, cookieName, cookieMaxAge);
|
|
122
98
|
return response;
|
|
123
99
|
}
|
|
124
100
|
}
|
|
101
|
+
if (prunedStale) {
|
|
102
|
+
const response = NextResponse.next();
|
|
103
|
+
setCookieHeader(response, cookie, cookieName, cookieMaxAge);
|
|
104
|
+
return response;
|
|
105
|
+
}
|
|
125
106
|
return null;
|
|
126
107
|
}
|
|
127
108
|
catch (error) {
|
|
@@ -134,158 +115,84 @@ export function createAbTestMiddleware(config) {
|
|
|
134
115
|
/**
|
|
135
116
|
* Low-level function to process an A/B test request.
|
|
136
117
|
* Use this if you need more control over the response handling.
|
|
137
|
-
*
|
|
138
|
-
* @param pathname - The request pathname
|
|
139
|
-
* @param searchParams - The request search params
|
|
140
|
-
* @param cookieValue - The current A/B test cookie value
|
|
141
|
-
* @param store - The blob store to fetch tests from
|
|
142
|
-
* @param cacheTtlMs - Cache TTL in milliseconds
|
|
143
|
-
* @param devTestData - Optional dev test data
|
|
144
|
-
* @returns Result object with test matching info and new cookie value
|
|
145
118
|
*/
|
|
146
|
-
export async function processAbTestRequest(pathname, searchParams, cookieValue, store, cacheTtlMs, devTestData) {
|
|
119
|
+
export async function processAbTestRequest(pathname, searchParams, cookieValue, store, cacheTtlMs, devTestData, options = {}) {
|
|
120
|
+
const { pruneStaleAssignments: pruneStale = true, onValidationFailure } = options;
|
|
147
121
|
const normalizedPathname = normalizePath(pathname);
|
|
148
122
|
const resolvedStore = await Promise.resolve(store);
|
|
149
123
|
const testsByPath = await getCachedTests(resolvedStore, cacheTtlMs, devTestData);
|
|
124
|
+
const activeTestIds = pruneStale ? getActiveTestIds(testsByPath) : null;
|
|
125
|
+
const cookie = parseCookie(cookieValue);
|
|
126
|
+
const prunedStale = activeTestIds ? pruneStaleAssignments(cookie, activeTestIds) : false;
|
|
150
127
|
const tests = testsByPath.get(normalizedPathname);
|
|
151
128
|
if (!tests || tests.length === 0) {
|
|
152
|
-
return {
|
|
129
|
+
return {
|
|
130
|
+
matched: false,
|
|
131
|
+
prunedStale,
|
|
132
|
+
cookieValue: prunedStale ? serializeCookie(cookie) : undefined,
|
|
133
|
+
};
|
|
153
134
|
}
|
|
154
|
-
const cookie = parseCookie(cookieValue);
|
|
155
135
|
for (const test of tests) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const testSearchParams = new URLSearchParams(test.searchParameters);
|
|
159
|
-
let paramsMatch = true;
|
|
160
|
-
for (const [key, value] of testSearchParams.entries()) {
|
|
161
|
-
if (searchParams.get(key) !== value) {
|
|
162
|
-
paramsMatch = false;
|
|
163
|
-
break;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
if (!paramsMatch)
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
let assignmentData = getAssignment(cookie, test.id);
|
|
170
|
-
if (assignmentData && !isValidAssignment(test, assignmentData)) {
|
|
171
|
-
assignmentData = undefined;
|
|
172
|
-
}
|
|
173
|
-
if (!assignmentData) {
|
|
174
|
-
const { selectedKey, variantSlug } = selectVariant(test);
|
|
175
|
-
assignmentData = createAssignment(test, selectedKey, variantSlug, normalizedPathname);
|
|
176
|
-
setAssignment(cookie, test.id, assignmentData);
|
|
136
|
+
if (!searchParamsMatch(test, searchParams)) {
|
|
137
|
+
continue;
|
|
177
138
|
}
|
|
178
|
-
const
|
|
179
|
-
const rewriteUrl =
|
|
180
|
-
? variantSlug.startsWith('/')
|
|
181
|
-
? variantSlug
|
|
182
|
-
: `/${variantSlug}`
|
|
183
|
-
: undefined;
|
|
139
|
+
const assignment = resolveAssignment(test, cookie, normalizedPathname, onValidationFailure);
|
|
140
|
+
const rewriteUrl = buildRewriteUrl(assignment, '');
|
|
184
141
|
return {
|
|
185
142
|
matched: true,
|
|
186
143
|
test,
|
|
187
|
-
assignment
|
|
144
|
+
assignment,
|
|
188
145
|
rewriteUrl,
|
|
189
146
|
cookieValue: serializeCookie(cookie),
|
|
190
|
-
|
|
147
|
+
prunedStale,
|
|
191
148
|
};
|
|
192
149
|
}
|
|
193
|
-
return {
|
|
150
|
+
return {
|
|
151
|
+
matched: false,
|
|
152
|
+
prunedStale,
|
|
153
|
+
cookieValue: prunedStale ? serializeCookie(cookie) : undefined,
|
|
154
|
+
};
|
|
194
155
|
}
|
|
195
156
|
/**
|
|
196
157
|
* Create a synchronous A/B test middleware from a pre-generated static testsByPath map.
|
|
197
|
-
*
|
|
198
|
-
* Use this when you generate `testsByPath` at build time (e.g. via `generateAbTestsFileContent`)
|
|
199
|
-
* and import the result directly — no blob store or cache TTL needed.
|
|
200
|
-
*
|
|
201
|
-
* @param staticTestsByPath - Pre-generated map from `buildStaticTestsByPath`
|
|
202
|
-
* @param options - Cookie, prefix, and filter options
|
|
203
|
-
* @returns Sync middleware function (returns NextResponse | null)
|
|
204
|
-
*
|
|
205
|
-
* @example
|
|
206
|
-
* ```typescript
|
|
207
|
-
* // middleware.ts
|
|
208
|
-
* import { createStaticAbTestMiddleware } from '@se-studio/ab-testing/middleware';
|
|
209
|
-
* import { testsByPath } from '@/lib/__generated/abTests';
|
|
210
|
-
*
|
|
211
|
-
* const abTestHandler = createStaticAbTestMiddleware(testsByPath, {
|
|
212
|
-
* variantPathPrefix: '/page-test',
|
|
213
|
-
* });
|
|
214
|
-
*
|
|
215
|
-
* export function middleware(request: NextRequest) {
|
|
216
|
-
* const abResponse = abTestHandler(request);
|
|
217
|
-
* if (abResponse) return abResponse;
|
|
218
|
-
* return NextResponse.next();
|
|
219
|
-
* }
|
|
220
|
-
* ```
|
|
221
158
|
*/
|
|
222
159
|
export function createStaticAbTestMiddleware(staticTestsByPath, options = {}) {
|
|
223
|
-
const { cookieName = DEFAULT_COOKIE_NAME, cookieMaxAge = DEFAULT_COOKIE_MAX_AGE, variantPathPrefix = '', shouldProcess, onValidationFailure,
|
|
224
|
-
// Convert Record to Map once at factory time for O(1) lookups per request
|
|
160
|
+
const { cookieName = DEFAULT_COOKIE_NAME, cookieMaxAge = DEFAULT_COOKIE_MAX_AGE, variantPathPrefix = '', shouldProcess, onValidationFailure, pruneStaleAssignments: pruneStale = true, respectExistingUrlParams = true, } = options;
|
|
225
161
|
const testsByPathMap = new Map(Object.entries(staticTestsByPath));
|
|
162
|
+
const activeTestIds = pruneStale ? getActiveTestIds(testsByPathMap) : null;
|
|
226
163
|
return function staticAbTestMiddleware(request) {
|
|
227
164
|
const pathname = request.nextUrl.pathname;
|
|
228
165
|
if (shouldProcess && !shouldProcess(pathname)) {
|
|
229
166
|
return null;
|
|
230
167
|
}
|
|
231
168
|
const normalizedPathname = normalizePath(pathname);
|
|
232
|
-
const tests = testsByPathMap.get(normalizedPathname);
|
|
233
|
-
if (!tests || tests.length === 0) {
|
|
234
|
-
return null;
|
|
235
|
-
}
|
|
236
169
|
const cookieValue = request.cookies.get(cookieName)?.value;
|
|
237
170
|
const cookie = parseCookie(cookieValue);
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
if (request.nextUrl.searchParams.get(key) !== value) {
|
|
244
|
-
paramsMatch = false;
|
|
245
|
-
break;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
if (!paramsMatch)
|
|
171
|
+
const prunedStale = activeTestIds ? pruneStaleAssignments(cookie, activeTestIds) : false;
|
|
172
|
+
const tests = testsByPathMap.get(normalizedPathname);
|
|
173
|
+
if (tests && tests.length > 0) {
|
|
174
|
+
for (const test of tests) {
|
|
175
|
+
if (!searchParamsMatch(test, request.nextUrl.searchParams)) {
|
|
249
176
|
continue;
|
|
177
|
+
}
|
|
178
|
+
const assignment = resolveAssignment(test, cookie, normalizedPathname, onValidationFailure);
|
|
179
|
+
const rewriteUrl = buildRewriteUrl(assignment, variantPathPrefix);
|
|
180
|
+
const url = request.nextUrl.clone();
|
|
181
|
+
if (rewriteUrl) {
|
|
182
|
+
url.pathname = rewriteUrl;
|
|
183
|
+
}
|
|
184
|
+
const paramsChanged = applyTestUrlParams(url, test, assignment, respectExistingUrlParams);
|
|
185
|
+
const urlChanged = url.pathname !== request.nextUrl.pathname || url.search !== request.nextUrl.search;
|
|
186
|
+
const response = rewriteUrl || paramsChanged || urlChanged
|
|
187
|
+
? NextResponse.rewrite(url)
|
|
188
|
+
: NextResponse.next();
|
|
189
|
+
setCookieHeader(response, cookie, cookieName, cookieMaxAge);
|
|
190
|
+
return response;
|
|
250
191
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
if (!assignmentData) {
|
|
257
|
-
const { selectedKey, variantSlug } = selectVariant(test);
|
|
258
|
-
assignmentData = createAssignment(test, selectedKey, variantSlug, normalizedPathname);
|
|
259
|
-
setAssignment(cookie, test.id, assignmentData);
|
|
260
|
-
}
|
|
261
|
-
const variantSlug = assignmentData.test_path;
|
|
262
|
-
let rewriteUrl;
|
|
263
|
-
if (variantSlug && variantSlug !== 'control') {
|
|
264
|
-
const cleanSlug = variantSlug.startsWith('/') ? variantSlug.slice(1) : variantSlug;
|
|
265
|
-
rewriteUrl = `${variantPathPrefix}/${cleanSlug}`;
|
|
266
|
-
}
|
|
267
|
-
let response;
|
|
268
|
-
const url = request.nextUrl.clone();
|
|
269
|
-
if (rewriteUrl) {
|
|
270
|
-
url.pathname = rewriteUrl;
|
|
271
|
-
}
|
|
272
|
-
if (appendUtmContent) {
|
|
273
|
-
url.searchParams.set('utm_content', `${assignmentData.test_label}:${assignmentData.test_path}`);
|
|
274
|
-
}
|
|
275
|
-
if (rewriteUrl || appendUtmContent) {
|
|
276
|
-
response = NextResponse.rewrite(url);
|
|
277
|
-
}
|
|
278
|
-
else {
|
|
279
|
-
response = NextResponse.next();
|
|
280
|
-
}
|
|
281
|
-
const newCookieValue = serializeCookie(cookie);
|
|
282
|
-
if (newCookieValue) {
|
|
283
|
-
response.cookies.set(cookieName, newCookieValue, {
|
|
284
|
-
path: '/',
|
|
285
|
-
maxAge: cookieMaxAge,
|
|
286
|
-
sameSite: 'lax',
|
|
287
|
-
});
|
|
288
|
-
}
|
|
192
|
+
}
|
|
193
|
+
if (prunedStale) {
|
|
194
|
+
const response = NextResponse.next();
|
|
195
|
+
setCookieHeader(response, cookie, cookieName, cookieMaxAge);
|
|
289
196
|
return response;
|
|
290
197
|
}
|
|
291
198
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/middleware/handler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClF,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxD,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,aAAa,EACb,WAAW,EACX,eAAe,EACf,aAAa,GACd,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/middleware/handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,5 +8,8 @@ export { clearTestCache, computeCachedTest, getCachedTests, getTestCacheState, n
|
|
|
8
8
|
export { DEFAULT_COOKIE_MAX_AGE, DEFAULT_COOKIE_NAME, getAssignment, parseCookie, readCookieFromDocument, serializeCookie, setAssignment, } from './cookies';
|
|
9
9
|
export type { StaticAbTestMiddlewareOptions } from './handler';
|
|
10
10
|
export { createAbTestMiddleware, createStaticAbTestMiddleware, processAbTestRequest, } from './handler';
|
|
11
|
+
export { getActiveTestIds, pruneStaleAssignments } from './prune';
|
|
11
12
|
export type { AbTestMiddlewareConfig, AbTestResult, CachedAbTest, TestsCache, } from './types';
|
|
13
|
+
export { applyUrlParams } from './urlParams';
|
|
14
|
+
export { buildVariantId } from './variantId';
|
|
12
15
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClF,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,aAAa,EACb,WAAW,EACX,sBAAsB,EACtB,eAAe,EACf,aAAa,GACd,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,6BAA6B,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EACL,sBAAsB,EACtB,4BAA4B,EAC5B,oBAAoB,GACrB,MAAM,WAAW,CAAC;AACnB,YAAY,EACV,sBAAsB,EACtB,YAAY,EACZ,YAAY,EACZ,UAAU,GACX,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClF,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,aAAa,EACb,WAAW,EACX,sBAAsB,EACtB,eAAe,EACf,aAAa,GACd,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,6BAA6B,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EACL,sBAAsB,EACtB,4BAA4B,EAC5B,oBAAoB,GACrB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAClE,YAAY,EACV,sBAAsB,EACtB,YAAY,EACZ,YAAY,EACZ,UAAU,GACX,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/middleware/index.js
CHANGED
|
@@ -7,4 +7,7 @@ export { createAssignment, isValidAssignment, selectVariant } from './assignment
|
|
|
7
7
|
export { clearTestCache, computeCachedTest, getCachedTests, getTestCacheState, normalizePath, } from './cache';
|
|
8
8
|
export { DEFAULT_COOKIE_MAX_AGE, DEFAULT_COOKIE_NAME, getAssignment, parseCookie, readCookieFromDocument, serializeCookie, setAssignment, } from './cookies';
|
|
9
9
|
export { createAbTestMiddleware, createStaticAbTestMiddleware, processAbTestRequest, } from './handler';
|
|
10
|
+
export { getActiveTestIds, pruneStaleAssignments } from './prune';
|
|
11
|
+
export { applyUrlParams } from './urlParams';
|
|
12
|
+
export { buildVariantId } from './variantId';
|
|
10
13
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClF,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,aAAa,EACb,WAAW,EACX,sBAAsB,EACtB,eAAe,EACf,aAAa,GACd,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,sBAAsB,EACtB,4BAA4B,EAC5B,oBAAoB,GACrB,MAAM,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClF,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,aAAa,EACb,WAAW,EACX,sBAAsB,EACtB,eAAe,EACf,aAAa,GACd,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,sBAAsB,EACtB,4BAA4B,EAC5B,oBAAoB,GACrB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAOlE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AbTestCookie } from '../types';
|
|
2
|
+
import type { CachedAbTest } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Collect all active test IDs from a testsByPath map.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getActiveTestIds(testsByPath: Record<string, CachedAbTest[]> | Map<string, CachedAbTest[]>): Set<string>;
|
|
7
|
+
/**
|
|
8
|
+
* Remove cookie entries for tests no longer in the active config.
|
|
9
|
+
*
|
|
10
|
+
* @returns true if any keys were removed
|
|
11
|
+
*/
|
|
12
|
+
export declare function pruneStaleAssignments(cookie: AbTestCookie, activeTestIds: Set<string>): boolean;
|
|
13
|
+
//# sourceMappingURL=prune.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prune.d.ts","sourceRoot":"","sources":["../../src/middleware/prune.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,GACxE,GAAG,CAAC,MAAM,CAAC,CAWb;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,OAAO,CAW/F"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collect all active test IDs from a testsByPath map.
|
|
3
|
+
*/
|
|
4
|
+
export function getActiveTestIds(testsByPath) {
|
|
5
|
+
const ids = new Set();
|
|
6
|
+
const testLists = testsByPath instanceof Map ? testsByPath.values() : Object.values(testsByPath);
|
|
7
|
+
for (const tests of testLists) {
|
|
8
|
+
for (const test of tests) {
|
|
9
|
+
ids.add(test.id);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return ids;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Remove cookie entries for tests no longer in the active config.
|
|
16
|
+
*
|
|
17
|
+
* @returns true if any keys were removed
|
|
18
|
+
*/
|
|
19
|
+
export function pruneStaleAssignments(cookie, activeTestIds) {
|
|
20
|
+
let pruned = false;
|
|
21
|
+
for (const testId of Object.keys(cookie)) {
|
|
22
|
+
if (!activeTestIds.has(testId)) {
|
|
23
|
+
delete cookie[testId];
|
|
24
|
+
pruned = true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return pruned;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=prune.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prune.js","sourceRoot":"","sources":["../../src/middleware/prune.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAC9B,WAAyE;IAEzE,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,MAAM,SAAS,GAAG,WAAW,YAAY,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAEjG,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;QAC9B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAoB,EAAE,aAA0B;IACpF,IAAI,MAAM,GAAG,KAAK,CAAC;IAEnB,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACzC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAC/B,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC;YACtB,MAAM,GAAG,IAAI,CAAC;QAChB,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -7,8 +7,14 @@ export interface CachedAbTest extends AbTest {
|
|
|
7
7
|
readyWeights: Record<string, number>;
|
|
8
8
|
/** Pre-computed lookup: variantId/control -> slug */
|
|
9
9
|
variantLookup: Record<string, string>;
|
|
10
|
-
/** Pre-computed lookup: variantId/control ->
|
|
11
|
-
|
|
10
|
+
/** Pre-computed lookup: variantId/control -> variant_label */
|
|
11
|
+
variantLabelLookup: Record<string, string | undefined>;
|
|
12
|
+
/** Pre-computed lookup: variantId/control -> reporting event name */
|
|
13
|
+
reportingEventLookup: Record<string, string | undefined>;
|
|
14
|
+
/** Pre-computed lookup: variantId/control -> url_params */
|
|
15
|
+
urlParamsLookup: Record<string, Record<string, string> | undefined>;
|
|
16
|
+
/** Pre-computed lookup: variantId/control -> reporting_params */
|
|
17
|
+
reportingParamsLookup: Record<string, Record<string, string> | undefined>;
|
|
12
18
|
}
|
|
13
19
|
/**
|
|
14
20
|
* In-memory cache structure for A/B tests.
|
|
@@ -67,14 +73,15 @@ export interface AbTestMiddlewareConfig {
|
|
|
67
73
|
*/
|
|
68
74
|
variantPathPrefix?: string;
|
|
69
75
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* The browser URL stays clean; the param is only visible server-side via `searchParams`.
|
|
73
|
-
* For control assignments, `NextResponse.rewrite()` is used instead of `NextResponse.next()`
|
|
74
|
-
* so the param is carried through.
|
|
75
|
-
* @default false
|
|
76
|
+
* Remove cookie entries for tests no longer in the active config.
|
|
77
|
+
* @default true
|
|
76
78
|
*/
|
|
77
|
-
|
|
79
|
+
pruneStaleAssignments?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* When injecting url_params, skip keys already present on the request URL.
|
|
82
|
+
* @default true
|
|
83
|
+
*/
|
|
84
|
+
respectExistingUrlParams?: boolean;
|
|
78
85
|
}
|
|
79
86
|
/**
|
|
80
87
|
* Result of processing an A/B test request.
|
|
@@ -90,7 +97,7 @@ export interface AbTestResult {
|
|
|
90
97
|
rewriteUrl?: string;
|
|
91
98
|
/** Updated cookie value to set */
|
|
92
99
|
cookieValue?: string;
|
|
93
|
-
/**
|
|
94
|
-
|
|
100
|
+
/** Whether stale cookie keys were pruned */
|
|
101
|
+
prunedStale?: boolean;
|
|
95
102
|
}
|
|
96
103
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/middleware/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,YAAa,SAAQ,MAAM;IAC1C,4DAA4D;IAC5D,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,qDAAqD;IACrD,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/middleware/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,YAAa,SAAQ,MAAM;IAC1C,4DAA4D;IAC5D,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,qDAAqD;IACrD,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,8DAA8D;IAC9D,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACvD,qEAAqE;IACrE,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzD,2DAA2D;IAC3D,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC;IACpE,iEAAiE;IACjE,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC;CAC3E;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,kDAAkD;IAClD,SAAS,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;CAC1C;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,QAAQ,EAAE,MAAM,UAAU,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;IAEjE;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;IAE9C;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IAEvB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAE7E;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAEhC;;;OAGG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,+CAA+C;IAC/C,OAAO,EAAE,OAAO,CAAC;IACjB,yCAAyC;IACzC,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,kDAAkD;IAClD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kCAAkC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apply url_params to a URL, optionally respecting existing query keys.
|
|
3
|
+
*
|
|
4
|
+
* @returns true if any param was set
|
|
5
|
+
*/
|
|
6
|
+
export declare function applyUrlParams(url: URL, urlParams: Record<string, string> | undefined, respectExisting: boolean): boolean;
|
|
7
|
+
//# sourceMappingURL=urlParams.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"urlParams.d.ts","sourceRoot":"","sources":["../../src/middleware/urlParams.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EAC7C,eAAe,EAAE,OAAO,GACvB,OAAO,CAeT"}
|