-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathcache.zod.ts
More file actions
232 lines (208 loc) · 11.2 KB
/
Copy pathcache.zod.ts
File metadata and controls
232 lines (208 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
import { z } from 'zod';
import { CronExpressionInputSchema } from '../shared/expression.zod';
/**
* @module system/cache
*
* Application-Level Cache Protocol
*
* Multi-tier caching strategy for application data.
* Supports Memory, Redis, Memcached, and CDN.
*
* ## Caching in ObjectStack
*
* **Application Cache (`system/cache.zod.ts`) - This File**
* - **Purpose**: Cache computed data, query results, aggregations
* - **Technologies**: Redis, Memcached, in-memory LRU
* - **Configuration**: TTL, eviction policies, cache warming
* - **Use case**: Cache expensive database queries, computed values
* - **Scope**: Application layer, server-side data storage
*
* **HTTP Cache (`api/http-cache.zod.ts`)**
* - **Purpose**: Cache API responses at HTTP protocol level
* - **Technologies**: HTTP headers (ETag, Last-Modified, Cache-Control), CDN
* - **Configuration**: Cache-Control headers, validation tokens
* - **Use case**: Reduce API response time for repeated metadata requests
* - **Scope**: HTTP layer, client-server communication
*
* @see ../../api/http-cache.zod.ts for HTTP-level caching
*/
import { lazySchema } from '../shared/lazy-schema';
import { retiredKey } from '../shared/retired-key';
/**
* Cache eviction strategy — the single declaration of the `CacheStrategy`
* vocabulary (#4537). `shared/enums.zod.ts` carried a second, diverged copy
* (`CacheStrategyEnum`, no importers anywhere) that was removed rather than
* reconciled. `adaptive` ("dynamic strategy selection") was declared only on
* this side and had zero producers in this repo, objectui and cloud — dropped
* so the enum carries only the values both declarations ever agreed on.
*/
export const CacheStrategySchema = lazySchema(() => z.enum([
'lru', // Least Recently Used
'lfu', // Least Frequently Used
'fifo', // First In First Out
'ttl', // Time To Live only
]).describe('Cache eviction strategy'));
export type CacheStrategy = z.input<typeof CacheStrategySchema>;
const CACHE_TIER_TTL_RETIRED =
'`CacheTier.ttl` was renamed to `ttlSeconds` in @objectstack/spec 17 — the unit of a '
+ 'duration-shaped number lives in the key name, not only in the describe prose, and the '
+ 'sibling `maxSize` on this same tier is a size in MB. Rename the key to `ttlSeconds`; '
+ 'the value (seconds) and the 300 default are unchanged.';
const CIRCUIT_BREAKER_RESET_TIMEOUT_RETIRED =
'`CacheAvalanchePrevention.circuitBreaker.resetTimeout` was renamed to '
+ '`resetTimeoutSeconds` in @objectstack/spec 17 — the unit of a duration-shaped number '
+ 'lives in the key name, not only in the describe prose, and the sibling `lockout` block '
+ 'on this same schema already spells `lockTimeoutMs`. Rename the key to '
+ '`resetTimeoutSeconds`; the value (seconds) and the 30 default are unchanged. Note the '
+ 'two are NOT the same unit: this one is seconds, `lockTimeoutMs` is milliseconds.';
export const CacheTierSchema = lazySchema(() => z.object({
name: z.string().describe('Unique cache tier name'),
type: z.enum(['memory', 'redis', 'memcached', 'cdn']).describe('Cache backend type'),
maxSize: z.number().optional().describe('Max size in MB'),
// Renamed from `ttl` (#15679, #14478 ruling B): the unit lived only in the
// describe prose, while the sibling `maxSize` on this very tier is MB — two
// bare numbers side by side, neither naming its unit at the authoring site.
ttlSeconds: z.number().default(300).describe('Default TTL in seconds'),
/** Tombstone for the rename above (#15679, ruling B on #14478). */
ttl: retiredKey(CACHE_TIER_TTL_RETIRED),
strategy: CacheStrategySchema.default('lru').describe('Eviction strategy'),
warmup: z.boolean().default(false).describe('Pre-populate cache on startup'),
}).describe('Configuration for a single cache tier in the hierarchy'));
export type CacheTier = z.input<typeof CacheTierSchema>;
/** Post-parse shape of {@link CacheTier} — defaults applied, transforms run (ADR-0122). */
export type CacheTierParsed = z.infer<typeof CacheTierSchema>;
export const CacheInvalidationSchema = lazySchema(() => z.object({
trigger: z.enum(['create', 'update', 'delete', 'manual']).describe('Event that triggers invalidation'),
scope: z.enum(['key', 'pattern', 'tag', 'all']).describe('Invalidation scope'),
pattern: z.string().optional().describe('Key pattern for pattern-based invalidation'),
tags: z.array(z.string()).optional().describe('Cache tags to invalidate'),
}).describe('Rule defining when and how cached entries are invalidated'));
export type CacheInvalidation = z.input<typeof CacheInvalidationSchema>;
export const CacheConfigSchema = lazySchema(() => z.object({
enabled: z.boolean().default(false).describe('Enable application-level caching'),
tiers: z.array(CacheTierSchema).describe('Ordered cache tier hierarchy'),
invalidation: z.array(CacheInvalidationSchema).describe('Cache invalidation rules'),
prefetch: z.boolean().default(false).describe('Enable cache prefetching'),
compression: z.boolean().default(false).describe('Enable data compression in cache'),
encryption: z.boolean().default(false).describe('Enable encryption for cached data'),
}).describe('Top-level application cache configuration'));
export type CacheConfig = z.input<typeof CacheConfigSchema>;
/** Post-parse shape of {@link CacheConfig} — defaults applied, transforms run (ADR-0122). */
export type CacheConfigParsed = z.infer<typeof CacheConfigSchema>;
/**
* Distributed Cache Consistency Schema
*
* Defines write strategies for distributed cache consistency.
*
* - **write_through**: Write to cache and backend simultaneously
* - **write_behind**: Write to cache first, async persist to backend
* - **write_around**: Write to backend only, cache on next read
* - **refresh_ahead**: Proactively refresh expiring entries before TTL
*/
export const CacheConsistencySchema = lazySchema(() => z.enum([
'write_through',
'write_behind',
'write_around',
'refresh_ahead',
]).describe('Distributed cache write consistency strategy'));
export type CacheConsistency = z.input<typeof CacheConsistencySchema>;
/**
* Cache Avalanche Prevention Schema
*
* Strategies to prevent cache stampede/avalanche when many keys expire simultaneously.
*
* @example
* ```typescript
* const prevention: CacheAvalanchePrevention = {
* jitterTtl: { enabled: true, maxJitterSeconds: 60 },
* circuitBreaker: { enabled: true, failureThreshold: 5, resetTimeoutSeconds: 30 },
* lockout: { enabled: true, lockTimeoutMs: 5000 },
* };
* ```
*/
export const CacheAvalanchePreventionSchema = lazySchema(() => z.object({
/** TTL jitter to stagger cache expiration */
jitterTtl: z.object({
enabled: z.boolean().default(false).describe('Add random jitter to TTL values'),
maxJitterSeconds: z.number().default(60).describe('Maximum jitter added to TTL in seconds'),
}).optional().describe('TTL jitter to prevent simultaneous expiration'),
/** Circuit breaker to protect backend under cache pressure */
circuitBreaker: z.object({
enabled: z.boolean().default(false).describe('Enable circuit breaker for backend protection'),
failureThreshold: z.number().default(5).describe('Failures before circuit opens'),
// Renamed from `resetTimeout` (#15679, #14478 ruling B): the unit lived only
// in the describe prose, and the sibling `lockout.lockTimeoutMs` on this same
// schema already spelled its own unit — one shape, two conventions.
resetTimeoutSeconds: z.number().default(30).describe('Seconds before half-open state'),
/** Tombstone for the rename above (#15679, ruling B on #14478). */
resetTimeout: retiredKey(CIRCUIT_BREAKER_RESET_TIMEOUT_RETIRED),
}).optional().describe('Circuit breaker for backend protection'),
/** Cache lock to prevent thundering herd on key miss */
lockout: z.object({
enabled: z.boolean().default(false).describe('Enable cache locking for key regeneration'),
lockTimeoutMs: z.number().default(5000).describe('Maximum lock wait time in milliseconds'),
}).optional().describe('Lock-based stampede prevention'),
}).describe('Cache avalanche/stampede prevention configuration'));
export type CacheAvalanchePrevention = z.input<typeof CacheAvalanchePreventionSchema>;
/** Post-parse shape of {@link CacheAvalanchePrevention} — defaults applied, transforms run (ADR-0122). */
export type CacheAvalanchePreventionParsed = z.infer<typeof CacheAvalanchePreventionSchema>;
/**
* Cache Warmup Strategy Schema
*
* Defines how cache is pre-populated on startup or after cache flush.
*/
export const CacheWarmupSchema = lazySchema(() => z.object({
/** Enable cache warming */
enabled: z.boolean().default(false).describe('Enable cache warmup'),
/** Warmup strategy */
strategy: z.enum(['eager', 'lazy', 'scheduled']).default('lazy')
.describe('Warmup strategy: eager (at startup), lazy (on first access), scheduled (cron)'),
/** Cron schedule for scheduled warmup */
schedule: CronExpressionInputSchema.optional().describe('Cron expression for scheduled warmup'),
/** Keys/patterns to warm up */
patterns: z.array(z.string()).optional().describe('Key patterns to warm up (e.g., "user:*", "config:*")'),
/** Maximum concurrent warmup operations */
concurrency: z.number().default(10).describe('Maximum concurrent warmup operations'),
}).describe('Cache warmup strategy'));
export type CacheWarmup = z.input<typeof CacheWarmupSchema>;
/** Post-parse shape of {@link CacheWarmup} — defaults applied, transforms run (ADR-0122). */
export type CacheWarmupParsed = z.infer<typeof CacheWarmupSchema>;
/**
* Distributed Cache Configuration Schema
*
* Extended cache configuration for distributed multi-node deployments.
* Adds consistency strategies, avalanche prevention, and warmup policies.
*
* @example
* ```typescript
* const distributedCache: DistributedCacheConfig = {
* enabled: true,
* tiers: [
* { name: 'l1', type: 'memory', maxSize: 100, ttlSeconds: 60, strategy: 'lru' },
* { name: 'l2', type: 'redis', maxSize: 1000, ttlSeconds: 300, strategy: 'lru' },
* ],
* invalidation: [
* { trigger: 'update', scope: 'key' },
* ],
* consistency: 'write_through',
* avalanchePrevention: {
* jitterTtl: { enabled: true, maxJitterSeconds: 30 },
* circuitBreaker: { enabled: true, failureThreshold: 5 },
* },
* warmup: { enabled: true, strategy: 'eager', patterns: ['config:*'] },
* };
* ```
*/
export const DistributedCacheConfigSchema = lazySchema(() => CacheConfigSchema.extend({
/** Distributed write consistency strategy */
consistency: CacheConsistencySchema.optional().describe('Distributed cache consistency strategy'),
/** Avalanche/stampede prevention settings */
avalanchePrevention: CacheAvalanchePreventionSchema.optional()
.describe('Cache avalanche and stampede prevention'),
/** Cache warmup configuration */
warmup: CacheWarmupSchema.optional().describe('Cache warmup strategy'),
}).describe('Distributed cache configuration with consistency and avalanche prevention'));
export type DistributedCacheConfig = z.input<typeof DistributedCacheConfigSchema>;
/** Post-parse shape of {@link DistributedCacheConfig} — defaults applied, transforms run (ADR-0122). */
export type DistributedCacheConfigParsed = z.infer<typeof DistributedCacheConfigSchema>;