@solvapay/next 1.0.0-preview.17 → 1.0.0-preview.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/LICENSE.md +21 -0
- package/README.md +90 -96
- package/dist/index.cjs +139 -147
- package/dist/index.d.cts +184 -78
- package/dist/index.d.ts +184 -78
- package/dist/index.js +142 -159
- package/package.json +7 -6
package/dist/index.cjs
CHANGED
|
@@ -50,6 +50,136 @@ var import_server16 = require("next/server");
|
|
|
50
50
|
var import_server17 = require("@solvapay/server");
|
|
51
51
|
var import_core = require("@solvapay/core");
|
|
52
52
|
|
|
53
|
+
// src/cache.ts
|
|
54
|
+
function createRequestDeduplicator(options = {}) {
|
|
55
|
+
const { cacheTTL = 2e3, maxCacheSize = 1e3, cacheErrors = true } = options;
|
|
56
|
+
const inFlightRequests = /* @__PURE__ */ new Map();
|
|
57
|
+
const resultCache = /* @__PURE__ */ new Map();
|
|
58
|
+
const cacheInvalidatedAt = /* @__PURE__ */ new Map();
|
|
59
|
+
if (cacheTTL > 0) {
|
|
60
|
+
setInterval(
|
|
61
|
+
() => {
|
|
62
|
+
const now = Date.now();
|
|
63
|
+
const entriesToDelete = [];
|
|
64
|
+
for (const [key, cached] of resultCache.entries()) {
|
|
65
|
+
if (now - cached.timestamp >= cacheTTL) {
|
|
66
|
+
entriesToDelete.push(key);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const key of entriesToDelete) {
|
|
70
|
+
resultCache.delete(key);
|
|
71
|
+
cacheInvalidatedAt.delete(key);
|
|
72
|
+
}
|
|
73
|
+
if (resultCache.size > maxCacheSize) {
|
|
74
|
+
const sortedEntries = Array.from(resultCache.entries()).sort(
|
|
75
|
+
(a, b) => a[1].timestamp - b[1].timestamp
|
|
76
|
+
);
|
|
77
|
+
const toRemove = sortedEntries.slice(0, resultCache.size - maxCacheSize);
|
|
78
|
+
for (const [key] of toRemove) {
|
|
79
|
+
resultCache.delete(key);
|
|
80
|
+
cacheInvalidatedAt.delete(key);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
Math.min(cacheTTL, 1e3)
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
const deduplicate = async (key, fn) => {
|
|
88
|
+
if (cacheTTL > 0) {
|
|
89
|
+
const cached = resultCache.get(key);
|
|
90
|
+
if (cached && Date.now() - cached.timestamp < cacheTTL) {
|
|
91
|
+
return cached.data;
|
|
92
|
+
} else if (cached) {
|
|
93
|
+
resultCache.delete(key);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
let requestPromise = inFlightRequests.get(key);
|
|
97
|
+
if (!requestPromise) {
|
|
98
|
+
const requestStartTime = Date.now();
|
|
99
|
+
requestPromise = (async () => {
|
|
100
|
+
try {
|
|
101
|
+
const result = await fn();
|
|
102
|
+
const invalidatedAt = cacheInvalidatedAt.get(key);
|
|
103
|
+
const shouldCache = !invalidatedAt || invalidatedAt < requestStartTime;
|
|
104
|
+
if (cacheTTL > 0 && shouldCache) {
|
|
105
|
+
resultCache.set(key, {
|
|
106
|
+
data: result,
|
|
107
|
+
timestamp: Date.now()
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
const invalidatedAt = cacheInvalidatedAt.get(key);
|
|
113
|
+
const shouldCache = !invalidatedAt || invalidatedAt < requestStartTime;
|
|
114
|
+
if (cacheTTL > 0 && cacheErrors && shouldCache) {
|
|
115
|
+
resultCache.set(key, {
|
|
116
|
+
data: error,
|
|
117
|
+
timestamp: Date.now()
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
throw error;
|
|
121
|
+
} finally {
|
|
122
|
+
inFlightRequests.delete(key);
|
|
123
|
+
}
|
|
124
|
+
})();
|
|
125
|
+
const existingPromise = inFlightRequests.get(key);
|
|
126
|
+
if (existingPromise) {
|
|
127
|
+
requestPromise = existingPromise;
|
|
128
|
+
} else {
|
|
129
|
+
inFlightRequests.set(key, requestPromise);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return requestPromise;
|
|
133
|
+
};
|
|
134
|
+
const clearCache = (key) => {
|
|
135
|
+
resultCache.delete(key);
|
|
136
|
+
cacheInvalidatedAt.set(key, Date.now());
|
|
137
|
+
inFlightRequests.delete(key);
|
|
138
|
+
};
|
|
139
|
+
const clearAllCache = () => {
|
|
140
|
+
resultCache.clear();
|
|
141
|
+
cacheInvalidatedAt.clear();
|
|
142
|
+
inFlightRequests.clear();
|
|
143
|
+
};
|
|
144
|
+
const getStats = () => ({
|
|
145
|
+
inFlight: inFlightRequests.size,
|
|
146
|
+
cached: resultCache.size
|
|
147
|
+
});
|
|
148
|
+
return {
|
|
149
|
+
deduplicate,
|
|
150
|
+
clearCache,
|
|
151
|
+
clearAllCache,
|
|
152
|
+
getStats
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
var sharedSubscriptionDeduplicator = null;
|
|
156
|
+
function getSharedDeduplicator(options) {
|
|
157
|
+
if (!sharedSubscriptionDeduplicator) {
|
|
158
|
+
sharedSubscriptionDeduplicator = createRequestDeduplicator({
|
|
159
|
+
cacheTTL: 2e3,
|
|
160
|
+
// Cache results for 2 seconds
|
|
161
|
+
maxCacheSize: 1e3,
|
|
162
|
+
// Maximum cache entries
|
|
163
|
+
cacheErrors: true,
|
|
164
|
+
// Cache error results too
|
|
165
|
+
...options
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return sharedSubscriptionDeduplicator;
|
|
169
|
+
}
|
|
170
|
+
function clearSubscriptionCache(userId) {
|
|
171
|
+
const deduplicator = getSharedDeduplicator();
|
|
172
|
+
deduplicator.clearCache(userId);
|
|
173
|
+
}
|
|
174
|
+
function clearAllSubscriptionCache() {
|
|
175
|
+
const deduplicator = getSharedDeduplicator();
|
|
176
|
+
deduplicator.clearAllCache();
|
|
177
|
+
}
|
|
178
|
+
function getSubscriptionCacheStats() {
|
|
179
|
+
const deduplicator = getSharedDeduplicator();
|
|
180
|
+
return deduplicator.getStats();
|
|
181
|
+
}
|
|
182
|
+
|
|
53
183
|
// src/helpers/auth.ts
|
|
54
184
|
var import_server = require("next/server");
|
|
55
185
|
var import_server2 = require("@solvapay/server");
|
|
@@ -180,20 +310,17 @@ async function listPlans(request) {
|
|
|
180
310
|
// src/helpers/middleware.ts
|
|
181
311
|
var import_server15 = require("next/server");
|
|
182
312
|
function createAuthMiddleware(options) {
|
|
183
|
-
const {
|
|
184
|
-
adapter,
|
|
185
|
-
publicRoutes = [],
|
|
186
|
-
userIdHeader = "x-user-id"
|
|
187
|
-
} = options;
|
|
313
|
+
const { adapter, publicRoutes = [], userIdHeader = "x-user-id" } = options;
|
|
188
314
|
return async function middleware(request) {
|
|
189
|
-
const
|
|
315
|
+
const req = request;
|
|
316
|
+
const { pathname } = req.nextUrl;
|
|
190
317
|
if (!pathname.startsWith("/api")) {
|
|
191
318
|
return import_server15.NextResponse.next();
|
|
192
319
|
}
|
|
193
320
|
const isPublicRoute = publicRoutes.some((route) => pathname.startsWith(route));
|
|
194
|
-
const userId = await adapter.getUserIdFromRequest(
|
|
321
|
+
const userId = await adapter.getUserIdFromRequest(req);
|
|
195
322
|
if (isPublicRoute) {
|
|
196
|
-
const requestHeaders2 = new Headers(
|
|
323
|
+
const requestHeaders2 = new Headers(req.headers);
|
|
197
324
|
if (userId) {
|
|
198
325
|
requestHeaders2.set(userIdHeader, userId);
|
|
199
326
|
}
|
|
@@ -209,7 +336,7 @@ function createAuthMiddleware(options) {
|
|
|
209
336
|
{ status: 401 }
|
|
210
337
|
);
|
|
211
338
|
}
|
|
212
|
-
const requestHeaders = new Headers(
|
|
339
|
+
const requestHeaders = new Headers(req.headers);
|
|
213
340
|
requestHeaders.set(userIdHeader, userId);
|
|
214
341
|
return import_server15.NextResponse.next({
|
|
215
342
|
request: {
|
|
@@ -219,11 +346,7 @@ function createAuthMiddleware(options) {
|
|
|
219
346
|
};
|
|
220
347
|
}
|
|
221
348
|
function createSupabaseAuthMiddleware(options = {}) {
|
|
222
|
-
const {
|
|
223
|
-
jwtSecret,
|
|
224
|
-
publicRoutes = [],
|
|
225
|
-
userIdHeader = "x-user-id"
|
|
226
|
-
} = options;
|
|
349
|
+
const { jwtSecret, publicRoutes = [], userIdHeader = "x-user-id" } = options;
|
|
227
350
|
let authAdapter = null;
|
|
228
351
|
let adapterPromise = null;
|
|
229
352
|
const lazyAdapter = {
|
|
@@ -255,122 +378,6 @@ function createSupabaseAuthMiddleware(options = {}) {
|
|
|
255
378
|
}
|
|
256
379
|
|
|
257
380
|
// src/index.ts
|
|
258
|
-
function createRequestDeduplicator(options = {}) {
|
|
259
|
-
const {
|
|
260
|
-
cacheTTL = 2e3,
|
|
261
|
-
maxCacheSize = 1e3,
|
|
262
|
-
cacheErrors = true
|
|
263
|
-
} = options;
|
|
264
|
-
const inFlightRequests = /* @__PURE__ */ new Map();
|
|
265
|
-
const resultCache = /* @__PURE__ */ new Map();
|
|
266
|
-
const cacheInvalidatedAt = /* @__PURE__ */ new Map();
|
|
267
|
-
let cleanupInterval = null;
|
|
268
|
-
if (cacheTTL > 0) {
|
|
269
|
-
cleanupInterval = setInterval(() => {
|
|
270
|
-
const now = Date.now();
|
|
271
|
-
const entriesToDelete = [];
|
|
272
|
-
for (const [key, cached] of resultCache.entries()) {
|
|
273
|
-
if (now - cached.timestamp >= cacheTTL) {
|
|
274
|
-
entriesToDelete.push(key);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
for (const key of entriesToDelete) {
|
|
278
|
-
resultCache.delete(key);
|
|
279
|
-
cacheInvalidatedAt.delete(key);
|
|
280
|
-
}
|
|
281
|
-
if (resultCache.size > maxCacheSize) {
|
|
282
|
-
const sortedEntries = Array.from(resultCache.entries()).sort((a, b) => a[1].timestamp - b[1].timestamp);
|
|
283
|
-
const toRemove = sortedEntries.slice(0, resultCache.size - maxCacheSize);
|
|
284
|
-
for (const [key] of toRemove) {
|
|
285
|
-
resultCache.delete(key);
|
|
286
|
-
cacheInvalidatedAt.delete(key);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}, Math.min(cacheTTL, 1e3));
|
|
290
|
-
}
|
|
291
|
-
const deduplicate = async (key, fn) => {
|
|
292
|
-
if (cacheTTL > 0) {
|
|
293
|
-
const cached = resultCache.get(key);
|
|
294
|
-
if (cached && Date.now() - cached.timestamp < cacheTTL) {
|
|
295
|
-
return cached.data;
|
|
296
|
-
} else if (cached) {
|
|
297
|
-
resultCache.delete(key);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
let requestPromise = inFlightRequests.get(key);
|
|
301
|
-
if (!requestPromise) {
|
|
302
|
-
const requestStartTime = Date.now();
|
|
303
|
-
requestPromise = (async () => {
|
|
304
|
-
try {
|
|
305
|
-
const result = await fn();
|
|
306
|
-
const invalidatedAt = cacheInvalidatedAt.get(key);
|
|
307
|
-
const shouldCache = !invalidatedAt || invalidatedAt < requestStartTime;
|
|
308
|
-
if (cacheTTL > 0 && shouldCache) {
|
|
309
|
-
resultCache.set(key, {
|
|
310
|
-
data: result,
|
|
311
|
-
timestamp: Date.now()
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
return result;
|
|
315
|
-
} catch (error) {
|
|
316
|
-
const invalidatedAt = cacheInvalidatedAt.get(key);
|
|
317
|
-
const shouldCache = !invalidatedAt || invalidatedAt < requestStartTime;
|
|
318
|
-
if (cacheTTL > 0 && cacheErrors && shouldCache) {
|
|
319
|
-
resultCache.set(key, {
|
|
320
|
-
data: error,
|
|
321
|
-
timestamp: Date.now()
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
throw error;
|
|
325
|
-
} finally {
|
|
326
|
-
inFlightRequests.delete(key);
|
|
327
|
-
}
|
|
328
|
-
})();
|
|
329
|
-
const existingPromise = inFlightRequests.get(key);
|
|
330
|
-
if (existingPromise) {
|
|
331
|
-
requestPromise = existingPromise;
|
|
332
|
-
} else {
|
|
333
|
-
inFlightRequests.set(key, requestPromise);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
return requestPromise;
|
|
337
|
-
};
|
|
338
|
-
const clearCache = (key) => {
|
|
339
|
-
resultCache.delete(key);
|
|
340
|
-
cacheInvalidatedAt.set(key, Date.now());
|
|
341
|
-
inFlightRequests.delete(key);
|
|
342
|
-
};
|
|
343
|
-
const clearAllCache = () => {
|
|
344
|
-
resultCache.clear();
|
|
345
|
-
cacheInvalidatedAt.clear();
|
|
346
|
-
inFlightRequests.clear();
|
|
347
|
-
};
|
|
348
|
-
const getStats = () => ({
|
|
349
|
-
inFlight: inFlightRequests.size,
|
|
350
|
-
cached: resultCache.size
|
|
351
|
-
});
|
|
352
|
-
return {
|
|
353
|
-
deduplicate,
|
|
354
|
-
clearCache,
|
|
355
|
-
clearAllCache,
|
|
356
|
-
getStats
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
var sharedSubscriptionDeduplicator = null;
|
|
360
|
-
function getSharedDeduplicator(options) {
|
|
361
|
-
if (!sharedSubscriptionDeduplicator) {
|
|
362
|
-
sharedSubscriptionDeduplicator = createRequestDeduplicator({
|
|
363
|
-
cacheTTL: 2e3,
|
|
364
|
-
// Cache results for 2 seconds
|
|
365
|
-
maxCacheSize: 1e3,
|
|
366
|
-
// Maximum cache entries
|
|
367
|
-
cacheErrors: true,
|
|
368
|
-
// Cache error results too
|
|
369
|
-
...options
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
return sharedSubscriptionDeduplicator;
|
|
373
|
-
}
|
|
374
381
|
async function checkSubscription(request, options = {}) {
|
|
375
382
|
try {
|
|
376
383
|
const { requireUserId, getUserEmailFromRequest, getUserNameFromRequest } = await import("@solvapay/auth");
|
|
@@ -401,7 +408,7 @@ async function checkSubscription(request, options = {}) {
|
|
|
401
408
|
};
|
|
402
409
|
}
|
|
403
410
|
}
|
|
404
|
-
} catch
|
|
411
|
+
} catch {
|
|
405
412
|
}
|
|
406
413
|
}
|
|
407
414
|
const deduplicator = getSharedDeduplicator(options.deduplication);
|
|
@@ -434,10 +441,7 @@ async function checkSubscription(request, options = {}) {
|
|
|
434
441
|
} catch (error) {
|
|
435
442
|
console.error("Check subscription failed:", error);
|
|
436
443
|
if (error instanceof import_core.SolvaPayError) {
|
|
437
|
-
return import_server16.NextResponse.json(
|
|
438
|
-
{ error: error.message },
|
|
439
|
-
{ status: 500 }
|
|
440
|
-
);
|
|
444
|
+
return import_server16.NextResponse.json({ error: error.message }, { status: 500 });
|
|
441
445
|
}
|
|
442
446
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
443
447
|
return import_server16.NextResponse.json(
|
|
@@ -446,18 +450,6 @@ async function checkSubscription(request, options = {}) {
|
|
|
446
450
|
);
|
|
447
451
|
}
|
|
448
452
|
}
|
|
449
|
-
function clearSubscriptionCache(userId) {
|
|
450
|
-
const deduplicator = getSharedDeduplicator();
|
|
451
|
-
deduplicator.clearCache(userId);
|
|
452
|
-
}
|
|
453
|
-
function clearAllSubscriptionCache() {
|
|
454
|
-
const deduplicator = getSharedDeduplicator();
|
|
455
|
-
deduplicator.clearAllCache();
|
|
456
|
-
}
|
|
457
|
-
function getSubscriptionCacheStats() {
|
|
458
|
-
const deduplicator = getSharedDeduplicator();
|
|
459
|
-
return deduplicator.getStats();
|
|
460
|
-
}
|
|
461
453
|
// Annotate the CommonJS export names for ESM import in node:
|
|
462
454
|
0 && (module.exports = {
|
|
463
455
|
cancelSubscription,
|
package/dist/index.d.cts
CHANGED
|
@@ -3,6 +3,128 @@ import * as _solvapay_server from '@solvapay/server';
|
|
|
3
3
|
import { AuthenticatedUser, SolvaPay } from '@solvapay/server';
|
|
4
4
|
import { AuthAdapter } from '@solvapay/auth';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Subscription Cache Utilities
|
|
8
|
+
*
|
|
9
|
+
* Cache management functions for subscription data.
|
|
10
|
+
* Separated from index.ts to avoid circular dependencies.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Request deduplication and caching options
|
|
14
|
+
*/
|
|
15
|
+
interface RequestDeduplicationOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Time-to-live for cached results in milliseconds (default: 2000)
|
|
18
|
+
* Set to 0 to disable caching (only deduplicate concurrent requests)
|
|
19
|
+
*/
|
|
20
|
+
cacheTTL?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Maximum cache size before cleanup (default: 1000)
|
|
23
|
+
* When exceeded, oldest entries are removed
|
|
24
|
+
*/
|
|
25
|
+
maxCacheSize?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Whether to cache error results (default: true)
|
|
28
|
+
* When false, only successful results are cached
|
|
29
|
+
*/
|
|
30
|
+
cacheErrors?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Subscription check result
|
|
34
|
+
*/
|
|
35
|
+
interface SubscriptionCheckResult {
|
|
36
|
+
customerRef: string;
|
|
37
|
+
email?: string;
|
|
38
|
+
name?: string;
|
|
39
|
+
subscriptions: Array<{
|
|
40
|
+
reference: string;
|
|
41
|
+
planName?: string;
|
|
42
|
+
agentName?: string;
|
|
43
|
+
status?: string;
|
|
44
|
+
startDate?: string;
|
|
45
|
+
[key: string]: unknown;
|
|
46
|
+
}>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Clear subscription cache for a specific user.
|
|
50
|
+
*
|
|
51
|
+
* Useful when you know subscription status has changed (e.g., after a successful
|
|
52
|
+
* checkout, subscription update, or cancellation). This forces the next
|
|
53
|
+
* `checkSubscription()` call to fetch fresh data from the backend.
|
|
54
|
+
*
|
|
55
|
+
* @param userId - User ID to clear cache for
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* import { clearSubscriptionCache } from '@solvapay/next';
|
|
60
|
+
*
|
|
61
|
+
* // After successful payment
|
|
62
|
+
* await processPayment(request, body);
|
|
63
|
+
* clearSubscriptionCache(userId); // Force refresh on next check
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* @see {@link checkSubscription} for subscription checking
|
|
67
|
+
* @see {@link clearAllSubscriptionCache} to clear all cache entries
|
|
68
|
+
* @since 1.0.0
|
|
69
|
+
*/
|
|
70
|
+
declare function clearSubscriptionCache(userId: string): void;
|
|
71
|
+
/**
|
|
72
|
+
* Clear all subscription cache entries.
|
|
73
|
+
*
|
|
74
|
+
* Useful for testing, debugging, or when you need to force fresh lookups
|
|
75
|
+
* for all users. This clears both the in-flight request cache and the
|
|
76
|
+
* result cache.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```typescript
|
|
80
|
+
* import { clearAllSubscriptionCache } from '@solvapay/next';
|
|
81
|
+
*
|
|
82
|
+
* // In a test setup
|
|
83
|
+
* beforeEach(() => {
|
|
84
|
+
* clearAllSubscriptionCache();
|
|
85
|
+
* });
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* @see {@link clearSubscriptionCache} to clear cache for a specific user
|
|
89
|
+
* @see {@link getSubscriptionCacheStats} for cache monitoring
|
|
90
|
+
* @since 1.0.0
|
|
91
|
+
*/
|
|
92
|
+
declare function clearAllSubscriptionCache(): void;
|
|
93
|
+
/**
|
|
94
|
+
* Get subscription cache statistics for monitoring and debugging.
|
|
95
|
+
*
|
|
96
|
+
* Returns the current state of the subscription cache, including:
|
|
97
|
+
* - Number of in-flight requests (being deduplicated)
|
|
98
|
+
* - Number of cached results
|
|
99
|
+
*
|
|
100
|
+
* @returns Cache statistics object
|
|
101
|
+
* @returns inFlight - Number of currently in-flight requests
|
|
102
|
+
* @returns cached - Number of cached results
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* import { getSubscriptionCacheStats } from '@solvapay/next';
|
|
107
|
+
*
|
|
108
|
+
* // In a monitoring endpoint
|
|
109
|
+
* export async function GET() {
|
|
110
|
+
* const stats = getSubscriptionCacheStats();
|
|
111
|
+
* return Response.json({
|
|
112
|
+
* cache: {
|
|
113
|
+
* inFlight: stats.inFlight,
|
|
114
|
+
* cached: stats.cached
|
|
115
|
+
* }
|
|
116
|
+
* });
|
|
117
|
+
* }
|
|
118
|
+
* ```
|
|
119
|
+
*
|
|
120
|
+
* @see {@link checkSubscription} for subscription checking
|
|
121
|
+
* @since 1.0.0
|
|
122
|
+
*/
|
|
123
|
+
declare function getSubscriptionCacheStats(): {
|
|
124
|
+
inFlight: number;
|
|
125
|
+
cached: number;
|
|
126
|
+
};
|
|
127
|
+
|
|
6
128
|
/**
|
|
7
129
|
* Next.js Authentication Helpers
|
|
8
130
|
*
|
|
@@ -10,11 +132,37 @@ import { AuthAdapter } from '@solvapay/auth';
|
|
|
10
132
|
*/
|
|
11
133
|
|
|
12
134
|
/**
|
|
13
|
-
* Get authenticated user
|
|
135
|
+
* Get authenticated user information from a Next.js request.
|
|
14
136
|
*
|
|
15
|
-
*
|
|
137
|
+
* This is a Next.js-specific wrapper around `getAuthenticatedUserCore` that
|
|
138
|
+
* returns NextResponse for errors instead of ErrorResult. Extracts user ID,
|
|
139
|
+
* email, and name from authenticated requests.
|
|
140
|
+
*
|
|
141
|
+
* @param request - Next.js request object (NextRequest or Request)
|
|
16
142
|
* @param options - Configuration options
|
|
143
|
+
* @param options.includeEmail - Whether to extract email from JWT token (default: true)
|
|
144
|
+
* @param options.includeName - Whether to extract name from JWT token (default: true)
|
|
17
145
|
* @returns Authenticated user info or NextResponse error
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```typescript
|
|
149
|
+
* import { NextRequest, NextResponse } from 'next/server';
|
|
150
|
+
* import { getAuthenticatedUser } from '@solvapay/next';
|
|
151
|
+
*
|
|
152
|
+
* export async function GET(request: NextRequest) {
|
|
153
|
+
* const userResult = await getAuthenticatedUser(request);
|
|
154
|
+
*
|
|
155
|
+
* if (userResult instanceof NextResponse) {
|
|
156
|
+
* return userResult; // Error response
|
|
157
|
+
* }
|
|
158
|
+
*
|
|
159
|
+
* const { userId, email, name } = userResult;
|
|
160
|
+
* return NextResponse.json({ userId, email, name });
|
|
161
|
+
* }
|
|
162
|
+
* ```
|
|
163
|
+
*
|
|
164
|
+
* @see {@link getAuthenticatedUserCore} for the core implementation
|
|
165
|
+
* @since 1.0.0
|
|
18
166
|
*/
|
|
19
167
|
declare function getAuthenticatedUser(request: globalThis.Request, options?: {
|
|
20
168
|
includeEmail?: boolean;
|
|
@@ -95,10 +243,12 @@ declare function processPayment(request: globalThis.Request, body: {
|
|
|
95
243
|
declare function createCheckoutSession(request: globalThis.Request, body: {
|
|
96
244
|
agentRef: string;
|
|
97
245
|
planRef?: string;
|
|
246
|
+
returnUrl?: string;
|
|
98
247
|
}, options?: {
|
|
99
248
|
solvaPay?: SolvaPay;
|
|
100
249
|
includeEmail?: boolean;
|
|
101
250
|
includeName?: boolean;
|
|
251
|
+
returnUrl?: string;
|
|
102
252
|
}): Promise<{
|
|
103
253
|
sessionId: string;
|
|
104
254
|
checkoutUrl: string;
|
|
@@ -124,7 +274,6 @@ declare function createCustomerSession(request: globalThis.Request, options?: {
|
|
|
124
274
|
*
|
|
125
275
|
* Next.js-specific wrappers for subscription helpers.
|
|
126
276
|
*/
|
|
127
|
-
|
|
128
277
|
/**
|
|
129
278
|
* Cancel subscription - Next.js wrapper
|
|
130
279
|
*
|
|
@@ -138,7 +287,7 @@ declare function cancelSubscription(request: globalThis.Request, body: {
|
|
|
138
287
|
reason?: string;
|
|
139
288
|
}, options?: {
|
|
140
289
|
solvaPay?: SolvaPay;
|
|
141
|
-
}): Promise<
|
|
290
|
+
}): Promise<Record<string, unknown> | NextResponse>;
|
|
142
291
|
|
|
143
292
|
/**
|
|
144
293
|
* Next.js Plans Helper
|
|
@@ -146,7 +295,6 @@ declare function cancelSubscription(request: globalThis.Request, body: {
|
|
|
146
295
|
* Next.js-specific wrapper for plans helper.
|
|
147
296
|
* This is a public route - no authentication required.
|
|
148
297
|
*/
|
|
149
|
-
|
|
150
298
|
/**
|
|
151
299
|
* List plans - Next.js wrapper
|
|
152
300
|
*
|
|
@@ -154,7 +302,7 @@ declare function cancelSubscription(request: globalThis.Request, body: {
|
|
|
154
302
|
* @returns Plans response or NextResponse error
|
|
155
303
|
*/
|
|
156
304
|
declare function listPlans(request: globalThis.Request): Promise<{
|
|
157
|
-
plans:
|
|
305
|
+
plans: Array<Record<string, unknown>>;
|
|
158
306
|
agentRef: string;
|
|
159
307
|
} | NextResponse>;
|
|
160
308
|
|
|
@@ -340,49 +488,17 @@ declare function createSupabaseAuthMiddleware(options?: SupabaseAuthMiddlewareOp
|
|
|
340
488
|
* like request deduplication and caching.
|
|
341
489
|
*/
|
|
342
490
|
|
|
343
|
-
/**
|
|
344
|
-
* Request deduplication and caching options
|
|
345
|
-
*/
|
|
346
|
-
interface RequestDeduplicationOptions {
|
|
347
|
-
/**
|
|
348
|
-
* Time-to-live for cached results in milliseconds (default: 2000)
|
|
349
|
-
* Set to 0 to disable caching (only deduplicate concurrent requests)
|
|
350
|
-
*/
|
|
351
|
-
cacheTTL?: number;
|
|
352
|
-
/**
|
|
353
|
-
* Maximum cache size before cleanup (default: 1000)
|
|
354
|
-
* When exceeded, oldest entries are removed
|
|
355
|
-
*/
|
|
356
|
-
maxCacheSize?: number;
|
|
357
|
-
/**
|
|
358
|
-
* Whether to cache error results (default: true)
|
|
359
|
-
* When false, only successful results are cached
|
|
360
|
-
*/
|
|
361
|
-
cacheErrors?: boolean;
|
|
362
|
-
}
|
|
363
|
-
/**
|
|
364
|
-
* Subscription check result
|
|
365
|
-
*/
|
|
366
|
-
interface SubscriptionCheckResult {
|
|
367
|
-
customerRef: string;
|
|
368
|
-
email?: string;
|
|
369
|
-
name?: string;
|
|
370
|
-
subscriptions: Array<{
|
|
371
|
-
reference: string;
|
|
372
|
-
planName?: string;
|
|
373
|
-
agentName?: string;
|
|
374
|
-
status?: string;
|
|
375
|
-
startDate?: string;
|
|
376
|
-
[key: string]: unknown;
|
|
377
|
-
}>;
|
|
378
|
-
}
|
|
379
491
|
/**
|
|
380
492
|
* Options for checking subscriptions
|
|
381
493
|
*/
|
|
382
494
|
interface CheckSubscriptionOptions {
|
|
383
495
|
/**
|
|
384
496
|
* Request deduplication options
|
|
385
|
-
*
|
|
497
|
+
*
|
|
498
|
+
* Default values:
|
|
499
|
+
* - `cacheTTL`: 2000ms
|
|
500
|
+
* - `maxCacheSize`: 1000
|
|
501
|
+
* - `cacheErrors`: true
|
|
386
502
|
*/
|
|
387
503
|
deduplication?: RequestDeduplicationOptions;
|
|
388
504
|
/**
|
|
@@ -402,59 +518,49 @@ interface CheckSubscriptionOptions {
|
|
|
402
518
|
includeName?: boolean;
|
|
403
519
|
}
|
|
404
520
|
/**
|
|
405
|
-
* Check user subscription status
|
|
521
|
+
* Check user subscription status with automatic deduplication and caching.
|
|
406
522
|
*
|
|
407
|
-
* This helper function:
|
|
523
|
+
* This Next.js helper function provides optimized subscription checking with:
|
|
524
|
+
* - Automatic request deduplication (concurrent requests share the same promise)
|
|
525
|
+
* - Short-term caching (2 seconds) to prevent duplicate sequential requests
|
|
526
|
+
* - Fast path optimization using cached customer references from client
|
|
527
|
+
* - Automatic customer creation if needed
|
|
528
|
+
*
|
|
529
|
+
* The function:
|
|
408
530
|
* 1. Extracts user ID from request (via requireUserId from @solvapay/auth)
|
|
409
|
-
* 2. Gets user email and name from Supabase JWT token
|
|
410
|
-
* 3.
|
|
411
|
-
* 4.
|
|
412
|
-
* 5.
|
|
531
|
+
* 2. Gets user email and name from Supabase JWT token (if available)
|
|
532
|
+
* 3. Validates cached customer reference (if provided via header)
|
|
533
|
+
* 4. Ensures customer exists in SolvaPay backend
|
|
534
|
+
* 5. Returns customer subscription information
|
|
413
535
|
*
|
|
414
|
-
* @param request - Next.js request object (NextRequest extends Request
|
|
536
|
+
* @param request - Next.js request object (NextRequest extends Request)
|
|
415
537
|
* @param options - Configuration options
|
|
416
|
-
* @
|
|
538
|
+
* @param options.deduplication - Request deduplication options (see {@link RequestDeduplicationOptions})
|
|
539
|
+
* @param options.solvaPay - Optional SolvaPay instance (creates new one if not provided)
|
|
540
|
+
* @param options.includeEmail - Whether to include email in response (default: true)
|
|
541
|
+
* @param options.includeName - Whether to include name in response (default: true)
|
|
542
|
+
* @returns Subscription check result with customer data and subscriptions, or NextResponse error
|
|
417
543
|
*
|
|
418
544
|
* @example
|
|
419
545
|
* ```typescript
|
|
420
|
-
* import { NextRequest } from 'next/server';
|
|
546
|
+
* import { NextRequest, NextResponse } from 'next/server';
|
|
421
547
|
* import { checkSubscription } from '@solvapay/next';
|
|
422
548
|
*
|
|
423
549
|
* export async function GET(request: NextRequest) {
|
|
424
550
|
* const result = await checkSubscription(request);
|
|
551
|
+
*
|
|
425
552
|
* if (result instanceof NextResponse) {
|
|
426
553
|
* return result; // Error response
|
|
427
554
|
* }
|
|
555
|
+
*
|
|
428
556
|
* return NextResponse.json(result);
|
|
429
557
|
* }
|
|
430
558
|
* ```
|
|
431
|
-
*/
|
|
432
|
-
declare function checkSubscription(request: Request, options?: CheckSubscriptionOptions): Promise<SubscriptionCheckResult | NextResponse>;
|
|
433
|
-
/**
|
|
434
|
-
* Clear subscription cache for a specific user
|
|
435
|
-
*
|
|
436
|
-
* Useful when you know subscription status has changed
|
|
437
|
-
* (e.g., after a successful checkout or subscription update)
|
|
438
|
-
*
|
|
439
|
-
* @param userId - User ID to clear cache for
|
|
440
|
-
*/
|
|
441
|
-
declare function clearSubscriptionCache(userId: string): void;
|
|
442
|
-
/**
|
|
443
|
-
* Clear all subscription cache entries
|
|
444
|
-
*
|
|
445
|
-
* Useful for testing or when you need to force fresh lookups
|
|
446
|
-
*/
|
|
447
|
-
declare function clearAllSubscriptionCache(): void;
|
|
448
|
-
/**
|
|
449
|
-
* Get subscription cache statistics
|
|
450
559
|
*
|
|
451
|
-
*
|
|
452
|
-
*
|
|
453
|
-
* @
|
|
560
|
+
* @see {@link clearSubscriptionCache} for cache management
|
|
561
|
+
* @see {@link getSubscriptionCacheStats} for cache monitoring
|
|
562
|
+
* @since 1.0.0
|
|
454
563
|
*/
|
|
455
|
-
declare function
|
|
456
|
-
inFlight: number;
|
|
457
|
-
cached: number;
|
|
458
|
-
};
|
|
564
|
+
declare function checkSubscription(request: Request, options?: CheckSubscriptionOptions): Promise<SubscriptionCheckResult | NextResponse>;
|
|
459
565
|
|
|
460
566
|
export { type AuthMiddlewareOptions, type CheckSubscriptionOptions, type RequestDeduplicationOptions, type SubscriptionCheckResult, type SupabaseAuthMiddlewareOptions, cancelSubscription, checkSubscription, clearAllSubscriptionCache, clearSubscriptionCache, createAuthMiddleware, createCheckoutSession, createCustomerSession, createPaymentIntent, createSupabaseAuthMiddleware, getAuthenticatedUser, getSubscriptionCacheStats, listPlans, processPayment, syncCustomer };
|