UNPKG

@comic-vine/sqlite-store

Version:

SQLite store implementations for Comic Vine client caching, deduplication, and rate limiting using Drizzle ORM

529 lines (522 loc) 18.3 kB
import { CacheStore, DedupeStore, RateLimitStore, RateLimitConfig, AdaptiveRateLimitStore, AdaptiveConfigSchema, RequestPriority } from '@comic-vine/client'; export { AdaptiveConfig, AdaptiveRateLimitStore, CacheStore, DedupeStore, RateLimitConfig, RateLimitStore, RequestPriority } from '@comic-vine/client'; import Database from 'better-sqlite3'; import { z } from 'zod'; import * as drizzle_orm_sqlite_core from 'drizzle-orm/sqlite-core'; interface SQLiteCacheStoreOptions { /** File path or existing `better-sqlite3` connection. Defaults to `':memory:'`. */ database?: string | InstanceType<typeof Database>; cleanupIntervalMs?: number; maxEntrySizeBytes?: number; } declare class SQLiteCacheStore<T = unknown> implements CacheStore<T> { private db; private sqlite; /** Indicates whether this store is responsible for managing (and therefore closing) the SQLite connection */ private readonly isConnectionManaged; private cleanupInterval?; private readonly cleanupIntervalMs; /** * Maximum allowed size (in bytes) for a single cache entry. If the serialized * value exceeds this limit the entry will be **silently skipped** to avoid * breaching SQLite's max length limits which could otherwise throw at write * time. Defaults to `5 MiB`, which is well under SQLiteʼs compiled * `SQLITE_MAX_LENGTH` (usually 1 GiB) yet large enough for typical Comic Vine * responses. */ private readonly maxEntrySizeBytes; private isDestroyed; constructor({ /** File path or existing `better-sqlite3` connection. Defaults to `':memory:'`. */ database, /** Cleanup interval in milliseconds. Defaults to 1 minute. */ cleanupIntervalMs, /** Maximum allowed size (in bytes) for a single cache entry. Defaults to 5 MiB. */ maxEntrySizeBytes, }?: SQLiteCacheStoreOptions); get(hash: string): Promise<T | undefined>; set(hash: string, value: T, ttlSeconds: number): Promise<void>; delete(hash: string): Promise<void>; clear(): Promise<void>; /** * Get cache statistics */ getStats(): Promise<{ totalItems: number; expiredItems: number; databaseSizeKB: number; }>; /** * Manually trigger cleanup of expired items */ cleanup(): Promise<void>; /** * Close the database connection */ close(): Promise<void>; /** * Alias for close() to match test expectations */ destroy(): void; private initializeDatabase; private startCleanupInterval; private cleanupExpiredItems; } interface SQLiteDedupeStoreOptions { database?: string | InstanceType<typeof Database>; jobTimeoutMs?: number; timeoutMs?: number; cleanupIntervalMs?: number; } declare class SQLiteDedupeStore<T = unknown> implements DedupeStore<T> { private db; private sqlite; /** Indicates whether this store manages (and should close) the SQLite connection */ private readonly isConnectionManaged; private jobPromises; private jobResolvers; private readonly jobTimeoutMs; private cleanupInterval?; private readonly cleanupIntervalMs; private isDestroyed; constructor({ /** File path or existing `better-sqlite3` Database instance. Defaults to `':memory:'`. */ database, /** Job timeout in milliseconds. Preferred over timeoutMs. */ jobTimeoutMs, /** Legacy alias for jobTimeoutMs. */ timeoutMs, /** Cleanup interval in milliseconds. Defaults to 1 minute. */ cleanupIntervalMs, }?: SQLiteDedupeStoreOptions); private startCleanupInterval; private cleanupExpiredJobs; waitFor(hash: string): Promise<T | undefined>; register(hash: string): Promise<string>; complete(hash: string, value: T | undefined): Promise<void>; fail(hash: string, error: Error): Promise<void>; isInProgress(hash: string): Promise<boolean>; getResult(hash: string): Promise<T | undefined>; /** * Get statistics about dedupe jobs */ getStats(): Promise<{ totalJobs: number; pendingJobs: number; completedJobs: number; failedJobs: number; expiredJobs: number; }>; /** * Clean up expired jobs */ cleanup(): Promise<void>; /** * Clear all jobs */ clear(): Promise<void>; /** * Close the database connection */ close(): Promise<void>; /** * Alias for close() to match test expectations */ destroy(): void; private initializeDatabase; } interface SQLiteRateLimitStoreOptions { /** File path or existing `better-sqlite3` Database instance. Defaults to `':memory:'`. */ database?: string | InstanceType<typeof Database>; /** Global/default rate-limit config applied when a resource-specific override is not provided. */ defaultConfig?: RateLimitConfig; /** Optional per-resource overrides. */ resourceConfigs?: Map<string, RateLimitConfig>; } declare class SQLiteRateLimitStore implements RateLimitStore { private db; private sqlite; /** Indicates whether this store manages (and should close) the SQLite connection */ private readonly isConnectionManaged; private defaultConfig; private resourceConfigs; private isDestroyed; constructor({ /** File path or existing `better-sqlite3` Database instance. Defaults to `':memory:'`. */ database, /** Global/default rate-limit config applied when a resource-specific override is not provided. */ defaultConfig, /** Optional per-resource overrides. */ resourceConfigs, }?: SQLiteRateLimitStoreOptions); canProceed(resource: string): Promise<boolean>; record(resource: string): Promise<void>; getStatus(resource: string): Promise<{ remaining: number; resetTime: Date; limit: number; }>; reset(resource: string): Promise<void>; getWaitTime(resource: string): Promise<number>; /** * Set rate limit configuration for a specific resource */ setResourceConfig(resource: string, config: RateLimitConfig): void; /** * Get rate limit configuration for a resource */ getResourceConfig(resource: string): RateLimitConfig; /** * Get statistics for all resources */ getStats(): Promise<{ totalRequests: number; uniqueResources: number; rateLimitedResources: Array<string>; }>; /** * Clean up all rate limit data */ clear(): Promise<void>; /** * Clean up expired requests for all resources */ cleanup(): Promise<void>; /** * Close the database connection */ close(): Promise<void>; /** * Alias for close() to match test expectations */ destroy(): void; private cleanupExpiredRequests; private initializeDatabase; } interface SqliteAdaptiveRateLimitStoreOptions { /** File path or existing `better-sqlite3` Database instance. Defaults to `':memory:'`. */ database?: string | InstanceType<typeof Database>; /** Global/default rate-limit config applied when a resource-specific override is not provided. */ defaultConfig?: RateLimitConfig; /** Optional per-resource overrides. */ resourceConfigs?: Map<string, RateLimitConfig>; /** Adaptive configuration for priority-based rate limiting */ adaptiveConfig?: Partial<z.input<typeof AdaptiveConfigSchema>>; } declare class SqliteAdaptiveRateLimitStore implements AdaptiveRateLimitStore { private db; private sqlite; /** Indicates whether this store manages (and should close) the SQLite connection */ private readonly isConnectionManaged; private defaultConfig; private resourceConfigs; private isDestroyed; private capacityCalculator; private activityMetrics; private lastCapacityUpdate; private cachedCapacity; constructor({ database, defaultConfig, resourceConfigs, adaptiveConfig, }?: SqliteAdaptiveRateLimitStoreOptions); canProceed(resource: string, priority?: RequestPriority): Promise<boolean>; record(resource: string, priority?: RequestPriority): Promise<void>; getStatus(resource: string): Promise<{ remaining: number; resetTime: Date; limit: number; adaptive?: { userReserved: number; backgroundMax: number; backgroundPaused: boolean; recentUserActivity: number; reason: string; }; }>; reset(resource: string): Promise<void>; getWaitTime(resource: string, priority?: RequestPriority): Promise<number>; /** * Set rate limit configuration for a specific resource */ setResourceConfig(resource: string, config: RateLimitConfig): void; /** * Get rate limit configuration for a resource */ getResourceConfig(resource: string): RateLimitConfig; /** * Get statistics for all resources */ getStats(): Promise<{ totalRequests: number; uniqueResources: number; rateLimitedResources: Array<string>; }>; /** * Clean up all rate limit data */ clear(): Promise<void>; /** * Clean up expired requests for all resources */ cleanup(): Promise<void>; /** * Close the database connection */ close(): Promise<void>; /** * Alias for close() to match test expectations */ destroy(): void; private calculateCurrentCapacity; private getOrCreateActivityMetrics; private ensureActivityMetrics; private getCurrentUsage; private cleanupOldRequests; private getResourceLimit; private getDefaultCapacity; private cleanupExpiredRequests; private initializeDatabase; } declare const cacheTable: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{ name: "cache"; schema: undefined; columns: { hash: drizzle_orm_sqlite_core.SQLiteColumn<{ name: "hash"; tableName: "cache"; dataType: "string"; columnType: "SQLiteText"; data: string; driverParam: string; notNull: true; hasDefault: false; isPrimaryKey: true; isAutoincrement: false; hasRuntimeDefault: false; enumValues: [string, ...string[]]; baseColumn: never; generated: undefined; }, object>; value: drizzle_orm_sqlite_core.SQLiteColumn<{ name: "value"; tableName: "cache"; dataType: "json"; columnType: "SQLiteBlobJson"; data: unknown; driverParam: Buffer; notNull: true; hasDefault: false; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; enumValues: undefined; baseColumn: never; generated: undefined; }, object>; expiresAt: drizzle_orm_sqlite_core.SQLiteColumn<{ name: "expires_at"; tableName: "cache"; dataType: "number"; columnType: "SQLiteInteger"; data: number; driverParam: number; notNull: true; hasDefault: false; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; enumValues: undefined; baseColumn: never; generated: undefined; }, object>; createdAt: drizzle_orm_sqlite_core.SQLiteColumn<{ name: "created_at"; tableName: "cache"; dataType: "number"; columnType: "SQLiteInteger"; data: number; driverParam: number; notNull: true; hasDefault: false; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; enumValues: undefined; baseColumn: never; generated: undefined; }, object>; }; dialect: "sqlite"; }>; declare const dedupeTable: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{ name: "dedupe_jobs"; schema: undefined; columns: { hash: drizzle_orm_sqlite_core.SQLiteColumn<{ name: "hash"; tableName: "dedupe_jobs"; dataType: "string"; columnType: "SQLiteText"; data: string; driverParam: string; notNull: true; hasDefault: false; isPrimaryKey: true; isAutoincrement: false; hasRuntimeDefault: false; enumValues: [string, ...string[]]; baseColumn: never; generated: undefined; }, object>; jobId: drizzle_orm_sqlite_core.SQLiteColumn<{ name: "job_id"; tableName: "dedupe_jobs"; dataType: "string"; columnType: "SQLiteText"; data: string; driverParam: string; notNull: true; hasDefault: false; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; enumValues: [string, ...string[]]; baseColumn: never; generated: undefined; }, object>; status: drizzle_orm_sqlite_core.SQLiteColumn<{ name: "status"; tableName: "dedupe_jobs"; dataType: "string"; columnType: "SQLiteText"; data: string; driverParam: string; notNull: true; hasDefault: false; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; enumValues: [string, ...string[]]; baseColumn: never; generated: undefined; }, object>; result: drizzle_orm_sqlite_core.SQLiteColumn<{ name: "result"; tableName: "dedupe_jobs"; dataType: "json"; columnType: "SQLiteBlobJson"; data: unknown; driverParam: Buffer; notNull: false; hasDefault: false; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; enumValues: undefined; baseColumn: never; generated: undefined; }, object>; error: drizzle_orm_sqlite_core.SQLiteColumn<{ name: "error"; tableName: "dedupe_jobs"; dataType: "string"; columnType: "SQLiteText"; data: string; driverParam: string; notNull: false; hasDefault: false; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; enumValues: [string, ...string[]]; baseColumn: never; generated: undefined; }, object>; createdAt: drizzle_orm_sqlite_core.SQLiteColumn<{ name: "created_at"; tableName: "dedupe_jobs"; dataType: "number"; columnType: "SQLiteInteger"; data: number; driverParam: number; notNull: true; hasDefault: false; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; enumValues: undefined; baseColumn: never; generated: undefined; }, object>; updatedAt: drizzle_orm_sqlite_core.SQLiteColumn<{ name: "updated_at"; tableName: "dedupe_jobs"; dataType: "number"; columnType: "SQLiteInteger"; data: number; driverParam: number; notNull: true; hasDefault: false; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; enumValues: undefined; baseColumn: never; generated: undefined; }, object>; }; dialect: "sqlite"; }>; declare const rateLimitTable: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{ name: "rate_limits"; schema: undefined; columns: { resource: drizzle_orm_sqlite_core.SQLiteColumn<{ name: "resource"; tableName: "rate_limits"; dataType: "string"; columnType: "SQLiteText"; data: string; driverParam: string; notNull: true; hasDefault: false; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; enumValues: [string, ...string[]]; baseColumn: never; generated: undefined; }, object>; timestamp: drizzle_orm_sqlite_core.SQLiteColumn<{ name: "timestamp"; tableName: "rate_limits"; dataType: "number"; columnType: "SQLiteInteger"; data: number; driverParam: number; notNull: true; hasDefault: false; isPrimaryKey: false; isAutoincrement: false; hasRuntimeDefault: false; enumValues: undefined; baseColumn: never; generated: undefined; }, object>; id: drizzle_orm_sqlite_core.SQLiteColumn<{ name: "id"; tableName: "rate_limits"; dataType: "number"; columnType: "SQLiteInteger"; data: number; driverParam: number; notNull: true; hasDefault: true; isPrimaryKey: true; isAutoincrement: false; hasRuntimeDefault: false; enumValues: undefined; baseColumn: never; generated: undefined; }, object>; }; dialect: "sqlite"; }>; type CacheRow = typeof cacheTable.$inferSelect; type DedupeRow = typeof dedupeTable.$inferSelect; type RateLimitRow = typeof rateLimitTable.$inferSelect; export { type CacheRow, type DedupeRow, type RateLimitRow, SQLiteCacheStore, type SQLiteCacheStoreOptions, SQLiteDedupeStore, type SQLiteDedupeStoreOptions, SQLiteRateLimitStore, type SQLiteRateLimitStoreOptions, SqliteAdaptiveRateLimitStore, type SqliteAdaptiveRateLimitStoreOptions, cacheTable, dedupeTable, rateLimitTable };