feat(pagination): add pagination utility functions for handling page size and continuation tokens

- Introduced `PaginationRequest` interface to define pagination parameters.
- Implemented `parsePagination` function to extract and validate pagination parameters from a URL.
- Added `encodeContinuationToken` and `decodeContinuationToken` functions for managing continuation tokens.
- Ensured that pagination respects maximum page size limits defined in configuration.
This commit is contained in:
shuaiplus
2026-02-18 20:59:46 +08:00
parent c53819e178
commit b6d4113e21
17 changed files with 668 additions and 232 deletions
+51 -2
View File
@@ -1,7 +1,40 @@
import { Env, SyncResponse, CipherResponse, FolderResponse, ProfileResponse } from '../types';
import { StorageService } from '../services/storage';
import { jsonResponse, errorResponse } from '../utils/response';
import { errorResponse } from '../utils/response';
import { cipherToResponse } from './ciphers';
import { LIMITS } from '../config/limits';
interface SyncCacheEntry {
body: string;
expiresAt: number;
}
const syncResponseCache = new Map<string, SyncCacheEntry>();
function buildSyncCacheKey(userId: string, revisionDate: string, excludeDomains: boolean): string {
return `${userId}:${revisionDate}:${excludeDomains ? '1' : '0'}`;
}
function readSyncCache(key: string): string | null {
const hit = syncResponseCache.get(key);
if (!hit) return null;
if (hit.expiresAt <= Date.now()) {
syncResponseCache.delete(key);
return null;
}
return hit.body;
}
function writeSyncCache(key: string, body: string): void {
if (syncResponseCache.size >= LIMITS.cache.syncResponseMaxEntries) {
const oldestKey = syncResponseCache.keys().next().value as string | undefined;
if (oldestKey) syncResponseCache.delete(oldestKey);
}
syncResponseCache.set(key, {
body,
expiresAt: Date.now() + LIMITS.cache.syncResponseTtlMs,
});
}
// GET /api/sync
export async function handleSync(request: Request, env: Env, userId: string): Promise<Response> {
@@ -15,6 +48,16 @@ export async function handleSync(request: Request, env: Env, userId: string): Pr
return errorResponse('User not found', 404);
}
const revisionDate = await storage.getRevisionDate(userId);
const cacheKey = buildSyncCacheKey(userId, revisionDate, excludeDomains);
const cachedBody = readSyncCache(cacheKey);
if (cachedBody) {
return new Response(cachedBody, {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
const ciphers = await storage.getAllCiphers(userId);
const folders = await storage.getAllFolders(userId);
const attachmentsByCipher = await storage.getAttachmentsByCipherIds(ciphers.map(c => c.id));
@@ -107,5 +150,11 @@ export async function handleSync(request: Request, env: Env, userId: string): Pr
object: 'sync',
};
return jsonResponse(syncResponse);
const body = JSON.stringify(syncResponse);
writeSyncCache(cacheKey, body);
return new Response(body, {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}