UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

637 lines 20.1 kB
import { Alepha, MiddlewareMetadata, OPTIONS, Primitive, Service, Static } from "alepha"; import { DateTimeProvider, DurationLike, Timeout } from "alepha/datetime"; //#region ../../src/cache/core/providers/CacheProvider.d.ts /** * Cache provider interface. * * All methods are asynchronous and return promises. * Values are stored as Uint8Array. */ declare abstract class CacheProvider { protected encoder: TextEncoder; protected decoder: TextDecoder; protected codes: { BINARY: number; JSON: number; STRING: number; COMPRESSED: number; }; /** * Get the value of a key. * * @param name Cache name, used to group keys. Should be Redis-like "some:group:name" format. * @param key The key of the value to get. * * @return The value of the key, or undefined if the key does not exist. */ abstract get(name: string, key: string): Promise<Uint8Array | undefined>; /** * Set the string value of a key. * * @param name Cache name, used to group keys. Should be Redis-like "some:group:name" format. * @param key The key of the value to set. * @param value The value to set. * @param ttl The time-to-live of the key, in milliseconds. * * @return The value of the key. */ abstract set(name: string, key: string, value: Uint8Array, ttl?: number): Promise<Uint8Array>; /** * Remove the specified keys. * * @param name Cache name, used to group keys. Should be Redis-like "some:group:name" format. * @param keys The keys to delete. */ abstract del(name: string, ...keys: string[]): Promise<void>; abstract has(name: string, key: string): Promise<boolean>; abstract keys(name: string, filter?: string): Promise<string[]>; /** * Remove all keys from all cache names. */ abstract clear(): Promise<void>; /** * Increment the integer value of a key by the given amount. * * If the key does not exist, it is set to 0 before performing the operation. * This operation is atomic when using Redis. * * @param name Cache name, used to group keys. * @param key The key to increment. * @param amount The amount to increment by. * @returns The new value after incrementing. */ abstract incr(name: string, key: string, amount: number): Promise<number>; /** * Set a typed value with automatic serialization and optional compression. */ setTyped(name: string, key: string, value: unknown, options?: { ttl?: number; compress?: boolean; }): Promise<void>; /** * Get a typed value with automatic deserialization and optional decompression. */ getTyped<T>(name: string, key: string): Promise<T | undefined>; /** * Invalidate cache keys with wildcard support. * * Keys ending in `*` are expanded via `this.keys()`. * Called with no keys, delegates to `this.del(name)` which clears the entire container. */ invalidateKeys(name: string, keys: string[]): Promise<void>; /** * Serialize a value to a typed Uint8Array with a leading type marker byte. */ protected serialize(value: unknown): Uint8Array; /** * Deserialize a typed Uint8Array back to the original value. */ protected deserialize<T>(uint8Array: Uint8Array): T; /** * Compress data with gzip, prepending a COMPRESSED marker byte. */ protected compress(data: Uint8Array): Promise<Uint8Array>; /** * Decompress gzipped data. */ protected decompress(data: Uint8Array): Promise<Uint8Array>; } //#endregion //#region ../../src/cache/core/primitives/$cache.d.ts /** * Creates a cache primitive for caching with automatic management. * * **Middleware mode** (no `handler`) — usable in `use` arrays AND as a store: * ```ts * class UserService { * userCache = $cache({ name: "users", ttl: [10, "minutes"] }); * * fetchUser = $pipeline({ * use: [this.userCache], * handler: async (userId: string) => this.repo.getById(userId), * }); * * async invalidateUser(userId: string) { * await this.userCache.invalidate(userId); * } * } * ``` * * **Primitive mode** (with `handler`) — standalone callable: * ```ts * getUserData = $cache({ * name: "user-data", * ttl: [10, "minutes"], * handler: async (userId: string) => { * return await database.users.findById(userId); * } * }); * ``` */ declare function $cache<TReturn = string, TParameter extends any[] = any[]>(options: CachePrimitiveOptions<TReturn, TParameter> & { handler: (...args: TParameter) => TReturn; }): CachePrimitiveFn<TReturn, TParameter>; declare function $cache<TReturn = any, TParameter extends any[] = any[]>(options?: CachePrimitiveOptions<TReturn, TParameter>): CacheMiddlewareFn<TReturn>; /** * Options for the in-memory L1 tier. */ interface CacheMemoryTierOptions { /** * TTL for the in-memory tier. Should be ≤ the remote `ttl`. * Bounds the cross-isolate staleness window after invalidation. * * @default min(ttl, 30s) */ ttl?: DurationLike; /** * LRU bound — max entries kept in memory before eviction. * * @default 500 */ max?: number; /** * Also cache provider misses (`undefined`) in memory for this duration. * Prevents hammering the remote tier on cold/unknown keys. * * @default off */ negative?: DurationLike; } interface CachePrimitiveOptions<TReturn = any, TParameter extends any[] = any[]> { /** * The cache name. This is useful for invalidating multiple caches at once. * * Store key as `cache:$name:$key`. * * @default Name of the key of the class. */ name?: string; /** * Function which returns cached data. */ handler?: (...args: TParameter) => TReturn; /** * The key generator for the cache. * If not provided, the arguments will be json.stringify(). */ key?: (...args: TParameter) => string; /** * The store provider for the cache. * * Accepts: * - `"memory"` — short-circuits to {@link MemoryCacheProvider} regardless * of the default `CacheProvider` binding. Useful for caches that must * stay process-local (e.g. ETag, HTTP client). * - A {@link CacheProvider} class (concrete OR abstract) — resolved via * `alepha.inject(...)` at primitive construction. Use this to opt a * specific cache into a non-default backend (e.g. * `provider: DatabaseCacheProvider` to keep one cache in SQL while the * rest of the app uses Cloudflare KV). * - `undefined` — falls back to whatever is bound to `CacheProvider` in * the container. On Cloudflare workers this is * {@link CloudflareKVProvider} by default; on Node it's * {@link MemoryCacheProvider}. * * Note: passing an *abstract* class works because Alepha's DI resolves * through substitutions, e.g. `alepha.with({ provide: CacheProvider, use: * MyCacheProvider })`. */ provider?: Service<CacheProvider> | "memory"; /** * The time-to-live for the cache in seconds. * Set 0 to skip expiration. * * @default 300 (5 minutes). */ ttl?: DurationLike; /** * If the cache is disabled. */ disabled?: boolean; /** * Enable gzip compression for cached values. * Reduces storage size by 60-80% for JSON payloads at the cost of CPU. */ compress?: boolean; /** * Add an in-process L1 memory tier in front of `provider`. * * Reads check memory first, fall back to the provider on miss. Writes go * to both tiers (write-through), so own-writes are immediately visible. * * Caveats: * - Per-process only. Each Worker isolate / Node process has its own L1. * `invalidate()` clears the local L1 + the remote provider; other * processes keep their L1 until its TTL expires. * - Use a short L1 TTL to bound the cross-isolate staleness window. * * @default off */ memory?: true | CacheMemoryTierOptions; /** * Stale-while-revalidate window. After `ttl` expires, the cached value * remains servable for `stale` longer; reads in this window return the * stale value immediately and trigger ONE background refresh * (single-flight per key). * * Requires a `handler` (primitive mode) OR middleware mode wrapping a * handler — the cache needs to know how to recompute. * * @default off */ stale?: DurationLike; } /** * Cache configuration atom. */ declare const cacheOptions: import("alepha").Atom<import("zod").ZodObject<{ enabled: import("zod").ZodDefault<import("zod").ZodBoolean>; defaultTtl: import("zod").ZodDefault<import("zod").ZodNumber>; }, import("zod/v4/core").$strip>, "alepha.cache.options">; type CacheAtomOptions = Static<typeof cacheOptions.schema>; declare module "alepha" { interface State { [cacheOptions.key]: CacheAtomOptions; } } declare const SWR_MARKER: "__swr"; type SwrEnvelope = { [SWR_MARKER]: 1; v: unknown; f: number; }; type L1Entry<T> = { value: T | undefined; expiresAt: number; negative: boolean; }; type ReadResult<T> = { value: T | undefined; stale: boolean; }; declare class CachePrimitive<TReturn = any, TParameter extends any[] = any[]> extends Primitive<CachePrimitiveOptions<TReturn, TParameter>> { protected readonly settings: Readonly<{ enabled: boolean; defaultTtl: number; }>; protected readonly dateTimeProvider: DateTimeProvider; readonly provider: CacheProvider; protected readonly memoryStore?: Map<string, L1Entry<TReturn>>; protected readonly memoryMax: number; protected readonly memoryTtlMs: number; protected readonly negativeTtlMs: number; protected readonly inflightRefreshes: Map<string, Promise<TReturn>>; constructor(args: ConstructorParameters<typeof Primitive<CachePrimitiveOptions<TReturn, TParameter>>>[0]); get container(): string; run(...args: TParameter): Promise<TReturn>; key(...args: TParameter): string; incr(key: string, amount?: number): Promise<number>; invalidate(...keys: string[]): Promise<void>; set(key: string, value: TReturn, ttl?: DurationLike): Promise<void>; get(key: string): Promise<TReturn | undefined>; /** * Internal read that also reports whether the value is stale (SWR * grace window). Middleware and `run()` use this to decide whether to * schedule a background refresh. */ read(key: string): Promise<ReadResult<TReturn>>; /** * Run a handler under single-flight: concurrent callers for the same * key share one in-flight promise. */ runSingleFlight(key: string, handler: () => Promise<TReturn> | TReturn): Promise<TReturn>; /** * Schedule a background refresh for a stale key. At most one refresh * per key is in-flight at any time; failures are swallowed (the stale * value keeps being served until expiry). */ scheduleRefresh(key: string, handler: () => Promise<TReturn> | TReturn): void; protected $provider(): CacheProvider; protected isSwrEnvelope(value: unknown): value is SwrEnvelope; protected setL1(key: string, entry: L1Entry<TReturn>): void; protected delL1(key: string): void; } interface CachePrimitiveFn<TReturn = any, TParameter extends any[] = any[]> extends CachePrimitive<TReturn, TParameter> { /** * Run the cache primitive with the provided arguments. */ (...args: TParameter): Promise<TReturn>; } /** * Cache middleware + store. Callable as middleware `(handler) => wrappedHandler` * AND exposes store methods (`.get()`, `.set()`, `.invalidate()`, `.incr()`). */ interface CacheMiddlewareFn<TReturn = any> extends CachePrimitive<TReturn, any[]> { <T extends (...args: any[]) => any>(handler: T): T; [OPTIONS]?: MiddlewareMetadata; } //#endregion //#region ../../src/cache/core/providers/CloudflareKVProvider.d.ts /** * KVNamespace interface matching Cloudflare's KV API. */ interface KVNamespace { get(key: string, type: "arrayBuffer"): Promise<ArrayBuffer | null>; get(key: string, type: "text"): Promise<string | null>; get(key: string, type?: "text"): Promise<string | null>; put(key: string, value: string | ArrayBuffer | ReadableStream, options?: KVPutOptions): Promise<void>; delete(key: string): Promise<void>; list(options?: KVListOptions): Promise<KVListResult>; } interface KVPutOptions { expiration?: number; expirationTtl?: number; metadata?: unknown; } interface KVListOptions { prefix?: string; limit?: number; cursor?: string; } interface KVListResult { keys: KVListKey[]; list_complete: boolean; cursor?: string; } interface KVListKey { name: string; expiration?: number; metadata?: unknown; } /** * Default KV binding name used in wrangler configuration. */ declare const KV_DEFAULT_BINDING = "KV_CACHE"; /** * Cloudflare KV cache provider. * * Uses a KV namespace binding for all cache operations. * Keys are stored as: `cache:{name}:{key}` * * **Required Cloudflare binding:** * - `KV_CACHE` - A KV namespace binding in wrangler configuration * * @example * ```toml * # wrangler.toml - automatically generated by alepha build * [[kv_namespaces]] * binding = "KV_CACHE" * id = "abc123" * ``` */ declare class CloudflareKVProvider extends CacheProvider { protected readonly alepha: Alepha; protected readonly log: import("alepha/logger").Logger; protected kv?: KVNamespace; protected readonly onStart: import("alepha").HookPrimitive<"start">; get(name: string, key: string): Promise<Uint8Array | undefined>; set(name: string, key: string, value: Uint8Array, ttl?: number): Promise<Uint8Array>; del(name: string, ...keys: string[]): Promise<void>; has(name: string, key: string): Promise<boolean>; keys(name: string, filter?: string): Promise<string[]>; clear(): Promise<void>; incr(name: string, key: string, amount: number): Promise<number>; /** * Build the full KV key: `cache:{name}:{key}` */ protected prefix(...path: string[]): string; protected getKV(): KVNamespace; /** * List all keys matching a prefix, handling pagination. */ protected listAllKeys(prefix: string): Promise<string[]>; } //#endregion //#region ../../src/cache/core/providers/MemoryCacheProvider.d.ts type CacheName = string; type CacheKey = string; type CacheValue = { data?: Uint8Array; timeout?: Timeout; }; interface MemoryCacheCall { name: string; key: string; timestamp: number; } interface MemoryCacheSetCall extends MemoryCacheCall { value: Uint8Array; ttl?: number; } interface MemoryCacheDelCall { name: string; keys: string[]; timestamp: number; } interface MemoryCacheStats { hits: number; misses: number; sets: number; deletes: number; } interface MemoryCacheProviderOptions { /** * Error to throw on get operations (for testing error handling) */ getError?: Error | null; /** * Error to throw on set operations (for testing error handling) */ setError?: Error | null; /** * Error to throw on del operations (for testing error handling) */ delError?: Error | null; } /** * In-memory implementation of CacheProvider for testing. * * This provider stores all cache entries in memory, making it ideal for * unit tests that need to verify cache operations without touching Redis or other backends. * * @example * ```typescript * // In tests, substitute the real CacheProvider with MemoryCacheProvider * const alepha = Alepha.create().with({ * provide: CacheProvider, * use: MemoryCacheProvider, * }); * * // Run code that uses caching * const service = alepha.inject(MyService); * await service.fetchWithCache("key"); * * // Verify cache behavior * const cache = alepha.inject(MemoryCacheProvider); * expect(cache.stats().misses).toBe(1); * await service.fetchWithCache("key"); * expect(cache.stats().hits).toBe(1); * ``` */ declare class MemoryCacheProvider extends CacheProvider { protected readonly dateTimeProvider: DateTimeProvider; protected readonly log: import("alepha/logger").Logger; protected store: Record<CacheName, Record<CacheKey, CacheValue>>; /** * All recorded get calls. */ getCalls: MemoryCacheCall[]; /** * All recorded set calls. */ setCalls: MemoryCacheSetCall[]; /** * All recorded del calls. */ delCalls: MemoryCacheDelCall[]; /** * Cache statistics. */ protected _stats: MemoryCacheStats; /** * Error to throw on get (for testing error handling) */ getError: Error | null; /** * Error to throw on set (for testing error handling) */ setError: Error | null; /** * Error to throw on del (for testing error handling) */ delError: Error | null; constructor(options?: MemoryCacheProviderOptions); get(name: string, key: string): Promise<Uint8Array | undefined>; set(name: string, key: string, value: Uint8Array, ttl?: number): Promise<Uint8Array>; del(name: string, ...keys: string[]): Promise<void>; has(name: string, key: string): Promise<boolean>; keys(name: string, filter?: string): Promise<string[]>; clear(): Promise<void>; incr(name: string, key: string, amount: number): Promise<number>; /** * Get cache statistics (hits, misses, sets, deletes). * * @example * ```typescript * expect(cache.stats().hits).toBe(1); * expect(cache.stats().misses).toBe(0); * ``` */ stats(): MemoryCacheStats; /** * Check if a key was set during the test. * * @example * ```typescript * expect(cache.wasSet("my-cache", "user:123")).toBe(true); * ``` */ wasSet(name: string, key?: string): boolean; /** * Check if a key was retrieved during the test. * * @example * ```typescript * expect(cache.wasGet("my-cache", "user:123")).toBe(true); * ``` */ wasGet(name: string, key?: string): boolean; /** * Check if a key was deleted during the test. * * @example * ```typescript * expect(cache.wasDeleted("my-cache", "user:123")).toBe(true); * ``` */ wasDeleted(name: string, key?: string): boolean; /** * Get the number of cached entries for a specific cache name. * * @example * ```typescript * expect(cache.size("my-cache")).toBe(5); * ``` */ size(name?: string): number; /** * Get all cache names. * * @example * ```typescript * expect(cache.names()).toContain("my-cache"); * ``` */ names(): string[]; /** * Reset all in-memory state (useful between tests). * * @example * ```typescript * beforeEach(() => { * cache.reset(); * }); * ``` */ reset(): void; } //#endregion //#region ../../src/cache/core/index.d.ts declare module "alepha" { interface Hooks { /** * Fires when a cache lookup finds a value. */ "cache:hit": { container: string; key: string; }; /** * Fires when a cache lookup does not find a value. */ "cache:miss": { container: string; key: string; }; /** * Fires when a value is written to the cache. */ "cache:set": { container: string; key: string; ttlMs?: number; }; /** * Fires when a stale value (SWR grace window) is served and a * background refresh is scheduled. */ "cache:stale": { container: string; key: string; }; /** * Fires when a background SWR refresh completes successfully and * the value has been written back to the cache. */ "cache:revalidate": { container: string; key: string; }; } } /** * Type-safe caching with TTL support. * * **Features:** * - Cached computations with type-safe keys and values * - Configurable TTL * - Cache invalidation * - Automatic cache population * - Providers: Memory (default), Redis * * @module alepha.cache */ declare const AlephaCache: import("alepha").Service<import("alepha").Module>; //#endregion export { $cache, AlephaCache, CacheAtomOptions, CacheMemoryTierOptions, CacheMiddlewareFn, CachePrimitive, CachePrimitiveFn, CachePrimitiveOptions, CacheProvider, CloudflareKVProvider, KVListKey, KVListOptions, KVListResult, KVNamespace, KVPutOptions, KV_DEFAULT_BINDING, MemoryCacheCall, MemoryCacheDelCall, MemoryCacheProvider, MemoryCacheProviderOptions, MemoryCacheSetCall, MemoryCacheStats, cacheOptions }; //# sourceMappingURL=index.d.ts.map