@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
TypeScript
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 };