@checkstack/cache-api 0.2.0
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/CHANGELOG.md +103 -0
- package/package.json +22 -0
- package/src/cache-manager.ts +38 -0
- package/src/cache-plugin.ts +39 -0
- package/src/cache-provider.test.ts +122 -0
- package/src/cache-provider.ts +68 -0
- package/src/index.ts +3 -0
- package/tsconfig.json +6 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# @checkstack/cache-api
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 8d1ef12: ## Per-entity caching with single-flight + safe invalidation across the dashboard hot paths
|
|
8
|
+
|
|
9
|
+
### `@checkstack/cache-api`
|
|
10
|
+
|
|
11
|
+
- **Breaking** for backend implementors: `CacheProvider` now requires `deleteByPrefix(prefix: string): Promise<number>` for family-level invalidation. The in-memory provider implements it; downstream providers (Redis, etc.) must add it before upgrading.
|
|
12
|
+
- `createScopedCache` forwards `deleteByPrefix` and keeps prefixes scoped to the calling plugin.
|
|
13
|
+
|
|
14
|
+
### `@checkstack/cache-utils` (new package)
|
|
15
|
+
|
|
16
|
+
High-level read-through caching helpers built on `CacheProvider`:
|
|
17
|
+
|
|
18
|
+
- `createCachedScope({ cacheManager, pluginId })` returns a scope with `wrap`, `wrapMany`, `invalidate`, and `invalidatePrefix`.
|
|
19
|
+
- **Single-flight**: concurrent cache misses for the same key share one loader.
|
|
20
|
+
- **Per-entity bulk caching** via `wrapMany` so list/bulk RPCs cache by id rather than by the full input shape — overlapping callers share entries and invalidation stays exact.
|
|
21
|
+
- **Race-safe invalidation** via per-key epoch counters: a loader started before a mutation cannot repopulate the cache with stale data after the mutation invalidates it. The mutation invariant is `db.write → cache.invalidate (await) → signals.emit`.
|
|
22
|
+
- Cache failures fall through to the loader so a cache outage cannot break reads.
|
|
23
|
+
|
|
24
|
+
### `@checkstack/backend`
|
|
25
|
+
|
|
26
|
+
- The internal null `CacheProvider` (used when no cache backend is configured) now implements the new `deleteByPrefix` method as a no-op. Patch bump only — no behavior change for existing callers.
|
|
27
|
+
|
|
28
|
+
### `@checkstack/healthcheck-backend`
|
|
29
|
+
|
|
30
|
+
- `getSystemHealthStatus` and `getBulkSystemHealthStatus` now read through a per-system cache (`healthcheck:status:<systemId>`), eliminating N database queries per dashboard refresh for unchanged systems.
|
|
31
|
+
- Mutation paths (configuration CRUD, system associations, satellite ingest, queue-driven check runs, system/satellite removal hooks) invalidate affected keys before broadcasting their signals so frontend refetches always observe fresh data.
|
|
32
|
+
|
|
33
|
+
### `@checkstack/incident-backend`
|
|
34
|
+
|
|
35
|
+
- `listIncidents`, `getIncident`, `getIncidentsForSystem`, and `getBulkIncidentsForSystems` now read through a scoped cache:
|
|
36
|
+
- per-incident at `incident:<id>`
|
|
37
|
+
- per-system at `system:<systemId>`
|
|
38
|
+
- per-filter-shape at `list:<stable-stringify(filters)>` for the few list shapes the dashboard polls
|
|
39
|
+
- Mutations (`createIncident`, `updateIncident`, `addUpdate`, `resolveIncident`, `deleteIncident`) invalidate the incident, every affected system, and every cached list before broadcasting `INCIDENT_UPDATED`.
|
|
40
|
+
- The catalog `systemDeleted` cleanup hook drops that system's cached entries.
|
|
41
|
+
|
|
42
|
+
### `@checkstack/maintenance-backend`
|
|
43
|
+
|
|
44
|
+
- `listMaintenances`, `getMaintenance`, `getMaintenancesForSystem`, and `getBulkMaintenancesForSystems` use the same per-entity / per-system / per-filter-shape pattern as incidents.
|
|
45
|
+
- Mutations (`createMaintenance`, `updateMaintenance`, `addUpdate`, `closeMaintenance`, `deleteMaintenance`) invalidate before broadcasting `MAINTENANCE_UPDATED`.
|
|
46
|
+
|
|
47
|
+
### `@checkstack/catalog-backend`
|
|
48
|
+
|
|
49
|
+
- Topology reads (`getEntities`, `getSystems`, `getSystem`, `getGroups`, `getSystemGroupIds`) cache under the `entity:` family (25s TTL).
|
|
50
|
+
- Views (`getViews`) and per-system contacts (`getSystemContacts`) cache in their own families.
|
|
51
|
+
- System / group / membership mutations drop the entire `entity:` family (every reader joins the same tables); view and contact mutations drop only their respective scopes.
|
|
52
|
+
|
|
53
|
+
### `@checkstack/slo-backend`
|
|
54
|
+
|
|
55
|
+
- `listObjectives`, `getObjective`, `getObjectivesForSystem`, and `getBulkObjectivesForSystems` cache results including the expensive `engine.computeStatus` output.
|
|
56
|
+
- Per-entity caching for the bulk handler so dashboards with overlapping system sets share entries.
|
|
57
|
+
- Mutations (`createObjective`, `updateObjective`, `deleteObjective`) invalidate before broadcasting `SLO_STATUS_CHANGED`.
|
|
58
|
+
|
|
59
|
+
### `@checkstack/anomaly-backend`
|
|
60
|
+
|
|
61
|
+
- New `router-cache.ts` adds a cache scope distinct from the existing detector baseline cache, keyed by stable filter hash.
|
|
62
|
+
- `getAnomalies` and `getAnomalyBaselines` cache through this scope (15s TTL).
|
|
63
|
+
- The detector invalidates the router cache before broadcasting `ANOMALY_STATE_CHANGED` on every state transition (suspicious/anomaly/recovered).
|
|
64
|
+
- Config mutations also invalidate.
|
|
65
|
+
|
|
66
|
+
### `@checkstack/notification-backend`
|
|
67
|
+
|
|
68
|
+
- `getUnreadCount`, `getNotifications`, and `getSubscriptions` cache per-user.
|
|
69
|
+
- `markAsRead`, `deleteNotification`, `notifyUsers`, and `notifyGroups` invalidate every affected user's cache before sending realtime signals to that user.
|
|
70
|
+
- `subscribe` and `unsubscribe` invalidate the user's subscription cache.
|
|
71
|
+
|
|
72
|
+
### `@checkstack/announcement-backend`
|
|
73
|
+
|
|
74
|
+
- `getActiveAnnouncements` caches per-user (or anonymous) and per-`includeDismissed` flag (45s TTL — admin-driven, slowly changing).
|
|
75
|
+
- `listAllAnnouncements` caches under a single key.
|
|
76
|
+
- `dismissAnnouncement` only drops that user's cache; `createAnnouncement`, `updateAnnouncement`, `deleteAnnouncement` drop every user's cache before broadcasting `ANNOUNCEMENT_UPDATED`.
|
|
77
|
+
- The auth `userDeleted` cleanup hook drops that user's cached entries.
|
|
78
|
+
|
|
79
|
+
- 8d1ef12: ## Infrastructure Configuration Shell & Cache System
|
|
80
|
+
|
|
81
|
+
### New Packages
|
|
82
|
+
|
|
83
|
+
- **`@checkstack/cache-api`**: Core cache abstractions — `CacheProvider` interface, `createScopedCache` factory for plugin key isolation, `CachePlugin`/`CacheManager` lifecycle interfaces.
|
|
84
|
+
- **`@checkstack/cache-common`**: Shared cache types, RPC contract (`getPlugins`, `getConfiguration`, `updateConfiguration`), access rules, and plugin metadata.
|
|
85
|
+
- **`@checkstack/cache-backend`**: Cache settings RPC router — exposes plugin discovery, configuration read/write endpoints with access-gated authorization.
|
|
86
|
+
- **`@checkstack/cache-frontend`**: Cache configuration tab component for the Infrastructure Settings page.
|
|
87
|
+
- **`@checkstack/infrastructure-common`**: Infrastructure tab registry, routes, and shared types for the IDE-style configuration shell.
|
|
88
|
+
- **`@checkstack/infrastructure-frontend`**: Infrastructure Settings page with vertical tab bar, per-tab access control, and user menu integration.
|
|
89
|
+
|
|
90
|
+
### Modified Packages
|
|
91
|
+
|
|
92
|
+
- **`@checkstack/backend-api`**: Added `cachePluginRegistry` and `cacheManager` to `RpcContext` and `coreServices`.
|
|
93
|
+
- **`@checkstack/backend`**: Registered cache services in boot sequence, added cache config loading, extended dependency sorter for cache plugin ordering.
|
|
94
|
+
- **`@checkstack/queue-frontend`**: Refactored from standalone `/queue/config` route to an infrastructure tab. Queue settings now live inside the Infrastructure Settings page.
|
|
95
|
+
|
|
96
|
+
### Architecture
|
|
97
|
+
|
|
98
|
+
The former monolithic Queue Config page is replaced by a pluggable Infrastructure Settings shell (`/infrastructure/config`). Plugins register configuration tabs via `registerInfrastructureTab()` with their own access rules, icons, and components. The shell evaluates per-tab access and only renders tabs the user can see.
|
|
99
|
+
|
|
100
|
+
### Patch Changes
|
|
101
|
+
|
|
102
|
+
- Updated dependencies [8d1ef12]
|
|
103
|
+
- @checkstack/backend-api@0.13.0
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@checkstack/cache-api",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"checkstack": {
|
|
5
|
+
"type": "tooling"
|
|
6
|
+
},
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "src/index.ts",
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@checkstack/backend-api": "0.12.0",
|
|
11
|
+
"zod": "^4.0.0"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@checkstack/tsconfig": "0.0.5",
|
|
15
|
+
"@checkstack/scripts": "0.1.2"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"lint": "bun run lint:code",
|
|
20
|
+
"lint:code": "eslint . --max-warnings 0"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { CacheProvider } from "./cache-provider";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CacheManager handles cache provider lifecycle and backend switching.
|
|
5
|
+
*
|
|
6
|
+
* Simpler than QueueManager — no proxy pattern needed since cache is stateless key/value.
|
|
7
|
+
* The active provider is replaced atomically on backend switch.
|
|
8
|
+
*/
|
|
9
|
+
export interface CacheManager {
|
|
10
|
+
/**
|
|
11
|
+
* Get the currently active CacheProvider.
|
|
12
|
+
* Returns a singleton provider for the active backend.
|
|
13
|
+
*/
|
|
14
|
+
getProvider(): CacheProvider;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get the currently active cache plugin ID.
|
|
18
|
+
*/
|
|
19
|
+
getActivePlugin(): string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get the currently active cache plugin configuration.
|
|
23
|
+
*/
|
|
24
|
+
getActiveConfig(): unknown;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Switch to a different cache backend.
|
|
28
|
+
* The old provider is shut down and replaced with a new one.
|
|
29
|
+
*
|
|
30
|
+
* @throws Error if connection test fails
|
|
31
|
+
*/
|
|
32
|
+
setActiveBackend(pluginId: string, config: unknown): Promise<void>;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Gracefully shutdown the active cache provider.
|
|
36
|
+
*/
|
|
37
|
+
shutdown(): Promise<void>;
|
|
38
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { CacheProvider } from "./cache-provider";
|
|
3
|
+
import type { Logger, Migration } from "@checkstack/backend-api";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Plugin interface for cache backend implementations.
|
|
7
|
+
* Mirrors the QueuePlugin pattern — each cache backend (in-memory, Redis, etc.)
|
|
8
|
+
* implements this interface and registers with the CachePluginRegistry.
|
|
9
|
+
*/
|
|
10
|
+
export interface CachePlugin<Config = unknown> {
|
|
11
|
+
id: string;
|
|
12
|
+
displayName: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
|
|
15
|
+
/** Current version of the configuration schema */
|
|
16
|
+
configVersion: number;
|
|
17
|
+
|
|
18
|
+
/** Validation schema for the plugin-specific config */
|
|
19
|
+
configSchema: z.ZodType<Config>;
|
|
20
|
+
|
|
21
|
+
/** Optional migrations for backward compatibility */
|
|
22
|
+
migrations?: Migration<unknown, unknown>[];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create a new CacheProvider instance with the given config.
|
|
26
|
+
* Unlike QueuePlugin.createQueue, there's only one provider per backend (not named instances).
|
|
27
|
+
*/
|
|
28
|
+
createProvider(config: Config, logger: Logger): CacheProvider;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Registry for cache plugin implementations.
|
|
33
|
+
* Cache backend plugins register themselves here during initialization.
|
|
34
|
+
*/
|
|
35
|
+
export interface CachePluginRegistry {
|
|
36
|
+
register(plugin: CachePlugin<unknown>): void;
|
|
37
|
+
getPlugin(id: string): CachePlugin<unknown> | undefined;
|
|
38
|
+
getPlugins(): CachePlugin<unknown>[];
|
|
39
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { describe, it, expect } from "bun:test";
|
|
2
|
+
import { createScopedCache, type CacheProvider } from "./cache-provider";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Minimal in-memory CacheProvider for testing the scoped cache factory.
|
|
6
|
+
*/
|
|
7
|
+
function createTestProvider(): CacheProvider & {
|
|
8
|
+
store: Map<string, unknown>;
|
|
9
|
+
} {
|
|
10
|
+
const store = new Map<string, unknown>();
|
|
11
|
+
return {
|
|
12
|
+
store,
|
|
13
|
+
get: async <T>(key: string) => store.get(key) as T | undefined,
|
|
14
|
+
set: async <T>(_key: string, value: T) => {
|
|
15
|
+
store.set(_key, value);
|
|
16
|
+
},
|
|
17
|
+
delete: async (key: string) => {
|
|
18
|
+
store.delete(key);
|
|
19
|
+
},
|
|
20
|
+
deleteByPrefix: async (prefix: string) => {
|
|
21
|
+
let removed = 0;
|
|
22
|
+
for (const key of [...store.keys()]) {
|
|
23
|
+
if (key.startsWith(prefix)) {
|
|
24
|
+
store.delete(key);
|
|
25
|
+
removed++;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return removed;
|
|
29
|
+
},
|
|
30
|
+
has: async (key: string) => store.has(key),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe("createScopedCache", () => {
|
|
35
|
+
it("prefixes keys with plugin ID on set/get", async () => {
|
|
36
|
+
const provider = createTestProvider();
|
|
37
|
+
const scoped = createScopedCache({ pluginId: "my-plugin", provider });
|
|
38
|
+
|
|
39
|
+
await scoped.set("baseline:abc", { mean: 42 });
|
|
40
|
+
|
|
41
|
+
// Scoped cache should prefix the key
|
|
42
|
+
expect(provider.store.has("my-plugin:baseline:abc")).toBe(true);
|
|
43
|
+
expect(provider.store.has("baseline:abc")).toBe(false);
|
|
44
|
+
|
|
45
|
+
// Scoped get should also prefix
|
|
46
|
+
const result = await scoped.get<{ mean: number }>("baseline:abc");
|
|
47
|
+
expect(result).toEqual({ mean: 42 });
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("prefixes keys on delete", async () => {
|
|
51
|
+
const provider = createTestProvider();
|
|
52
|
+
const scoped = createScopedCache({ pluginId: "test", provider });
|
|
53
|
+
|
|
54
|
+
await scoped.set("key1", "value1");
|
|
55
|
+
expect(await scoped.has("key1")).toBe(true);
|
|
56
|
+
|
|
57
|
+
await scoped.delete("key1");
|
|
58
|
+
expect(await scoped.has("key1")).toBe(false);
|
|
59
|
+
expect(provider.store.has("test:key1")).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("prefixes keys on has", async () => {
|
|
63
|
+
const provider = createTestProvider();
|
|
64
|
+
const scoped = createScopedCache({ pluginId: "plugin-a", provider });
|
|
65
|
+
|
|
66
|
+
expect(await scoped.has("missing")).toBe(false);
|
|
67
|
+
|
|
68
|
+
await scoped.set("exists", true);
|
|
69
|
+
expect(await scoped.has("exists")).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("isolates keys between different scoped caches", async () => {
|
|
73
|
+
const provider = createTestProvider();
|
|
74
|
+
const scopeA = createScopedCache({ pluginId: "plugin-a", provider });
|
|
75
|
+
const scopeB = createScopedCache({ pluginId: "plugin-b", provider });
|
|
76
|
+
|
|
77
|
+
await scopeA.set("shared-key", "value-a");
|
|
78
|
+
await scopeB.set("shared-key", "value-b");
|
|
79
|
+
|
|
80
|
+
// Each scope should see its own value
|
|
81
|
+
expect(await scopeA.get<string>("shared-key")).toBe("value-a");
|
|
82
|
+
expect(await scopeB.get<string>("shared-key")).toBe("value-b");
|
|
83
|
+
|
|
84
|
+
// Underlying provider should have both prefixed keys
|
|
85
|
+
expect(provider.store.has("plugin-a:shared-key")).toBe(true);
|
|
86
|
+
expect(provider.store.has("plugin-b:shared-key")).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("deleteByPrefix only removes keys under the scope's prefix", async () => {
|
|
90
|
+
const provider = createTestProvider();
|
|
91
|
+
const scopeA = createScopedCache({ pluginId: "a", provider });
|
|
92
|
+
const scopeB = createScopedCache({ pluginId: "b", provider });
|
|
93
|
+
|
|
94
|
+
await scopeA.set("bulk:1", "x");
|
|
95
|
+
await scopeA.set("bulk:2", "y");
|
|
96
|
+
await scopeA.set("other", "z");
|
|
97
|
+
await scopeB.set("bulk:1", "keep");
|
|
98
|
+
|
|
99
|
+
const removed = await scopeA.deleteByPrefix("bulk:");
|
|
100
|
+
|
|
101
|
+
expect(removed).toBe(2);
|
|
102
|
+
expect(await scopeA.has("bulk:1")).toBe(false);
|
|
103
|
+
expect(await scopeA.has("bulk:2")).toBe(false);
|
|
104
|
+
expect(await scopeA.has("other")).toBe(true);
|
|
105
|
+
expect(await scopeB.has("bulk:1")).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("deleting from one scope does not affect the other", async () => {
|
|
109
|
+
const provider = createTestProvider();
|
|
110
|
+
const scopeA = createScopedCache({ pluginId: "a", provider });
|
|
111
|
+
const scopeB = createScopedCache({ pluginId: "b", provider });
|
|
112
|
+
|
|
113
|
+
await scopeA.set("key", 1);
|
|
114
|
+
await scopeB.set("key", 2);
|
|
115
|
+
|
|
116
|
+
await scopeA.delete("key");
|
|
117
|
+
|
|
118
|
+
expect(await scopeA.has("key")).toBe(false);
|
|
119
|
+
expect(await scopeB.has("key")).toBe(true);
|
|
120
|
+
expect(await scopeB.get<number>("key")).toBe(2);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache provider interface for key-value storage with optional TTL.
|
|
3
|
+
*
|
|
4
|
+
* Implementations should be stateless from the caller's perspective —
|
|
5
|
+
* the provider manages internal state (eviction, connections, etc.)
|
|
6
|
+
* but all operations are explicit via this interface.
|
|
7
|
+
*/
|
|
8
|
+
export interface CacheProvider {
|
|
9
|
+
/**
|
|
10
|
+
* Retrieve a cached value by key.
|
|
11
|
+
* Returns undefined if the key doesn't exist or has expired.
|
|
12
|
+
*/
|
|
13
|
+
get<T>(key: string): Promise<T | undefined>;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Store a value with an optional TTL (time-to-live) in milliseconds.
|
|
17
|
+
* If the key already exists, its value and TTL are replaced.
|
|
18
|
+
*/
|
|
19
|
+
set<T>(key: string, value: T, ttlMs?: number): Promise<void>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Delete a cached value by key.
|
|
23
|
+
* No-op if the key doesn't exist.
|
|
24
|
+
*/
|
|
25
|
+
delete(key: string): Promise<void>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Delete every key starting with the given prefix.
|
|
29
|
+
* Used to invalidate a whole family of keys at once (e.g. all bulk results
|
|
30
|
+
* for a plugin). Returns the number of keys actually removed so callers
|
|
31
|
+
* can emit metrics.
|
|
32
|
+
*/
|
|
33
|
+
deleteByPrefix(prefix: string): Promise<number>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Check if a key exists and has not expired.
|
|
37
|
+
*/
|
|
38
|
+
has(key: string): Promise<boolean>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Creates a scoped cache that automatically prefixes all keys with the plugin ID.
|
|
43
|
+
* This ensures key isolation between plugins using the same underlying CacheProvider.
|
|
44
|
+
*
|
|
45
|
+
* Follows the Scoped Registry Pattern used by HealthCheckRegistry and other core services.
|
|
46
|
+
*
|
|
47
|
+
* @param pluginId - The plugin ID to use as a key prefix
|
|
48
|
+
* @param provider - The underlying CacheProvider
|
|
49
|
+
* @returns A CacheProvider with keys scoped to the plugin
|
|
50
|
+
*/
|
|
51
|
+
export function createScopedCache({
|
|
52
|
+
pluginId,
|
|
53
|
+
provider,
|
|
54
|
+
}: {
|
|
55
|
+
pluginId: string;
|
|
56
|
+
provider: CacheProvider;
|
|
57
|
+
}): CacheProvider {
|
|
58
|
+
const prefix = `${pluginId}:`;
|
|
59
|
+
return {
|
|
60
|
+
get: <T>(key: string) => provider.get<T>(`${prefix}${key}`),
|
|
61
|
+
set: <T>(key: string, value: T, ttlMs?: number) =>
|
|
62
|
+
provider.set<T>(`${prefix}${key}`, value, ttlMs),
|
|
63
|
+
delete: (key: string) => provider.delete(`${prefix}${key}`),
|
|
64
|
+
deleteByPrefix: (innerPrefix: string) =>
|
|
65
|
+
provider.deleteByPrefix(`${prefix}${innerPrefix}`),
|
|
66
|
+
has: (key: string) => provider.has(`${prefix}${key}`),
|
|
67
|
+
};
|
|
68
|
+
}
|
package/src/index.ts
ADDED