code: dbal,session,sessions (7 files)

This commit is contained in:
2025-12-26 00:53:44 +00:00
parent acea2a624b
commit a0473de08b
7 changed files with 213 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
/**
* @file clean-expired.ts
* @description Clean expired sessions operation
*/
import type { Result } from '../types';
import type { InMemoryStore } from '../store/in-memory-store';
/**
* Clean up expired sessions
* @returns Number of sessions removed
*/
export async function cleanExpiredSessions(store: InMemoryStore): Promise<Result<number>> {
const now = new Date();
const expiredIds: string[] = [];
for (const [id, session] of store.sessions) {
if (session.expiresAt < now) {
expiredIds.push(id);
}
}
for (const id of expiredIds) {
const session = store.sessions.get(id);
if (session) {
store.sessionTokens.delete(session.token);
store.sessions.delete(id);
}
}
return { success: true, data: expiredIds.length };
}

View File

@@ -0,0 +1,39 @@
/**
* @file create-session.ts
* @description Create session operation
*/
import type { Session, CreateSessionInput, Result } from '../types';
import type { InMemoryStore } from '../store/in-memory-store';
/**
* Create a new session in the store
*/
export async function createSession(
store: InMemoryStore,
input: CreateSessionInput
): Promise<Result<Session>> {
if (!input.userId) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: 'User ID required' } };
}
if (!store.users.has(input.userId)) {
return { success: false, error: { code: 'NOT_FOUND', message: 'User not found' } };
}
if (input.ttlSeconds <= 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: 'TTL must be positive' } };
}
const session: Session = {
id: store.generateId('session'),
userId: input.userId,
token: store.generateToken(),
expiresAt: new Date(Date.now() + input.ttlSeconds * 1000),
ipAddress: input.ipAddress ?? '',
userAgent: input.userAgent ?? '',
createdAt: new Date(),
};
store.sessions.set(session.id, session);
store.sessionTokens.set(session.token, session.id);
return { success: true, data: session };
}

View File

@@ -0,0 +1,25 @@
/**
* @file delete-session.ts
* @description Delete session operation
*/
import type { Result } from '../types';
import type { InMemoryStore } from '../store/in-memory-store';
/**
* Delete a session by ID (logout)
*/
export async function deleteSession(store: InMemoryStore, id: string): Promise<Result<boolean>> {
if (!id) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: 'ID required' } };
}
const session = store.sessions.get(id);
if (!session) {
return { success: false, error: { code: 'NOT_FOUND', message: `Session not found: ${id}` } };
}
store.sessionTokens.delete(session.token);
store.sessions.delete(id);
return { success: true, data: true };
}

View File

@@ -0,0 +1,34 @@
/**
* @file extend-session.ts
* @description Extend session expiration operation
*/
import type { Session, Result } from '../types';
import type { InMemoryStore } from '../store/in-memory-store';
/**
* Extend a session's expiration time
*/
export async function extendSession(
store: InMemoryStore,
id: string,
additionalSeconds: number
): Promise<Result<Session>> {
if (!id) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: 'ID required' } };
}
if (additionalSeconds <= 0) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: 'Additional seconds must be positive' } };
}
const session = store.sessions.get(id);
if (!session) {
return { success: false, error: { code: 'NOT_FOUND', message: `Session not found: ${id}` } };
}
if (session.expiresAt < new Date()) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: 'Cannot extend expired session' } };
}
session.expiresAt = new Date(session.expiresAt.getTime() + additionalSeconds * 1000);
return { success: true, data: session };
}

View File

@@ -0,0 +1,38 @@
/**
* @file get-session.ts
* @description Get session operations
*/
import type { Session, Result } from '../types';
import type { InMemoryStore } from '../store/in-memory-store';
/**
* Get a session by ID
*/
export async function getSession(store: InMemoryStore, id: string): Promise<Result<Session>> {
if (!id) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: 'ID required' } };
}
const session = store.sessions.get(id);
if (!session) {
return { success: false, error: { code: 'NOT_FOUND', message: `Session not found: ${id}` } };
}
return { success: true, data: session };
}
/**
* Get a session by token
*/
export async function getSessionByToken(store: InMemoryStore, token: string): Promise<Result<Session>> {
if (!token) {
return { success: false, error: { code: 'VALIDATION_ERROR', message: 'Token required' } };
}
const id = store.sessionTokens.get(token);
if (!id) {
return { success: false, error: { code: 'NOT_FOUND', message: 'Session not found' } };
}
return getSession(store, id);
}

View File

@@ -0,0 +1,10 @@
/**
* @file index.ts
* @description Barrel export for session operations
*/
export { createSession } from './create-session';
export { getSession, getSessionByToken } from './get-session';
export { extendSession } from './extend-session';
export { deleteSession } from './delete-session';
export { listSessions } from './list-sessions';
export { cleanExpiredSessions } from './clean-expired';

View File

@@ -0,0 +1,36 @@
/**
* @file list-sessions.ts
* @description List sessions with filtering
*/
import type { Session, ListOptions, Result } from '../types';
import type { InMemoryStore } from '../store/in-memory-store';
/**
* List sessions with filtering and pagination
*/
export async function listSessions(
store: InMemoryStore,
options: ListOptions = {}
): Promise<Result<Session[]>> {
const { filter = {}, page = 1, limit = 20 } = options;
const now = new Date();
let sessions = Array.from(store.sessions.values());
// Apply filters
if (filter.userId !== undefined) {
sessions = sessions.filter((s) => s.userId === filter.userId);
}
if (filter.activeOnly) {
sessions = sessions.filter((s) => s.expiresAt > now);
}
// Sort by created_at descending (newest first)
sessions.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
// Apply pagination
const start = (page - 1) * limit;
const paginated = sessions.slice(start, start + limit);
return { success: true, data: paginated };
}