@holo-js/cache 0.1.3

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.
@@ -0,0 +1,253 @@
1
+ // src/contracts.ts
2
+ var CacheError = class extends Error {
3
+ code;
4
+ constructor(code, message, options = {}) {
5
+ super(message, options);
6
+ this.name = "CacheError";
7
+ this.code = code;
8
+ }
9
+ };
10
+ var CacheConfigError = class extends CacheError {
11
+ constructor(message, options = {}) {
12
+ super("CACHE_INVALID_CONFIG", message, options);
13
+ this.name = "CacheConfigError";
14
+ }
15
+ };
16
+ var CacheInvalidTtlError = class extends CacheError {
17
+ constructor(message, options = {}) {
18
+ super("CACHE_INVALID_TTL", message, options);
19
+ this.name = "CacheInvalidTtlError";
20
+ }
21
+ };
22
+ var CacheSerializationError = class extends CacheError {
23
+ constructor(message, options = {}) {
24
+ super("CACHE_UNSUPPORTED_VALUE", message, options);
25
+ this.name = "CacheSerializationError";
26
+ }
27
+ };
28
+ var CacheDriverResolutionError = class extends CacheError {
29
+ constructor(message, options = {}) {
30
+ super("CACHE_DRIVER_RESOLUTION_FAILED", message, options);
31
+ this.name = "CacheDriverResolutionError";
32
+ }
33
+ };
34
+ var CacheOptionalPackageError = class extends CacheError {
35
+ constructor(message, options = {}) {
36
+ super("CACHE_OPTIONAL_PACKAGE_MISSING", message, options);
37
+ this.name = "CacheOptionalPackageError";
38
+ }
39
+ };
40
+ var CacheInvalidNumericMutationError = class extends CacheError {
41
+ constructor(message, options = {}) {
42
+ super("CACHE_INVALID_NUMERIC_MUTATION", message, options);
43
+ this.name = "CacheInvalidNumericMutationError";
44
+ }
45
+ };
46
+ var CacheLockAcquisitionError = class extends CacheError {
47
+ constructor(message, options = {}) {
48
+ super("CACHE_LOCK_ACQUISITION_FAILED", message, options);
49
+ this.name = "CacheLockAcquisitionError";
50
+ }
51
+ };
52
+ var CacheQueryIntegrationError = class extends CacheError {
53
+ constructor(message, options = {}) {
54
+ super("CACHE_QUERY_INTEGRATION_MISUSE", message, options);
55
+ this.name = "CacheQueryIntegrationError";
56
+ }
57
+ };
58
+ var CacheRuntimeNotConfiguredError = class extends CacheError {
59
+ constructor() {
60
+ super("CACHE_RUNTIME_NOT_CONFIGURED", "[@holo-js/cache] Cache runtime is not configured yet.");
61
+ this.name = "CacheRuntimeNotConfiguredError";
62
+ }
63
+ };
64
+ function isPlainObject(value) {
65
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
66
+ return false;
67
+ }
68
+ const prototype = Object.getPrototypeOf(value);
69
+ return prototype === Object.prototype || prototype === null;
70
+ }
71
+ function normalizeCacheStringKey(key) {
72
+ const normalized = key.trim();
73
+ if (!normalized) {
74
+ throw new CacheConfigError("[@holo-js/cache] Cache keys must be non-empty strings.");
75
+ }
76
+ return normalized;
77
+ }
78
+ function encodeFiniteNumber(value, path) {
79
+ if (!Number.isFinite(value)) {
80
+ throw new CacheSerializationError(`[@holo-js/cache] Cache value at ${path} must be JSON-safe.`);
81
+ }
82
+ return value;
83
+ }
84
+ function encodeDateValue(value, path) {
85
+ if (Number.isNaN(value.getTime())) {
86
+ throw new CacheSerializationError(`[@holo-js/cache] Cache value at ${path} contains an invalid Date.`);
87
+ }
88
+ return Object.freeze({
89
+ __holo_cache_type: "date",
90
+ value: value.toISOString()
91
+ });
92
+ }
93
+ function encodeArrayValue(value, path) {
94
+ const encodedEntries = [];
95
+ for (let index = 0; index < value.length; index += 1) {
96
+ if (!(index in value)) {
97
+ throw new CacheSerializationError(`[@holo-js/cache] Cache value at ${path}[${index}] must not be sparse.`);
98
+ }
99
+ encodedEntries.push(encodeCacheValue(value[index], `${path}[${index}]`));
100
+ }
101
+ return Object.freeze(encodedEntries);
102
+ }
103
+ function encodeObjectValue(value, path) {
104
+ const encodedEntries = {};
105
+ for (const [key, entry] of Object.entries(value)) {
106
+ if (key === "__holo_cache_type") {
107
+ throw new CacheSerializationError(`[@holo-js/cache] Cache value at ${path}.${key} uses a reserved key.`);
108
+ }
109
+ if (typeof entry === "undefined") {
110
+ throw new CacheSerializationError(`[@holo-js/cache] Cache value at ${path}.${key} must be JSON-safe.`);
111
+ }
112
+ encodedEntries[key] = encodeCacheValue(entry, `${path}.${key}`);
113
+ }
114
+ return Object.freeze(encodedEntries);
115
+ }
116
+ function encodeCacheValue(value, path) {
117
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
118
+ return value;
119
+ }
120
+ if (typeof value === "number") {
121
+ return encodeFiniteNumber(value, path);
122
+ }
123
+ if (value instanceof Date) {
124
+ return encodeDateValue(value, path);
125
+ }
126
+ if (Array.isArray(value)) {
127
+ return encodeArrayValue(value, path);
128
+ }
129
+ if (isPlainObject(value)) {
130
+ return encodeObjectValue(value, path);
131
+ }
132
+ throw new CacheSerializationError(`[@holo-js/cache] Cache value at ${path} must be JSON-safe.`);
133
+ }
134
+ function decodeCacheValue(value, path) {
135
+ if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number") {
136
+ return value;
137
+ }
138
+ if (Array.isArray(value)) {
139
+ return Object.freeze(value.map((entry, index) => decodeCacheValue(entry, `${path}[${index}]`)));
140
+ }
141
+ if (isPlainObject(value)) {
142
+ if (value.__holo_cache_type === "date") {
143
+ if (typeof value.value !== "string") {
144
+ throw new CacheSerializationError(`[@holo-js/cache] Cache payload at ${path} is malformed.`);
145
+ }
146
+ const decoded = new Date(value.value);
147
+ if (Number.isNaN(decoded.getTime())) {
148
+ throw new CacheSerializationError(`[@holo-js/cache] Cache payload at ${path} contains an invalid Date.`);
149
+ }
150
+ return decoded;
151
+ }
152
+ const decodedEntries = {};
153
+ for (const [key, entry] of Object.entries(value)) {
154
+ decodedEntries[key] = decodeCacheValue(entry, `${path}.${key}`);
155
+ }
156
+ return Object.freeze(decodedEntries);
157
+ }
158
+ throw new CacheSerializationError(`[@holo-js/cache] Cache payload at ${path} is malformed.`);
159
+ }
160
+ function defineCacheKey(key) {
161
+ return Object.freeze({
162
+ key: normalizeCacheStringKey(key)
163
+ });
164
+ }
165
+ function isCacheKey(value) {
166
+ return !!value && typeof value === "object" && "key" in value && typeof value.key === "string";
167
+ }
168
+ function resolveCacheKey(key) {
169
+ return normalizeCacheStringKey(typeof key === "string" ? key : key.key);
170
+ }
171
+ function normalizeCacheTtl(ttl, options = {}) {
172
+ const now = options.now instanceof Date ? (() => {
173
+ const timestamp = options.now.getTime();
174
+ if (Number.isNaN(timestamp)) {
175
+ throw new TypeError("[@holo-js/cache] Cache TTL options.now Date must be valid.");
176
+ }
177
+ return timestamp;
178
+ })() : typeof options.now === "number" ? (() => {
179
+ if (Number.isNaN(options.now) || !Number.isFinite(options.now)) {
180
+ throw new TypeError("[@holo-js/cache] Cache TTL options.now must be a valid finite number.");
181
+ }
182
+ return options.now;
183
+ })() : Date.now();
184
+ if (ttl instanceof Date) {
185
+ const expiresAt2 = ttl.getTime();
186
+ if (Number.isNaN(expiresAt2)) {
187
+ throw new CacheInvalidTtlError("[@holo-js/cache] Cache TTL Date must be valid.");
188
+ }
189
+ const remainingMilliseconds = Math.max(0, expiresAt2 - now);
190
+ return Object.freeze({
191
+ seconds: Math.floor(remainingMilliseconds / 1e3),
192
+ expiresAt: expiresAt2,
193
+ isExpired: expiresAt2 <= now
194
+ });
195
+ }
196
+ if (!Number.isInteger(ttl)) {
197
+ throw new CacheInvalidTtlError("[@holo-js/cache] Cache TTL seconds must be an integer.");
198
+ }
199
+ if (ttl < 0) {
200
+ throw new CacheInvalidTtlError("[@holo-js/cache] Cache TTL seconds must be > 0 or use a Date/forever option.");
201
+ }
202
+ if (ttl === 0) {
203
+ throw new CacheInvalidTtlError("[@holo-js/cache] Cache TTL seconds must be > 0 or use a Date/forever option.");
204
+ }
205
+ const expiresAt = now + ttl * 1e3;
206
+ return Object.freeze({
207
+ seconds: ttl,
208
+ expiresAt,
209
+ isExpired: expiresAt <= now
210
+ });
211
+ }
212
+ function serializeCacheValue(value) {
213
+ const payload = encodeCacheValue(value, "$");
214
+ return JSON.stringify(payload);
215
+ }
216
+ function deserializeCacheValue(payload) {
217
+ try {
218
+ return decodeCacheValue(JSON.parse(payload), "$");
219
+ } catch (error) {
220
+ if (error instanceof CacheSerializationError) {
221
+ throw error;
222
+ }
223
+ throw new CacheSerializationError("[@holo-js/cache] Cache payload is not valid JSON.", {
224
+ cause: error
225
+ });
226
+ }
227
+ }
228
+ var cacheContractsInternals = {
229
+ decodeCacheValue,
230
+ encodeCacheValue,
231
+ isPlainObject,
232
+ normalizeCacheStringKey
233
+ };
234
+
235
+ export {
236
+ CacheError,
237
+ CacheConfigError,
238
+ CacheInvalidTtlError,
239
+ CacheSerializationError,
240
+ CacheDriverResolutionError,
241
+ CacheOptionalPackageError,
242
+ CacheInvalidNumericMutationError,
243
+ CacheLockAcquisitionError,
244
+ CacheQueryIntegrationError,
245
+ CacheRuntimeNotConfiguredError,
246
+ defineCacheKey,
247
+ isCacheKey,
248
+ resolveCacheKey,
249
+ normalizeCacheTtl,
250
+ serializeCacheValue,
251
+ deserializeCacheValue,
252
+ cacheContractsInternals
253
+ };
@@ -0,0 +1,196 @@
1
+ import { HoloCacheConfig, NormalizedHoloCacheConfig, HoloDatabaseConfig, NormalizedHoloDatabaseConfig, HoloRedisConfig, NormalizedHoloRedisConfig } from '@holo-js/config';
2
+
3
+ declare const cacheKeyBrand: unique symbol;
4
+ type CacheKey<TValue> = Readonly<{
5
+ readonly key: string;
6
+ readonly [cacheKeyBrand]?: TValue;
7
+ }>;
8
+ type CacheKeyInput<TValue = unknown> = string | CacheKey<TValue>;
9
+ type CacheDependencyDescriptor = string;
10
+ type CacheTtlInput = number | Date;
11
+ type CacheFallbackResolver<TValue> = () => TValue | Promise<TValue>;
12
+ type CacheFallback<TValue> = TValue | CacheFallbackResolver<TValue>;
13
+ type CacheValueResolver<TValue> = () => TValue | Promise<TValue>;
14
+ type CacheFlexibleTtlInput = readonly [fresh: number, stale: number] | {
15
+ readonly fresh: number;
16
+ readonly stale: number;
17
+ };
18
+ interface NormalizedCacheTtl {
19
+ readonly seconds: number;
20
+ readonly expiresAt: number;
21
+ readonly isExpired: boolean;
22
+ }
23
+ type CacheErrorCode = 'CACHE_INVALID_CONFIG' | 'CACHE_INVALID_TTL' | 'CACHE_UNSUPPORTED_VALUE' | 'CACHE_DRIVER_RESOLUTION_FAILED' | 'CACHE_OPTIONAL_PACKAGE_MISSING' | 'CACHE_INVALID_NUMERIC_MUTATION' | 'CACHE_LOCK_ACQUISITION_FAILED' | 'CACHE_QUERY_INTEGRATION_MISUSE' | 'CACHE_RUNTIME_NOT_CONFIGURED';
24
+ declare class CacheError extends Error {
25
+ readonly code: CacheErrorCode;
26
+ constructor(code: CacheErrorCode, message: string, options?: {
27
+ cause?: unknown;
28
+ });
29
+ }
30
+ declare class CacheConfigError extends CacheError {
31
+ constructor(message: string, options?: {
32
+ cause?: unknown;
33
+ });
34
+ }
35
+ declare class CacheInvalidTtlError extends CacheError {
36
+ constructor(message: string, options?: {
37
+ cause?: unknown;
38
+ });
39
+ }
40
+ declare class CacheSerializationError extends CacheError {
41
+ constructor(message: string, options?: {
42
+ cause?: unknown;
43
+ });
44
+ }
45
+ declare class CacheDriverResolutionError extends CacheError {
46
+ constructor(message: string, options?: {
47
+ cause?: unknown;
48
+ });
49
+ }
50
+ declare class CacheOptionalPackageError extends CacheError {
51
+ constructor(message: string, options?: {
52
+ cause?: unknown;
53
+ });
54
+ }
55
+ declare class CacheInvalidNumericMutationError extends CacheError {
56
+ constructor(message: string, options?: {
57
+ cause?: unknown;
58
+ });
59
+ }
60
+ declare class CacheLockAcquisitionError extends CacheError {
61
+ constructor(message: string, options?: {
62
+ cause?: unknown;
63
+ });
64
+ }
65
+ declare class CacheQueryIntegrationError extends CacheError {
66
+ constructor(message: string, options?: {
67
+ cause?: unknown;
68
+ });
69
+ }
70
+ declare class CacheRuntimeNotConfiguredError extends CacheError {
71
+ constructor();
72
+ }
73
+ interface CacheDriverGetResult {
74
+ readonly hit: boolean;
75
+ readonly payload?: string;
76
+ readonly expiresAt?: number;
77
+ }
78
+ interface CacheDriverPutInput {
79
+ readonly key: string;
80
+ readonly payload: string;
81
+ readonly expiresAt?: number;
82
+ }
83
+ interface CacheLockContract {
84
+ readonly name: string;
85
+ get<TValue>(callback?: () => TValue | Promise<TValue>): Promise<boolean | TValue>;
86
+ release(): Promise<boolean>;
87
+ block<TValue>(waitSeconds: number, callback?: () => TValue | Promise<TValue>): Promise<boolean | TValue>;
88
+ }
89
+ interface CacheDriverContract {
90
+ readonly name: string;
91
+ readonly driver: string;
92
+ get(key: string): Promise<CacheDriverGetResult>;
93
+ put(input: CacheDriverPutInput): Promise<boolean>;
94
+ add(input: CacheDriverPutInput): Promise<boolean>;
95
+ forget(key: string): Promise<boolean>;
96
+ flush(): Promise<void>;
97
+ increment(key: string, amount: number): Promise<number>;
98
+ decrement(key: string, amount: number): Promise<number>;
99
+ lock(name: string, seconds: number): CacheLockContract;
100
+ }
101
+ interface CacheDependencyIndex {
102
+ register(key: string, dependencies: readonly CacheDependencyDescriptor[]): Promise<void>;
103
+ listKeys(dependency: CacheDependencyDescriptor): Promise<readonly string[]>;
104
+ listRegisteredKeys(): Promise<readonly string[]>;
105
+ removeKey(key: string): Promise<void>;
106
+ clear(): Promise<void>;
107
+ }
108
+ interface CacheQueryBridge {
109
+ get<TValue>(key: CacheKeyInput<TValue>, options?: {
110
+ driver?: string;
111
+ }): Promise<TValue | null>;
112
+ put<TValue>(key: CacheKeyInput<TValue>, value: TValue, options: {
113
+ readonly driver?: string;
114
+ readonly ttl?: CacheTtlInput;
115
+ readonly flexible?: CacheFlexibleTtlInput;
116
+ readonly dependencies?: readonly CacheDependencyDescriptor[];
117
+ }): Promise<void>;
118
+ flexible<TValue>(key: CacheKeyInput<TValue>, ttl: CacheFlexibleTtlInput, callback: CacheValueResolver<TValue>, options?: {
119
+ readonly driver?: string;
120
+ readonly dependencies?: readonly CacheDependencyDescriptor[];
121
+ }): Promise<Awaited<TValue>>;
122
+ forget(key: CacheKeyInput<unknown>, options?: {
123
+ driver?: string;
124
+ }): Promise<boolean>;
125
+ invalidateDependencies(dependencies: readonly CacheDependencyDescriptor[], options?: {
126
+ driver?: string;
127
+ }): Promise<void>;
128
+ }
129
+ interface CacheRuntimeBindings {
130
+ readonly config: HoloCacheConfig | NormalizedHoloCacheConfig;
131
+ readonly databaseConfig?: HoloDatabaseConfig | NormalizedHoloDatabaseConfig;
132
+ readonly redisConfig?: HoloRedisConfig | NormalizedHoloRedisConfig;
133
+ readonly drivers?: ReadonlyMap<string, CacheDriverContract>;
134
+ readonly dependencyIndex?: CacheDependencyIndex;
135
+ readonly queryBridge?: CacheQueryBridge;
136
+ }
137
+ /**
138
+ * Cache reads return immutable snapshots.
139
+ * Arrays and plain objects returned by the cache are recursively frozen and
140
+ * must not be mutated in place.
141
+ */
142
+ interface CacheRepository {
143
+ /**
144
+ * Returns the cached value or `null` when the key is missing.
145
+ * Arrays and plain objects in the returned value are frozen snapshots.
146
+ */
147
+ get<TValue>(key: CacheKey<TValue>): Promise<TValue | null>;
148
+ get<TValue>(key: CacheKeyInput<TValue>, fallback: CacheFallback<TValue>): Promise<TValue>;
149
+ get<TValue>(key: string, fallback: CacheFallback<TValue>): Promise<TValue>;
150
+ get(key: string): Promise<unknown | null>;
151
+ put<TValue>(key: CacheKeyInput<TValue>, value: TValue, ttl: CacheTtlInput): Promise<boolean>;
152
+ add<TValue>(key: CacheKeyInput<TValue>, value: TValue, ttl: CacheTtlInput): Promise<boolean>;
153
+ forever<TValue>(key: CacheKeyInput<TValue>, value: TValue): Promise<boolean>;
154
+ has(key: CacheKeyInput<unknown>): Promise<boolean>;
155
+ missing(key: CacheKeyInput<unknown>): Promise<boolean>;
156
+ forget(key: CacheKeyInput<unknown>): Promise<boolean>;
157
+ flush(): Promise<void>;
158
+ increment(key: CacheKeyInput<number>, amount?: number): Promise<number>;
159
+ decrement(key: CacheKeyInput<number>, amount?: number): Promise<number>;
160
+ /**
161
+ * Resolves and stores the value on a cache miss.
162
+ * Arrays and plain objects returned from the cache path are frozen snapshots.
163
+ */
164
+ remember<TValue>(key: CacheKeyInput<Awaited<TValue>>, ttl: CacheTtlInput, callback: CacheValueResolver<TValue>): Promise<Awaited<TValue>>;
165
+ rememberForever<TValue>(key: CacheKeyInput<Awaited<TValue>>, callback: CacheValueResolver<TValue>): Promise<Awaited<TValue>>;
166
+ flexible<TValue>(key: CacheKeyInput<Awaited<TValue>>, ttl: CacheFlexibleTtlInput, callback: CacheValueResolver<TValue>): Promise<Awaited<TValue>>;
167
+ lock(name: string, seconds: number): CacheLockContract;
168
+ }
169
+ interface CacheFacade extends CacheRepository {
170
+ driver(name?: string): CacheRepository;
171
+ }
172
+ type EncodedCacheValue = null | string | number | boolean | readonly EncodedCacheValue[] | {
173
+ readonly __holo_cache_type?: 'date';
174
+ readonly value?: string;
175
+ readonly [key: string]: EncodedCacheValue | 'date' | string | undefined;
176
+ };
177
+ declare function isPlainObject(value: unknown): value is Record<string, unknown>;
178
+ declare function normalizeCacheStringKey(key: string): string;
179
+ declare function encodeCacheValue(value: unknown, path: string): EncodedCacheValue;
180
+ declare function decodeCacheValue(value: unknown, path: string): unknown;
181
+ declare function defineCacheKey<TValue>(key: string): CacheKey<TValue>;
182
+ declare function isCacheKey(value: unknown): value is CacheKey<unknown>;
183
+ declare function resolveCacheKey<TValue>(key: CacheKeyInput<TValue>): string;
184
+ declare function normalizeCacheTtl(ttl: CacheTtlInput, options?: {
185
+ now?: number | Date;
186
+ }): NormalizedCacheTtl;
187
+ declare function serializeCacheValue<TValue>(value: TValue): string;
188
+ declare function deserializeCacheValue<TValue>(payload: string): TValue;
189
+ declare const cacheContractsInternals: {
190
+ decodeCacheValue: typeof decodeCacheValue;
191
+ encodeCacheValue: typeof encodeCacheValue;
192
+ isPlainObject: typeof isPlainObject;
193
+ normalizeCacheStringKey: typeof normalizeCacheStringKey;
194
+ };
195
+
196
+ export { CacheConfigError, type CacheDependencyDescriptor, type CacheDependencyIndex, type CacheDriverContract, type CacheDriverGetResult, type CacheDriverPutInput, CacheDriverResolutionError, CacheError, type CacheErrorCode, type CacheFacade, type CacheFallback, type CacheFallbackResolver, type CacheFlexibleTtlInput, CacheInvalidNumericMutationError, CacheInvalidTtlError, type CacheKey, type CacheKeyInput, CacheLockAcquisitionError, type CacheLockContract, CacheOptionalPackageError, type CacheQueryBridge, CacheQueryIntegrationError, type CacheRepository, type CacheRuntimeBindings, CacheRuntimeNotConfiguredError, CacheSerializationError, type CacheTtlInput, type CacheValueResolver, type NormalizedCacheTtl, cacheContractsInternals, defineCacheKey, deserializeCacheValue, isCacheKey, normalizeCacheTtl, resolveCacheKey, serializeCacheValue };
@@ -0,0 +1,38 @@
1
+ import {
2
+ CacheConfigError,
3
+ CacheDriverResolutionError,
4
+ CacheError,
5
+ CacheInvalidNumericMutationError,
6
+ CacheInvalidTtlError,
7
+ CacheLockAcquisitionError,
8
+ CacheOptionalPackageError,
9
+ CacheQueryIntegrationError,
10
+ CacheRuntimeNotConfiguredError,
11
+ CacheSerializationError,
12
+ cacheContractsInternals,
13
+ defineCacheKey,
14
+ deserializeCacheValue,
15
+ isCacheKey,
16
+ normalizeCacheTtl,
17
+ resolveCacheKey,
18
+ serializeCacheValue
19
+ } from "./chunk-KIYULZES.mjs";
20
+ export {
21
+ CacheConfigError,
22
+ CacheDriverResolutionError,
23
+ CacheError,
24
+ CacheInvalidNumericMutationError,
25
+ CacheInvalidTtlError,
26
+ CacheLockAcquisitionError,
27
+ CacheOptionalPackageError,
28
+ CacheQueryIntegrationError,
29
+ CacheRuntimeNotConfiguredError,
30
+ CacheSerializationError,
31
+ cacheContractsInternals,
32
+ defineCacheKey,
33
+ deserializeCacheValue,
34
+ isCacheKey,
35
+ normalizeCacheTtl,
36
+ resolveCacheKey,
37
+ serializeCacheValue
38
+ };