@zudojs/cache 0.1.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +90 -14
- package/dist/cache.d.ts +107 -18
- package/dist/cache.js +399 -33
- package/dist/constants.d.ts +46 -12
- package/dist/constants.js +47 -19
- package/dist/index.d.ts +10 -9
- package/dist/index.js +9 -8
- package/dist/invalidation.d.ts +23 -16
- package/dist/invalidation.js +43 -33
- package/dist/key-builder.d.ts +15 -0
- package/dist/key-builder.js +43 -3
- package/dist/lock.d.ts +36 -2
- package/dist/lock.js +160 -30
- package/dist/memory.d.ts +63 -7
- package/dist/memory.js +223 -39
- package/dist/metrics.d.ts +10 -0
- package/dist/metrics.js +49 -4
- package/dist/serializer.d.ts +19 -10
- package/dist/serializer.js +88 -21
- package/dist/store.d.ts +21 -11
- package/dist/store.js +125 -65
- package/dist/tags.d.ts +32 -12
- package/dist/tags.js +113 -33
- package/dist/types-adapter.d.ts +23 -9
- package/dist/types-config.d.ts +46 -9
- package/dist/types-health.d.ts +5 -2
- package/dist/types-keys.d.ts +8 -4
- package/dist/types-lock.d.ts +8 -0
- package/dist/types-operations.d.ts +13 -20
- package/dist/types-results.d.ts +7 -1
- package/dist/types-tags.d.ts +9 -0
- package/dist/types-utility.d.ts +6 -10
- package/dist/types-values.d.ts +9 -14
- package/dist/types.d.ts +6 -6
- package/dist/utils.d.ts +65 -0
- package/dist/utils.js +210 -0
- package/package.json +25 -14
- package/dist/.tsbuildinfo +0 -1
- package/dist/cache.d.ts.map +0 -1
- package/dist/cache.js.map +0 -1
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/invalidation.d.ts.map +0 -1
- package/dist/invalidation.js.map +0 -1
- package/dist/key-builder.d.ts.map +0 -1
- package/dist/key-builder.js.map +0 -1
- package/dist/lock.d.ts.map +0 -1
- package/dist/lock.js.map +0 -1
- package/dist/memory.d.ts.map +0 -1
- package/dist/memory.js.map +0 -1
- package/dist/metrics.d.ts.map +0 -1
- package/dist/metrics.js.map +0 -1
- package/dist/serializer.d.ts.map +0 -1
- package/dist/serializer.js.map +0 -1
- package/dist/store.d.ts.map +0 -1
- package/dist/store.js.map +0 -1
- package/dist/tags.d.ts.map +0 -1
- package/dist/tags.js.map +0 -1
- package/dist/types-adapter.d.ts.map +0 -1
- package/dist/types-adapter.js.map +0 -1
- package/dist/types-config.d.ts.map +0 -1
- package/dist/types-config.js.map +0 -1
- package/dist/types-events.d.ts.map +0 -1
- package/dist/types-events.js.map +0 -1
- package/dist/types-health.d.ts.map +0 -1
- package/dist/types-health.js.map +0 -1
- package/dist/types-keys.d.ts.map +0 -1
- package/dist/types-keys.js.map +0 -1
- package/dist/types-lock.d.ts.map +0 -1
- package/dist/types-lock.js.map +0 -1
- package/dist/types-metrics.d.ts.map +0 -1
- package/dist/types-metrics.js.map +0 -1
- package/dist/types-operations.d.ts.map +0 -1
- package/dist/types-operations.js.map +0 -1
- package/dist/types-results.d.ts.map +0 -1
- package/dist/types-results.js.map +0 -1
- package/dist/types-tags.d.ts.map +0 -1
- package/dist/types-tags.js.map +0 -1
- package/dist/types-utility.d.ts.map +0 -1
- package/dist/types-utility.js.map +0 -1
- package/dist/types-values.d.ts.map +0 -1
- package/dist/types-values.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
package/dist/types-config.d.ts
CHANGED
|
@@ -1,21 +1,58 @@
|
|
|
1
1
|
import type { CacheNamespace } from "./types-keys.js";
|
|
2
2
|
import type { CacheTTL } from "./types-values.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { CacheMiddleware } from "./types-metrics.js";
|
|
4
|
+
import type { CacheSerializer } from "./types-health.js";
|
|
5
|
+
import type { CacheLockStore } from "./types-lock.js";
|
|
4
6
|
export interface CacheConfig {
|
|
5
7
|
readonly enabled?: boolean;
|
|
6
8
|
readonly defaultTtl?: CacheTTL;
|
|
7
9
|
readonly namespace?: CacheNamespace;
|
|
8
10
|
readonly prefix?: string;
|
|
9
11
|
readonly separator?: string;
|
|
12
|
+
/**
|
|
13
|
+
* When true, adapter failures are swallowed and a neutral result is
|
|
14
|
+
* returned instead of throwing, for every fallible operation:
|
|
15
|
+
* `get`/`has` report a miss, `set`/`delete` report a no-op,
|
|
16
|
+
* `clear`/`invalidateByTag`/`invalidateByPattern` report `cleared: 0`,
|
|
17
|
+
* `ttl` returns `undefined` and `expire` returns `false`.
|
|
18
|
+
*
|
|
19
|
+
* Two deliberate exclusions:
|
|
20
|
+
* - Key/pattern/TTL **validation** errors always throw. A malformed key is
|
|
21
|
+
* programmer error, not an adapter fault, and silently ignoring it would
|
|
22
|
+
* hide a bug rather than degrade gracefully.
|
|
23
|
+
* - `withLock` always throws when the lock cannot be acquired or its lease
|
|
24
|
+
* is lost. Running a critical section without mutual exclusion is never
|
|
25
|
+
* a safe degradation.
|
|
26
|
+
*/
|
|
10
27
|
readonly failSilently?: boolean;
|
|
11
28
|
readonly collectStats?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Optional serializer applied by CacheService: values are serialized on
|
|
31
|
+
* set and deserialized on get, so cached values are structural copies.
|
|
32
|
+
* When omitted, values are stored by reference (memory adapter).
|
|
33
|
+
*/
|
|
34
|
+
readonly serializer?: CacheSerializer;
|
|
35
|
+
/**
|
|
36
|
+
* Middlewares wrapping every adapter operation, applied in order (the
|
|
37
|
+
* first entry is outermost). Used for tracing, circuit-breaking,
|
|
38
|
+
* per-operation timeouts, and similar cross-cutting concerns.
|
|
39
|
+
*/
|
|
40
|
+
readonly middlewares?: readonly CacheMiddleware[];
|
|
41
|
+
/**
|
|
42
|
+
* Lock store backing `withLock`. Defaults to a fresh in-process store per
|
|
43
|
+
* service. Pass a shared store (e.g. the exported `defaultLockStore`, or a
|
|
44
|
+
* Redis-backed implementation) to share locks across service instances or
|
|
45
|
+
* processes.
|
|
46
|
+
*/
|
|
47
|
+
readonly lockStore?: CacheLockStore;
|
|
12
48
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Error codes set by errors this package constructs directly. Every member
|
|
51
|
+
* is produced by some code path in `@zudojs/cache`.
|
|
52
|
+
*
|
|
53
|
+
* NOTE: errors created by the re-exported `@zudojs/errors` cache factories
|
|
54
|
+
* (`cacheConnectionError`, `cacheInvalidKeyError`, …) carry that package's
|
|
55
|
+
* generic `ErrorCode` values, not these.
|
|
56
|
+
*/
|
|
57
|
+
export type CacheErrorCode = "CACHE_DISABLED" | "CACHE_OPERATION_FAILED" | "CACHE_INVALID_TTL" | "CACHE_MIDDLEWARE_RESULT_MISSING" | "CACHE_LOCK_UNAVAILABLE" | "CACHE_LOCK_ACQUIRE_FAILED" | "CACHE_LOCK_LOST";
|
|
21
58
|
//# sourceMappingURL=types-config.d.ts.map
|
package/dist/types-health.d.ts
CHANGED
|
@@ -4,6 +4,11 @@ export interface CacheHealth {
|
|
|
4
4
|
readonly latencyMs?: number;
|
|
5
5
|
readonly checkedAt: Date;
|
|
6
6
|
readonly error?: string;
|
|
7
|
+
/**
|
|
8
|
+
* True when the cache is configured `enabled: false`. The check is
|
|
9
|
+
* reported healthy without touching the adapter.
|
|
10
|
+
*/
|
|
11
|
+
readonly disabled?: boolean;
|
|
7
12
|
}
|
|
8
13
|
export interface CacheHealthChecker {
|
|
9
14
|
healthCheck(): Promise<CacheHealth>;
|
|
@@ -14,7 +19,5 @@ export interface CacheSerializer<TValue = unknown, TSerialized = unknown> {
|
|
|
14
19
|
}
|
|
15
20
|
export interface CacheSerializationOptions {
|
|
16
21
|
readonly serializer?: CacheSerializer;
|
|
17
|
-
readonly compress?: boolean;
|
|
18
|
-
readonly encryption?: boolean;
|
|
19
22
|
}
|
|
20
23
|
//# sourceMappingURL=types-health.d.ts.map
|
package/dist/types-keys.d.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
export type CacheKey = string;
|
|
2
2
|
export type CacheNamespace = string;
|
|
3
|
-
export interface CacheKeyParts {
|
|
4
|
-
readonly namespace?: CacheNamespace;
|
|
5
|
-
readonly key: CacheKey;
|
|
6
|
-
}
|
|
7
3
|
export interface CacheKeyOptions {
|
|
8
4
|
readonly namespace?: CacheNamespace;
|
|
9
5
|
readonly prefix?: string;
|
|
@@ -11,5 +7,13 @@ export interface CacheKeyOptions {
|
|
|
11
7
|
}
|
|
12
8
|
export interface CacheKeyBuilder {
|
|
13
9
|
build(key: string, options?: CacheKeyOptions): CacheKey;
|
|
10
|
+
/**
|
|
11
|
+
* Builds a fully-qualified glob pattern for pattern-based operations.
|
|
12
|
+
*
|
|
13
|
+
* Prefix and namespace are identity parts, never glob parts, so they are
|
|
14
|
+
* validated exactly as `build()` validates them. Only the trailing
|
|
15
|
+
* pattern segment may contain the glob metacharacters `*` and `?`.
|
|
16
|
+
*/
|
|
17
|
+
buildPattern?(pattern: string, options?: CacheKeyOptions): string;
|
|
14
18
|
}
|
|
15
19
|
//# sourceMappingURL=types-keys.d.ts.map
|
package/dist/types-lock.d.ts
CHANGED
|
@@ -2,6 +2,10 @@ import type { CacheNamespace } from "./types-keys.js";
|
|
|
2
2
|
import type { CacheTTL } from "./types-values.js";
|
|
3
3
|
export interface CacheLockOptions {
|
|
4
4
|
readonly ttl?: CacheTTL;
|
|
5
|
+
/**
|
|
6
|
+
* Scopes the lock. Two callers using the same key under different
|
|
7
|
+
* namespaces do not contend with each other.
|
|
8
|
+
*/
|
|
5
9
|
readonly namespace?: CacheNamespace;
|
|
6
10
|
readonly retry?: {
|
|
7
11
|
readonly attempts: number;
|
|
@@ -18,5 +22,9 @@ export interface CacheLock {
|
|
|
18
22
|
}
|
|
19
23
|
export interface CacheLockStore {
|
|
20
24
|
acquire(key: string, options?: CacheLockOptions): Promise<CacheLock | null>;
|
|
25
|
+
/** Drops expired leases. Called opportunistically by in-process stores. */
|
|
26
|
+
sweepExpired?(now?: number): void;
|
|
27
|
+
/** Releases every held lease. Intended for tests and shutdown. */
|
|
28
|
+
clear?(): void;
|
|
21
29
|
}
|
|
22
30
|
//# sourceMappingURL=types-lock.d.ts.map
|
|
@@ -1,39 +1,32 @@
|
|
|
1
|
-
import type { CacheNamespace } from "./types-keys.js";
|
|
2
1
|
import type { CacheTTL } from "./types-values.js";
|
|
2
|
+
/**
|
|
3
|
+
* Adapter-level operation options.
|
|
4
|
+
*
|
|
5
|
+
* NOTE: Adapters always receive fully-qualified keys. Namespace and prefix
|
|
6
|
+
* resolution happens in `CacheService` / the key builder, so adapter-level
|
|
7
|
+
* options intentionally carry no `namespace` field — every option present
|
|
8
|
+
* here is honored by the adapter.
|
|
9
|
+
*/
|
|
3
10
|
export interface CacheSetOptions {
|
|
4
11
|
readonly ttl?: CacheTTL;
|
|
5
|
-
readonly namespace?: CacheNamespace;
|
|
6
12
|
readonly tags?: readonly string[];
|
|
13
|
+
/**
|
|
14
|
+
* When `false`, the set is skipped if the key already exists (the result
|
|
15
|
+
* carries `skipped: true` and `success: false`). Defaults to `true`.
|
|
16
|
+
*/
|
|
7
17
|
readonly overwrite?: boolean;
|
|
8
18
|
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
9
19
|
}
|
|
10
|
-
export interface CacheGetOptions {
|
|
11
|
-
readonly namespace?: CacheNamespace;
|
|
12
|
-
}
|
|
13
|
-
export interface CacheDeleteOptions {
|
|
14
|
-
readonly namespace?: CacheNamespace;
|
|
15
|
-
}
|
|
16
|
-
export interface CacheHasOptions {
|
|
17
|
-
readonly namespace?: CacheNamespace;
|
|
18
|
-
}
|
|
19
20
|
export interface CacheClearOptions {
|
|
20
|
-
|
|
21
|
+
/** Glob pattern of fully-qualified keys to clear. Omit to clear everything. */
|
|
21
22
|
readonly pattern?: string;
|
|
22
23
|
}
|
|
23
24
|
export interface CacheKeysOptions {
|
|
24
|
-
readonly namespace?: CacheNamespace;
|
|
25
25
|
readonly pattern?: string;
|
|
26
26
|
readonly limit?: number;
|
|
27
27
|
}
|
|
28
|
-
export interface CacheGetManyOptions {
|
|
29
|
-
readonly namespace?: CacheNamespace;
|
|
30
|
-
}
|
|
31
28
|
export interface CacheSetManyOptions {
|
|
32
29
|
readonly ttl?: CacheTTL;
|
|
33
|
-
readonly namespace?: CacheNamespace;
|
|
34
30
|
readonly overwrite?: boolean;
|
|
35
31
|
}
|
|
36
|
-
export interface CacheDeleteManyOptions {
|
|
37
|
-
readonly namespace?: CacheNamespace;
|
|
38
|
-
}
|
|
39
32
|
//# sourceMappingURL=types-operations.d.ts.map
|
package/dist/types-results.d.ts
CHANGED
|
@@ -3,12 +3,19 @@ import type { CacheEntry } from "./types-values.js";
|
|
|
3
3
|
export interface CacheGetResult<TValue = unknown> {
|
|
4
4
|
readonly hit: boolean;
|
|
5
5
|
readonly value: TValue | null;
|
|
6
|
+
/**
|
|
7
|
+
* The full entry (creation time, expiry, tags, metadata) when the adapter
|
|
8
|
+
* retains it and the read was a hit. The memory adapter always populates
|
|
9
|
+
* this on a hit.
|
|
10
|
+
*/
|
|
6
11
|
readonly entry?: CacheEntry<TValue>;
|
|
7
12
|
}
|
|
8
13
|
export interface CacheSetResult {
|
|
9
14
|
readonly success: boolean;
|
|
10
15
|
readonly key: CacheKey;
|
|
11
16
|
readonly expiresAt: Date | null;
|
|
17
|
+
/** True when the set was skipped because the key exists and `overwrite: false` was passed. */
|
|
18
|
+
readonly skipped?: boolean;
|
|
12
19
|
}
|
|
13
20
|
export interface CacheDeleteResult {
|
|
14
21
|
readonly deleted: boolean;
|
|
@@ -27,7 +34,6 @@ export interface CacheStats {
|
|
|
27
34
|
readonly sets: number;
|
|
28
35
|
readonly deletes: number;
|
|
29
36
|
readonly errors: number;
|
|
30
|
-
readonly size?: number;
|
|
31
37
|
readonly hitRate: number;
|
|
32
38
|
}
|
|
33
39
|
//# sourceMappingURL=types-results.d.ts.map
|
package/dist/types-tags.d.ts
CHANGED
|
@@ -2,6 +2,11 @@ import type { CacheKey, CacheNamespace } from "./types-keys.js";
|
|
|
2
2
|
import type { CacheClearResult } from "./types-results.js";
|
|
3
3
|
export type CacheTag = string;
|
|
4
4
|
export interface CacheTagOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Scopes the tag. Tags registered under one namespace are invisible to
|
|
7
|
+
* lookups and invalidations made under another, so one tenant's
|
|
8
|
+
* `invalidateByTag` can never reach another tenant's entries.
|
|
9
|
+
*/
|
|
5
10
|
readonly namespace?: CacheNamespace;
|
|
6
11
|
}
|
|
7
12
|
export interface CacheTagStore {
|
|
@@ -9,5 +14,9 @@ export interface CacheTagStore {
|
|
|
9
14
|
remove(key: CacheKey, tags: readonly CacheTag[], options?: CacheTagOptions): Promise<void>;
|
|
10
15
|
getKeys(tag: CacheTag, options?: CacheTagOptions): Promise<readonly CacheKey[]>;
|
|
11
16
|
invalidate(tag: CacheTag, options?: CacheTagOptions): Promise<CacheClearResult>;
|
|
17
|
+
/** Removes all tag mappings for a key, across every namespace. */
|
|
18
|
+
removeKey?(key: CacheKey): void;
|
|
19
|
+
/** Removes all tag mappings. */
|
|
20
|
+
clear?(): void;
|
|
12
21
|
}
|
|
13
22
|
//# sourceMappingURL=types-tags.d.ts.map
|
package/dist/types-utility.d.ts
CHANGED
|
@@ -1,31 +1,27 @@
|
|
|
1
|
-
import type { CacheKey } from "./types-keys.js";
|
|
1
|
+
import type { CacheKey, CacheNamespace } from "./types-keys.js";
|
|
2
2
|
import type { MaybePromise as BaseMaybePromise } from "@zudojs/types";
|
|
3
|
-
import type {
|
|
3
|
+
import type { CacheSetOptions } from "./types-operations.js";
|
|
4
4
|
export type { BaseMaybePromise as MaybePromise };
|
|
5
5
|
export interface CacheOrComputeOptions extends CacheSetOptions {
|
|
6
|
+
readonly namespace?: CacheNamespace;
|
|
6
7
|
readonly forceRefresh?: boolean;
|
|
7
8
|
}
|
|
8
9
|
export interface CacheOrComputeResult<TValue> {
|
|
9
10
|
readonly value: TValue;
|
|
10
11
|
readonly cached: boolean;
|
|
11
12
|
}
|
|
13
|
+
/** A single operation in a `CacheService.batch()` call. */
|
|
12
14
|
export interface CacheBatchOperation {
|
|
13
15
|
readonly type: "get" | "set" | "delete";
|
|
14
16
|
readonly key: CacheKey;
|
|
15
17
|
readonly value?: unknown;
|
|
16
|
-
readonly options?:
|
|
18
|
+
readonly options?: CacheSetOptions;
|
|
17
19
|
}
|
|
20
|
+
/** The outcome of one `CacheBatchOperation`, in submission order. */
|
|
18
21
|
export interface CacheBatchResult {
|
|
19
22
|
readonly operation: CacheBatchOperation;
|
|
20
23
|
readonly success: boolean;
|
|
21
24
|
readonly result?: unknown;
|
|
22
25
|
readonly error?: unknown;
|
|
23
26
|
}
|
|
24
|
-
export type CacheResult<TValue> = {
|
|
25
|
-
readonly success: true;
|
|
26
|
-
readonly value: TValue;
|
|
27
|
-
} | {
|
|
28
|
-
readonly success: false;
|
|
29
|
-
readonly error: unknown;
|
|
30
|
-
};
|
|
31
27
|
//# sourceMappingURL=types-utility.d.ts.map
|
package/dist/types-values.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { CacheKey } from "./types-keys.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export type
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Time-to-live in milliseconds. `null` means the entry never expires.
|
|
4
|
+
*/
|
|
5
|
+
export type CacheTTL = number | null;
|
|
6
|
+
export type CacheExpiration = CacheTTL | undefined;
|
|
7
|
+
/**
|
|
8
|
+
* A cached entry with its metadata, as returned on `CacheGetResult.entry`
|
|
9
|
+
* by adapters that retain per-entry metadata (the memory adapter does).
|
|
10
|
+
*/
|
|
10
11
|
export interface CacheEntry<TValue = unknown> {
|
|
11
12
|
readonly key: CacheKey;
|
|
12
13
|
readonly value: TValue;
|
|
@@ -15,10 +16,4 @@ export interface CacheEntry<TValue = unknown> {
|
|
|
15
16
|
readonly tags?: readonly string[];
|
|
16
17
|
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
17
18
|
}
|
|
18
|
-
export interface CacheEntryMetadata {
|
|
19
|
-
readonly createdAt?: Date;
|
|
20
|
-
readonly expiresAt?: Date | null;
|
|
21
|
-
readonly tags?: readonly string[];
|
|
22
|
-
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
23
|
-
}
|
|
24
19
|
//# sourceMappingURL=types-values.d.ts.map
|
package/dist/types.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
export type { CacheKey, CacheKeyOptions,
|
|
2
|
-
export type { CacheExpiration,
|
|
3
|
-
export type { CacheClearOptions,
|
|
1
|
+
export type { CacheKey, CacheKeyOptions, CacheNamespace, } from "./types-keys.js";
|
|
2
|
+
export type { CacheExpiration, CacheEntry, CacheTTL } from "./types-values.js";
|
|
3
|
+
export type { CacheClearOptions, CacheKeysOptions, CacheSetManyOptions, CacheSetOptions, } from "./types-operations.js";
|
|
4
4
|
export type { CacheClearResult, CacheDeleteManyResult, CacheDeleteResult, CacheGetResult, CacheSetResult, CacheStats, } from "./types-results.js";
|
|
5
|
-
export type { CacheAdapter,
|
|
5
|
+
export type { CacheAdapter, CacheStore } from "./types-adapter.js";
|
|
6
6
|
export type { CacheClearEvent, CacheDeleteEvent, CacheErrorEvent, CacheEvent, CacheEventHandler, CacheEventSubscription, CacheEventType, CacheHitEvent, CacheMissEvent, CacheSetEvent, } from "./types-events.js";
|
|
7
7
|
export type { CacheTag, CacheTagOptions, CacheTagStore } from "./types-tags.js";
|
|
8
8
|
export type { CacheLock, CacheLockOptions, CacheLockStore, } from "./types-lock.js";
|
|
9
9
|
export type { CacheHealth, CacheHealthChecker, CacheSerializer, CacheSerializationOptions, } from "./types-health.js";
|
|
10
10
|
export type { CacheMiddleware, CacheMiddlewareContext, CacheMetrics, CacheOperation, } from "./types-metrics.js";
|
|
11
|
-
export type { CacheConfig, CacheErrorCode
|
|
12
|
-
export type { CacheBatchOperation, CacheBatchResult, CacheOrComputeOptions, CacheOrComputeResult,
|
|
11
|
+
export type { CacheConfig, CacheErrorCode } from "./types-config.js";
|
|
12
|
+
export type { CacheBatchOperation, CacheBatchResult, CacheOrComputeOptions, CacheOrComputeResult, MaybePromise, } from "./types-utility.js";
|
|
13
13
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/cache — Internal Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared helpers: glob matching, sequential delete-many fallback,
|
|
5
|
+
* TTL/pattern validation, and a monotonic clock.
|
|
6
|
+
*/
|
|
7
|
+
import type { CacheDeleteManyResult, CacheDeleteResult, CacheKey, CacheTTL } from "./types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Monotonic milliseconds since process start. Unlike `Date.now()` this is
|
|
10
|
+
* unaffected by NTP steps, VM resume, or manual clock changes, so deadlines
|
|
11
|
+
* computed from it cannot be extended or collapsed by a wall-clock jump.
|
|
12
|
+
*
|
|
13
|
+
* Use `wallClockFor()` to convert a monotonic deadline back into a `Date`
|
|
14
|
+
* for human-facing fields.
|
|
15
|
+
*/
|
|
16
|
+
export declare function monotonicNow(): number;
|
|
17
|
+
/** Converts a monotonic timestamp into an approximate wall-clock `Date`. */
|
|
18
|
+
export declare function wallClockFor(monotonicMs: number): Date;
|
|
19
|
+
/** A compiled glob matcher. */
|
|
20
|
+
export type GlobMatcher = (value: string) => boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Compiles a glob pattern into a linear-time matcher.
|
|
23
|
+
*
|
|
24
|
+
* Semantics (deliberately segment-aware, so a wildcard cannot escape the
|
|
25
|
+
* scope it was written for):
|
|
26
|
+
* - `*` matches any run of characters **within a single key segment**; it
|
|
27
|
+
* never crosses the separator. `zudojs:*` therefore matches `zudojs:a`
|
|
28
|
+
* but not `zudojs:tenant:a`.
|
|
29
|
+
* - `?` matches exactly one character within a segment.
|
|
30
|
+
* - `**` as a whole segment matches zero or more whole segments, so
|
|
31
|
+
* `zudojs:**` matches every key under the `zudojs` prefix.
|
|
32
|
+
*
|
|
33
|
+
* The pattern is length-bounded (see `assertValidPattern`) and star runs are
|
|
34
|
+
* collapsed, so pathological patterns cannot cause catastrophic backtracking.
|
|
35
|
+
*/
|
|
36
|
+
export declare function createGlobMatcher(pattern: string, options?: {
|
|
37
|
+
readonly separator?: string;
|
|
38
|
+
}): GlobMatcher;
|
|
39
|
+
/**
|
|
40
|
+
* Validates a glob pattern before it is compiled or composed into a key
|
|
41
|
+
* pattern. Patterns are untrusted input on every pattern-based entry point,
|
|
42
|
+
* so they are bounded in length and restricted to the key alphabet plus the
|
|
43
|
+
* glob metacharacters and the separator.
|
|
44
|
+
*/
|
|
45
|
+
export declare function assertValidPattern(pattern: string): void;
|
|
46
|
+
/**
|
|
47
|
+
* Validates a single pattern segment supplied by a caller (the part that is
|
|
48
|
+
* appended after prefix/namespace). Only the key alphabet plus `*` and `?`
|
|
49
|
+
* have meaning; anything else is caller error.
|
|
50
|
+
*/
|
|
51
|
+
export declare function assertValidPatternPart(part: string, separator: string): void;
|
|
52
|
+
/**
|
|
53
|
+
* Deletes many keys by calling a single-key delete function sequentially.
|
|
54
|
+
* Shared fallback used by the memory adapter, the store, and the
|
|
55
|
+
* invalidation manager.
|
|
56
|
+
*/
|
|
57
|
+
export declare function deleteManyViaDelete(keys: readonly CacheKey[], deleteOne: (key: CacheKey) => Promise<CacheDeleteResult>): Promise<CacheDeleteManyResult>;
|
|
58
|
+
/**
|
|
59
|
+
* Validates a TTL value. `null` means "never expires" and `undefined`
|
|
60
|
+
* means "use the default"; both are valid. Finite numbers must be within
|
|
61
|
+
* [MIN_TTL_MS, MAX_TTL_MS]. Throws a CacheError with the
|
|
62
|
+
* `CACHE_INVALID_TTL` code otherwise.
|
|
63
|
+
*/
|
|
64
|
+
export declare function assertValidTtl(ttl: CacheTTL | undefined): void;
|
|
65
|
+
//# sourceMappingURL=utils.d.ts.map
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/cache — Internal Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared helpers: glob matching, sequential delete-many fallback,
|
|
5
|
+
* TTL/pattern validation, and a monotonic clock.
|
|
6
|
+
*/
|
|
7
|
+
import { CACHE_PATTERN_PART_PATTERN, DEFAULT_SEPARATOR, MAX_KEY_LENGTH, MAX_TTL_MS, MIN_TTL_MS, } from "./constants.js";
|
|
8
|
+
import { CacheError, cacheInvalidKeyError } from "./errors.js";
|
|
9
|
+
/* -------------------------------------------------------------------------- */
|
|
10
|
+
/* Monotonic clock */
|
|
11
|
+
/* -------------------------------------------------------------------------- */
|
|
12
|
+
/**
|
|
13
|
+
* Monotonic milliseconds since process start. Unlike `Date.now()` this is
|
|
14
|
+
* unaffected by NTP steps, VM resume, or manual clock changes, so deadlines
|
|
15
|
+
* computed from it cannot be extended or collapsed by a wall-clock jump.
|
|
16
|
+
*
|
|
17
|
+
* Use `wallClockFor()` to convert a monotonic deadline back into a `Date`
|
|
18
|
+
* for human-facing fields.
|
|
19
|
+
*/
|
|
20
|
+
export function monotonicNow() {
|
|
21
|
+
return performance.now();
|
|
22
|
+
}
|
|
23
|
+
/** Converts a monotonic timestamp into an approximate wall-clock `Date`. */
|
|
24
|
+
export function wallClockFor(monotonicMs) {
|
|
25
|
+
return new Date(performance.timeOrigin + monotonicMs);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Splits one pattern segment into tokens, collapsing runs of `*` into a
|
|
29
|
+
* single star token. Collapsing is what makes matching cheap: without it a
|
|
30
|
+
* pattern of *n* consecutive stars is a catastrophic-backtracking hazard.
|
|
31
|
+
*/
|
|
32
|
+
function tokenizeSegment(segment) {
|
|
33
|
+
const tokens = [];
|
|
34
|
+
for (const char of segment) {
|
|
35
|
+
if (char === "*") {
|
|
36
|
+
if (tokens[tokens.length - 1]?.kind === "star")
|
|
37
|
+
continue;
|
|
38
|
+
tokens.push({ kind: "star" });
|
|
39
|
+
}
|
|
40
|
+
else if (char === "?") {
|
|
41
|
+
tokens.push({ kind: "single" });
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
tokens.push({ kind: "literal", char });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return tokens;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Classic linear wildcard matcher (greedy with single-star backtracking).
|
|
51
|
+
* Runs in O(pattern × value) worst case and O(value) in practice — it can
|
|
52
|
+
* never backtrack exponentially.
|
|
53
|
+
*/
|
|
54
|
+
function matchSegment(tokens, value) {
|
|
55
|
+
let ti = 0;
|
|
56
|
+
let vi = 0;
|
|
57
|
+
let starTi = -1;
|
|
58
|
+
let starVi = 0;
|
|
59
|
+
while (vi < value.length) {
|
|
60
|
+
const token = tokens[ti];
|
|
61
|
+
if (token !== undefined &&
|
|
62
|
+
((token.kind === "literal" && token.char === value[vi]) ||
|
|
63
|
+
token.kind === "single")) {
|
|
64
|
+
ti++;
|
|
65
|
+
vi++;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (token !== undefined && token.kind === "star") {
|
|
69
|
+
starTi = ti;
|
|
70
|
+
starVi = vi;
|
|
71
|
+
ti++;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (starTi >= 0) {
|
|
75
|
+
starVi++;
|
|
76
|
+
vi = starVi;
|
|
77
|
+
ti = starTi + 1;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
while (tokens[ti]?.kind === "star")
|
|
83
|
+
ti++;
|
|
84
|
+
return ti === tokens.length;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Compiles a glob pattern into a linear-time matcher.
|
|
88
|
+
*
|
|
89
|
+
* Semantics (deliberately segment-aware, so a wildcard cannot escape the
|
|
90
|
+
* scope it was written for):
|
|
91
|
+
* - `*` matches any run of characters **within a single key segment**; it
|
|
92
|
+
* never crosses the separator. `zudojs:*` therefore matches `zudojs:a`
|
|
93
|
+
* but not `zudojs:tenant:a`.
|
|
94
|
+
* - `?` matches exactly one character within a segment.
|
|
95
|
+
* - `**` as a whole segment matches zero or more whole segments, so
|
|
96
|
+
* `zudojs:**` matches every key under the `zudojs` prefix.
|
|
97
|
+
*
|
|
98
|
+
* The pattern is length-bounded (see `assertValidPattern`) and star runs are
|
|
99
|
+
* collapsed, so pathological patterns cannot cause catastrophic backtracking.
|
|
100
|
+
*/
|
|
101
|
+
export function createGlobMatcher(pattern, options) {
|
|
102
|
+
assertValidPattern(pattern);
|
|
103
|
+
const separator = options?.separator ?? DEFAULT_SEPARATOR;
|
|
104
|
+
const rawSegments = pattern.split(separator);
|
|
105
|
+
const compiled = rawSegments.map((segment) => segment === "**"
|
|
106
|
+
? "globstar"
|
|
107
|
+
: { tokens: tokenizeSegment(segment) });
|
|
108
|
+
return (value) => {
|
|
109
|
+
const valueSegments = value.split(separator);
|
|
110
|
+
// Two-pointer match at the segment level: `**` behaves as a star over
|
|
111
|
+
// whole segments, using the same linear greedy algorithm.
|
|
112
|
+
let pi = 0;
|
|
113
|
+
let vi = 0;
|
|
114
|
+
let starPi = -1;
|
|
115
|
+
let starVi = 0;
|
|
116
|
+
while (vi < valueSegments.length) {
|
|
117
|
+
const part = compiled[pi];
|
|
118
|
+
if (part !== undefined &&
|
|
119
|
+
part !== "globstar" &&
|
|
120
|
+
matchSegment(part.tokens, valueSegments[vi])) {
|
|
121
|
+
pi++;
|
|
122
|
+
vi++;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (part === "globstar") {
|
|
126
|
+
starPi = pi;
|
|
127
|
+
starVi = vi;
|
|
128
|
+
pi++;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (starPi >= 0) {
|
|
132
|
+
starVi++;
|
|
133
|
+
vi = starVi;
|
|
134
|
+
pi = starPi + 1;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
while (compiled[pi] === "globstar")
|
|
140
|
+
pi++;
|
|
141
|
+
return pi === compiled.length;
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Validates a glob pattern before it is compiled or composed into a key
|
|
146
|
+
* pattern. Patterns are untrusted input on every pattern-based entry point,
|
|
147
|
+
* so they are bounded in length and restricted to the key alphabet plus the
|
|
148
|
+
* glob metacharacters and the separator.
|
|
149
|
+
*/
|
|
150
|
+
export function assertValidPattern(pattern) {
|
|
151
|
+
if (pattern.length === 0) {
|
|
152
|
+
throw cacheInvalidKeyError(pattern, "Cache pattern must not be empty.");
|
|
153
|
+
}
|
|
154
|
+
if (pattern.length > MAX_KEY_LENGTH) {
|
|
155
|
+
throw cacheInvalidKeyError(pattern, `Cache pattern exceeds maximum length of ${MAX_KEY_LENGTH} characters.`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Validates a single pattern segment supplied by a caller (the part that is
|
|
160
|
+
* appended after prefix/namespace). Only the key alphabet plus `*` and `?`
|
|
161
|
+
* have meaning; anything else is caller error.
|
|
162
|
+
*/
|
|
163
|
+
export function assertValidPatternPart(part, separator) {
|
|
164
|
+
if (part.length === 0) {
|
|
165
|
+
throw cacheInvalidKeyError(part, "Cache pattern must not be empty.");
|
|
166
|
+
}
|
|
167
|
+
if (part.includes(separator) || !CACHE_PATTERN_PART_PATTERN.test(part)) {
|
|
168
|
+
throw cacheInvalidKeyError(part, `Invalid cache pattern "${part}": patterns must match ${String(CACHE_PATTERN_PART_PATTERN)} and must not contain the separator "${separator}".`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/* -------------------------------------------------------------------------- */
|
|
172
|
+
/* Misc */
|
|
173
|
+
/* -------------------------------------------------------------------------- */
|
|
174
|
+
/**
|
|
175
|
+
* Deletes many keys by calling a single-key delete function sequentially.
|
|
176
|
+
* Shared fallback used by the memory adapter, the store, and the
|
|
177
|
+
* invalidation manager.
|
|
178
|
+
*/
|
|
179
|
+
export async function deleteManyViaDelete(keys, deleteOne) {
|
|
180
|
+
let deleted = 0;
|
|
181
|
+
const deletedKeys = [];
|
|
182
|
+
for (const key of keys) {
|
|
183
|
+
const result = await deleteOne(key);
|
|
184
|
+
if (result.deleted) {
|
|
185
|
+
deleted++;
|
|
186
|
+
deletedKeys.push(key);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return { deleted, keys: deletedKeys };
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Validates a TTL value. `null` means "never expires" and `undefined`
|
|
193
|
+
* means "use the default"; both are valid. Finite numbers must be within
|
|
194
|
+
* [MIN_TTL_MS, MAX_TTL_MS]. Throws a CacheError with the
|
|
195
|
+
* `CACHE_INVALID_TTL` code otherwise.
|
|
196
|
+
*/
|
|
197
|
+
export function assertValidTtl(ttl) {
|
|
198
|
+
if (ttl === null || ttl === undefined)
|
|
199
|
+
return;
|
|
200
|
+
if (!Number.isFinite(ttl) || ttl < MIN_TTL_MS || ttl > MAX_TTL_MS) {
|
|
201
|
+
const code = "CACHE_INVALID_TTL";
|
|
202
|
+
throw new CacheError(`Invalid cache TTL: ${ttl}. TTL must be between ${MIN_TTL_MS} and ${MAX_TTL_MS} ms, or null for no expiry.`, {
|
|
203
|
+
code,
|
|
204
|
+
statusCode: 400,
|
|
205
|
+
expose: true,
|
|
206
|
+
metadata: { ttl },
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=utils.js.map
|
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/cache",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Caching primitives, abstractions, and adapters for the Zudojs framework.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Oluwayemi Oyinlola",
|
|
8
|
+
"url": "https://github.com/oyinlola-tech"
|
|
9
|
+
},
|
|
6
10
|
"type": "module",
|
|
7
11
|
"main": "./dist/index.js",
|
|
8
12
|
"module": "./dist/index.js",
|
|
@@ -14,19 +18,15 @@
|
|
|
14
18
|
}
|
|
15
19
|
},
|
|
16
20
|
"files": [
|
|
17
|
-
"dist"
|
|
21
|
+
"dist",
|
|
22
|
+
"!dist/**/*.map",
|
|
23
|
+
"!dist/**/*.tsbuildinfo",
|
|
24
|
+
"!dist/.tsbuildinfo"
|
|
18
25
|
],
|
|
19
|
-
"scripts": {
|
|
20
|
-
"build": "tsc -p tsconfig.json",
|
|
21
|
-
"clean": "rm -rf dist",
|
|
22
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
23
|
-
"test": "vitest run",
|
|
24
|
-
"test:watch": "vitest"
|
|
25
|
-
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@zudojs/errors": "0.1
|
|
28
|
-
"@zudojs/types": "
|
|
29
|
-
"@zudojs/serialization": "0.1
|
|
27
|
+
"@zudojs/errors": "1.0.1",
|
|
28
|
+
"@zudojs/types": "1.0.0",
|
|
29
|
+
"@zudojs/serialization": "1.0.1"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"typescript": "7.0.2",
|
|
@@ -46,8 +46,19 @@
|
|
|
46
46
|
"tags"
|
|
47
47
|
],
|
|
48
48
|
"homepage": "https://github.com/oyinlola-tech/zudo#readme",
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/oyinlola-tech/zudo/issues"
|
|
51
|
+
},
|
|
49
52
|
"repository": {
|
|
50
53
|
"type": "git",
|
|
51
|
-
"url": "https://github.com/oyinlola-tech/zudo"
|
|
54
|
+
"url": "https://github.com/oyinlola-tech/zudo",
|
|
55
|
+
"directory": "packages/cache"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsc -p tsconfig.json",
|
|
59
|
+
"clean": "rm -rf dist",
|
|
60
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit",
|
|
61
|
+
"test": "vitest run",
|
|
62
|
+
"test:watch": "vitest"
|
|
52
63
|
}
|
|
53
|
-
}
|
|
64
|
+
}
|