@dynlabs/react-native-immutable-file-cache 1.0.0-alpha.2 → 1.0.0-alpha.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.
Files changed (73) hide show
  1. package/lib/commonjs/adapters/memoryAdapter.js +1 -0
  2. package/lib/commonjs/adapters/memoryAdapter.js.map +1 -1
  3. package/lib/commonjs/adapters/rnfsAdapter.js +9 -4
  4. package/lib/commonjs/adapters/rnfsAdapter.js.map +1 -1
  5. package/lib/commonjs/adapters/webAdapter.js +1 -0
  6. package/lib/commonjs/adapters/webAdapter.js.map +1 -1
  7. package/lib/commonjs/core/adapter.js +54 -0
  8. package/lib/commonjs/core/adapter.js.map +1 -1
  9. package/lib/commonjs/core/cacheEngine.js +452 -59
  10. package/lib/commonjs/core/cacheEngine.js.map +1 -1
  11. package/lib/commonjs/core/errors.js +9 -6
  12. package/lib/commonjs/core/errors.js.map +1 -1
  13. package/lib/commonjs/core/hash.js +3 -3
  14. package/lib/commonjs/core/hash.js.map +1 -1
  15. package/lib/commonjs/core/indexStore.js +85 -8
  16. package/lib/commonjs/core/indexStore.js.map +1 -1
  17. package/lib/commonjs/core/prune.js +42 -11
  18. package/lib/commonjs/core/prune.js.map +1 -1
  19. package/lib/commonjs/core/types.js +132 -0
  20. package/lib/commonjs/core/types.js.map +1 -1
  21. package/lib/commonjs/index.js +33 -0
  22. package/lib/commonjs/index.js.map +1 -1
  23. package/lib/module/adapters/memoryAdapter.js +1 -0
  24. package/lib/module/adapters/memoryAdapter.js.map +1 -1
  25. package/lib/module/adapters/rnfsAdapter.js +9 -4
  26. package/lib/module/adapters/rnfsAdapter.js.map +1 -1
  27. package/lib/module/adapters/webAdapter.js +1 -0
  28. package/lib/module/adapters/webAdapter.js.map +1 -1
  29. package/lib/module/core/adapter.js +48 -0
  30. package/lib/module/core/adapter.js.map +1 -1
  31. package/lib/module/core/cacheEngine.js +453 -60
  32. package/lib/module/core/cacheEngine.js.map +1 -1
  33. package/lib/module/core/errors.js +9 -6
  34. package/lib/module/core/errors.js.map +1 -1
  35. package/lib/module/core/hash.js +3 -3
  36. package/lib/module/core/hash.js.map +1 -1
  37. package/lib/module/core/indexStore.js +86 -8
  38. package/lib/module/core/indexStore.js.map +1 -1
  39. package/lib/module/core/prune.js +40 -11
  40. package/lib/module/core/prune.js.map +1 -1
  41. package/lib/module/core/types.js +130 -1
  42. package/lib/module/core/types.js.map +1 -1
  43. package/lib/module/index.js +4 -0
  44. package/lib/module/index.js.map +1 -1
  45. package/lib/typescript/src/adapters/memoryAdapter.d.ts.map +1 -1
  46. package/lib/typescript/src/adapters/rnfsAdapter.d.ts.map +1 -1
  47. package/lib/typescript/src/adapters/webAdapter.d.ts.map +1 -1
  48. package/lib/typescript/src/core/adapter.d.ts +16 -0
  49. package/lib/typescript/src/core/adapter.d.ts.map +1 -1
  50. package/lib/typescript/src/core/cacheEngine.d.ts +120 -1
  51. package/lib/typescript/src/core/cacheEngine.d.ts.map +1 -1
  52. package/lib/typescript/src/core/errors.d.ts +6 -5
  53. package/lib/typescript/src/core/errors.d.ts.map +1 -1
  54. package/lib/typescript/src/core/indexStore.d.ts +7 -0
  55. package/lib/typescript/src/core/indexStore.d.ts.map +1 -1
  56. package/lib/typescript/src/core/prune.d.ts +22 -8
  57. package/lib/typescript/src/core/prune.d.ts.map +1 -1
  58. package/lib/typescript/src/core/types.d.ts +153 -0
  59. package/lib/typescript/src/core/types.d.ts.map +1 -1
  60. package/lib/typescript/src/index.d.ts +5 -2
  61. package/lib/typescript/src/index.d.ts.map +1 -1
  62. package/package.json +1 -1
  63. package/src/adapters/memoryAdapter.ts +3 -0
  64. package/src/adapters/rnfsAdapter.ts +11 -4
  65. package/src/adapters/webAdapter.ts +1 -0
  66. package/src/core/adapter.ts +28 -0
  67. package/src/core/cacheEngine.ts +476 -62
  68. package/src/core/errors.ts +8 -6
  69. package/src/core/hash.ts +3 -3
  70. package/src/core/indexStore.ts +99 -11
  71. package/src/core/prune.ts +44 -14
  72. package/src/core/types.ts +194 -0
  73. package/src/index.ts +22 -0
@@ -2,12 +2,13 @@ import type { TBinarySource } from "./adapter";
2
2
 
3
3
  /**
4
4
  * Base error class for cache operations.
5
+ * Supports ES2022 Error.cause for proper error chaining.
5
6
  */
6
7
  export abstract class CacheError extends Error {
7
8
  abstract readonly code: string;
8
9
 
9
- constructor(message: string) {
10
- super(message);
10
+ constructor(message: string, options?: { cause?: Error }) {
11
+ super(message, options);
11
12
  this.name = this.constructor.name;
12
13
  Object.setPrototypeOf(this, new.target.prototype);
13
14
  }
@@ -36,10 +37,11 @@ export class AdapterIOError extends CacheError {
36
37
  constructor(
37
38
  public readonly operation: string,
38
39
  public readonly path: string,
39
- public readonly cause?: Error
40
+ cause?: Error
40
41
  ) {
41
42
  super(
42
- `Adapter I/O error during "${operation}" at path "${path}": ${cause?.message ?? "unknown"}`
43
+ `Adapter I/O error during "${operation}" at path "${path}": ${cause?.message ?? "unknown"}`,
44
+ cause ? { cause } : undefined
43
45
  );
44
46
  }
45
47
  }
@@ -52,9 +54,9 @@ export class CorruptIndexError extends CacheError {
52
54
 
53
55
  constructor(
54
56
  public readonly reason: string,
55
- public readonly cause?: Error
57
+ cause?: Error
56
58
  ) {
57
- super(`Cache index is corrupt: ${reason}`);
59
+ super(`Cache index is corrupt: ${reason}`, cause ? { cause } : undefined);
58
60
  }
59
61
  }
60
62
 
package/src/core/hash.ts CHANGED
@@ -8,11 +8,11 @@
8
8
  */
9
9
  function arrayBufferToHex(buffer: ArrayBuffer): string {
10
10
  const bytes = new Uint8Array(buffer);
11
- let hex = "";
11
+ const hexParts: string[] = new Array<string>(bytes.length);
12
12
  for (let i = 0; i < bytes.length; i++) {
13
- hex += bytes[i].toString(16).padStart(2, "0");
13
+ hexParts[i] = bytes[i].toString(16).padStart(2, "0");
14
14
  }
15
- return hex;
15
+ return hexParts.join("");
16
16
  }
17
17
 
18
18
  /**
@@ -3,7 +3,42 @@ import type { ICacheIndex, ICacheEntryMeta } from "./types";
3
3
  import { CorruptIndexError, AdapterIOError } from "./errors";
4
4
  import { createEmptyIndex } from "./prune";
5
5
 
6
- const INDEX_VERSION = 1;
6
+ /**
7
+ * Current index schema version.
8
+ * Increment when making breaking changes to ICacheIndex structure.
9
+ */
10
+ const CURRENT_INDEX_VERSION = 1;
11
+
12
+ /**
13
+ * Minimum supported version for migration.
14
+ * Versions below this will be rebuilt from scratch.
15
+ */
16
+ const MIN_SUPPORTED_VERSION = 1;
17
+
18
+ /**
19
+ * Migration function type.
20
+ * Takes an index at version N and returns an index at version N+1.
21
+ */
22
+ type TIndexMigrator = (index: unknown) => unknown;
23
+
24
+ /**
25
+ * Registry of migration functions.
26
+ * Key is the source version, value migrates to source version + 1.
27
+ *
28
+ * Example: When upgrading from v1 to v2, add:
29
+ * ```
30
+ * 1: (index) => {
31
+ * // Transform v1 index to v2 format
32
+ * return { ...index, version: 2, newField: defaultValue };
33
+ * }
34
+ * ```
35
+ */
36
+ const INDEX_MIGRATIONS: Record<number, TIndexMigrator> = {
37
+ // No migrations yet - v1 is the initial version
38
+ // Future migrations will be added here:
39
+ // 1: (index) => migrateV1ToV2(index),
40
+ // 2: (index) => migrateV2ToV3(index),
41
+ };
7
42
 
8
43
  /**
9
44
  * Manages persistence of the cache index via the storage adapter.
@@ -101,7 +136,7 @@ export class IndexStore {
101
136
  }
102
137
 
103
138
  const rebuiltIndex: ICacheIndex = {
104
- version: INDEX_VERSION,
139
+ version: CURRENT_INDEX_VERSION,
105
140
  entries,
106
141
  totalSizeBytes,
107
142
  lastModifiedAt: Date.now(),
@@ -114,18 +149,71 @@ export class IndexStore {
114
149
  }
115
150
 
116
151
  /**
117
- * Validates that the parsed object is a valid ICacheIndex.
152
+ * Applies migrations to upgrade an index from an older version to current.
153
+ * @param parsed - The parsed index object (may be any version)
154
+ * @returns The migrated index at current version
155
+ * @throws CorruptIndexError if version is unsupported or migration fails
118
156
  */
119
- private _validateIndex(parsed: unknown): ICacheIndex {
157
+ private _migrateIndex(parsed: unknown): unknown {
120
158
  if (typeof parsed !== "object" || parsed === null) {
121
159
  throw new CorruptIndexError("Index is not an object");
122
160
  }
123
161
 
124
162
  const obj = parsed as Record<string, unknown>;
163
+ let version = obj.version;
164
+
165
+ // Validate version is a number
166
+ if (typeof version !== "number") {
167
+ throw new CorruptIndexError(`Invalid version type: ${typeof version}`);
168
+ }
169
+
170
+ // Check if version is too old to migrate
171
+ if (version < MIN_SUPPORTED_VERSION) {
172
+ throw new CorruptIndexError(
173
+ `Version ${version} is too old. Minimum supported: ${MIN_SUPPORTED_VERSION}`
174
+ );
175
+ }
176
+
177
+ // Check if version is from the future
178
+ if (version > CURRENT_INDEX_VERSION) {
179
+ throw new CorruptIndexError(
180
+ `Version ${version} is newer than current (${CURRENT_INDEX_VERSION}). ` +
181
+ `Please upgrade the package.`
182
+ );
183
+ }
184
+
185
+ // Apply migrations sequentially
186
+ let migrated: unknown = parsed;
187
+ while (version < CURRENT_INDEX_VERSION) {
188
+ const migrator = INDEX_MIGRATIONS[version];
189
+ if (!migrator) {
190
+ throw new CorruptIndexError(`No migration path from version ${version} to ${version + 1}`);
191
+ }
192
+ migrated = migrator(migrated);
193
+ version++;
194
+ }
195
+
196
+ return migrated;
197
+ }
198
+
199
+ /**
200
+ * Validates that the parsed object is a valid ICacheIndex.
201
+ */
202
+ private _validateIndex(parsed: unknown): ICacheIndex {
203
+ // First, migrate to current version if needed
204
+ const migrated = this._migrateIndex(parsed);
205
+
206
+ if (typeof migrated !== "object" || migrated === null) {
207
+ throw new CorruptIndexError("Index is not an object");
208
+ }
209
+
210
+ const obj = migrated as Record<string, unknown>;
125
211
 
126
- // Check version
127
- if (obj.version !== INDEX_VERSION) {
128
- throw new CorruptIndexError(`Unsupported version: ${String(obj.version)}`);
212
+ // Verify version is current after migration
213
+ if (obj.version !== CURRENT_INDEX_VERSION) {
214
+ throw new CorruptIndexError(
215
+ `Migration failed: expected version ${CURRENT_INDEX_VERSION}, got ${String(obj.version)}`
216
+ );
129
217
  }
130
218
 
131
219
  // Check entries
@@ -150,7 +238,7 @@ export class IndexStore {
150
238
  }
151
239
 
152
240
  return {
153
- version: INDEX_VERSION,
241
+ version: CURRENT_INDEX_VERSION,
154
242
  entries: entries as Record<string, ICacheEntryMeta>,
155
243
  totalSizeBytes: obj.totalSizeBytes,
156
244
  lastModifiedAt: obj.lastModifiedAt,
@@ -160,14 +248,14 @@ export class IndexStore {
160
248
  /**
161
249
  * Validates that an entry has all required fields.
162
250
  */
163
- private _validateEntry(key: string, entry: unknown): void {
251
+ private _validateEntry(key: string, entry: unknown): asserts entry is ICacheEntryMeta {
164
252
  if (typeof entry !== "object" || entry === null) {
165
253
  throw new CorruptIndexError(`Entry "${key}" is not an object`);
166
254
  }
167
255
 
168
256
  const obj = entry as Record<string, unknown>;
169
- const requiredStrings = ["key", "hash", "ext"];
170
- const requiredNumbers = ["sizeBytes", "createdAt", "lastAccessedAt"];
257
+ const requiredStrings = ["key", "hash", "ext"] as const;
258
+ const requiredNumbers = ["sizeBytes", "createdAt", "lastAccessedAt"] as const;
171
259
 
172
260
  for (const field of requiredStrings) {
173
261
  if (typeof obj[field] !== "string") {
package/src/core/prune.ts CHANGED
@@ -1,11 +1,29 @@
1
1
  import type { ICacheIndex, ICacheEntryMeta } from "./types";
2
2
 
3
3
  /**
4
- * Options for pruning operations.
4
+ * Current index schema version.
5
+ * Keep in sync with indexStore.ts CURRENT_INDEX_VERSION.
5
6
  */
6
- export interface IPruneOptions {
7
- readonly maxSizeBytes?: number;
8
- readonly now?: number;
7
+ const INDEX_VERSION = 1;
8
+
9
+ /**
10
+ * Checks if a cache entry has expired.
11
+ * @param entry - The cache entry to check
12
+ * @param now - Current timestamp in milliseconds (defaults to Date.now())
13
+ * @returns true if the entry has expired, false otherwise
14
+ */
15
+ export function isEntryExpired(entry: ICacheEntryMeta, now: number = Date.now()): boolean {
16
+ return entry.expiresAt !== undefined && entry.expiresAt < now;
17
+ }
18
+
19
+ /**
20
+ * Checks if a cache entry is valid (not expired).
21
+ * @param entry - The cache entry to check
22
+ * @param now - Current timestamp in milliseconds (defaults to Date.now())
23
+ * @returns true if the entry is valid, false if expired
24
+ */
25
+ export function isEntryValid(entry: ICacheEntryMeta, now: number = Date.now()): boolean {
26
+ return entry.expiresAt === undefined || entry.expiresAt >= now;
9
27
  }
10
28
 
11
29
  /**
@@ -19,7 +37,7 @@ export function getExpiredEntries(
19
37
  const expired: ICacheEntryMeta[] = [];
20
38
 
21
39
  for (const entry of Object.values(index.entries)) {
22
- if (entry.expiresAt !== undefined && entry.expiresAt < now) {
40
+ if (isEntryExpired(entry, now)) {
23
41
  expired.push(entry);
24
42
  }
25
43
  }
@@ -62,10 +80,14 @@ export function getLruPruneTargets(
62
80
  /**
63
81
  * Creates a new index with the specified entries removed.
64
82
  * Returns a new ICacheIndex without mutating the original.
83
+ * @param index - The source index
84
+ * @param keysToRemove - Keys to remove from the index
85
+ * @param now - Optional timestamp for lastModifiedAt (defaults to Date.now())
65
86
  */
66
87
  export function removeEntriesFromIndex(
67
88
  index: ICacheIndex,
68
- keysToRemove: ReadonlyArray<string>
89
+ keysToRemove: ReadonlyArray<string>,
90
+ now: number = Date.now()
69
91
  ): ICacheIndex {
70
92
  const keySet = new Set(keysToRemove);
71
93
  const newEntries: Record<string, ICacheEntryMeta> = {};
@@ -79,29 +101,36 @@ export function removeEntriesFromIndex(
79
101
  }
80
102
 
81
103
  return {
82
- version: 1,
104
+ version: INDEX_VERSION,
83
105
  entries: newEntries,
84
106
  totalSizeBytes: newTotalSize,
85
- lastModifiedAt: Date.now(),
107
+ lastModifiedAt: now,
86
108
  };
87
109
  }
88
110
 
89
111
  /**
90
112
  * Adds or updates an entry in the index.
91
113
  * Returns a new ICacheIndex without mutating the original.
114
+ * @param index - The source index
115
+ * @param entry - The entry to add or update
116
+ * @param now - Optional timestamp for lastModifiedAt (defaults to Date.now())
92
117
  */
93
- export function addEntryToIndex(index: ICacheIndex, entry: ICacheEntryMeta): ICacheIndex {
118
+ export function addEntryToIndex(
119
+ index: ICacheIndex,
120
+ entry: ICacheEntryMeta,
121
+ now: number = Date.now()
122
+ ): ICacheIndex {
94
123
  const existingEntry = index.entries[entry.key];
95
124
  const sizeDelta = entry.sizeBytes - (existingEntry?.sizeBytes ?? 0);
96
125
 
97
126
  return {
98
- version: 1,
127
+ version: INDEX_VERSION,
99
128
  entries: {
100
129
  ...index.entries,
101
130
  [entry.key]: entry,
102
131
  },
103
132
  totalSizeBytes: index.totalSizeBytes + sizeDelta,
104
- lastModifiedAt: Date.now(),
133
+ lastModifiedAt: now,
105
134
  };
106
135
  }
107
136
 
@@ -134,12 +163,13 @@ export function touchEntry(
134
163
 
135
164
  /**
136
165
  * Creates an empty cache index.
166
+ * @param now - Optional timestamp for lastModifiedAt (defaults to Date.now())
137
167
  */
138
- export function createEmptyIndex(): ICacheIndex {
168
+ export function createEmptyIndex(now: number = Date.now()): ICacheIndex {
139
169
  return {
140
- version: 1,
170
+ version: INDEX_VERSION,
141
171
  entries: {},
142
172
  totalSizeBytes: 0,
143
- lastModifiedAt: Date.now(),
173
+ lastModifiedAt: now,
144
174
  };
145
175
  }
package/src/core/types.ts CHANGED
@@ -1,5 +1,138 @@
1
1
  import type { IStorageAdapter, TAdapterPath } from "./adapter";
2
2
 
3
+ // ─────────────────────────────────────────────────────────────────
4
+ // Result Type (for internal use)
5
+ // ─────────────────────────────────────────────────────────────────
6
+
7
+ /**
8
+ * Represents a successful result.
9
+ */
10
+ export interface IOk<T> {
11
+ readonly ok: true;
12
+ readonly value: T;
13
+ }
14
+
15
+ /**
16
+ * Represents a failure result.
17
+ */
18
+ export interface IErr<E> {
19
+ readonly ok: false;
20
+ readonly error: E;
21
+ }
22
+
23
+ /**
24
+ * Discriminated union for operation results.
25
+ * Enables explicit error handling without exceptions.
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * function divide(a: number, b: number): TResult<number, string> {
30
+ * if (b === 0) return { ok: false, error: "Division by zero" };
31
+ * return { ok: true, value: a / b };
32
+ * }
33
+ *
34
+ * const result = divide(10, 2);
35
+ * if (result.ok) {
36
+ * console.log(result.value); // 5
37
+ * } else {
38
+ * console.error(result.error);
39
+ * }
40
+ * ```
41
+ */
42
+ export type TResult<T, E = Error> = IOk<T> | IErr<E>;
43
+
44
+ /**
45
+ * Helper to create a success result.
46
+ */
47
+ export function ok<T>(value: T): IOk<T> {
48
+ return { ok: true, value };
49
+ }
50
+
51
+ /**
52
+ * Helper to create a failure result.
53
+ */
54
+ export function err<E>(error: E): IErr<E> {
55
+ return { ok: false, error };
56
+ }
57
+
58
+ // ─────────────────────────────────────────────────────────────────
59
+ // Cache Events (Observability)
60
+ // ─────────────────────────────────────────────────────────────────
61
+
62
+ /**
63
+ * Event emitted when a cache hit occurs.
64
+ */
65
+ export interface ICacheHitEvent {
66
+ readonly type: "cache_hit";
67
+ readonly key: string;
68
+ readonly sizeBytes: number;
69
+ readonly ageMs: number;
70
+ }
71
+
72
+ /**
73
+ * Event emitted when a cache miss occurs.
74
+ */
75
+ export interface ICacheMissEvent {
76
+ readonly type: "cache_miss";
77
+ readonly key: string;
78
+ readonly reason: "not_found" | "expired";
79
+ }
80
+
81
+ /**
82
+ * Event emitted when an entry is created.
83
+ */
84
+ export interface ICacheWriteEvent {
85
+ readonly type: "cache_write";
86
+ readonly key: string;
87
+ readonly sizeBytes: number;
88
+ readonly source: "url" | "file" | "blob" | "bytes";
89
+ readonly durationMs: number;
90
+ }
91
+
92
+ /**
93
+ * Event emitted when an entry is removed.
94
+ */
95
+ export interface ICacheRemoveEvent {
96
+ readonly type: "cache_remove";
97
+ readonly key: string;
98
+ readonly reason: "explicit" | "expired" | "lru";
99
+ }
100
+
101
+ /**
102
+ * Event emitted during pruning operations.
103
+ */
104
+ export interface ICachePruneEvent {
105
+ readonly type: "cache_prune";
106
+ readonly reason: "expired" | "lru";
107
+ readonly removedCount: number;
108
+ readonly freedBytes: number;
109
+ }
110
+
111
+ /**
112
+ * Event emitted on errors (non-fatal).
113
+ */
114
+ export interface ICacheErrorEvent {
115
+ readonly type: "cache_error";
116
+ readonly operation: string;
117
+ readonly error: Error;
118
+ }
119
+
120
+ /**
121
+ * Union of all cache events.
122
+ */
123
+ export type TCacheEvent =
124
+ | ICacheHitEvent
125
+ | ICacheMissEvent
126
+ | ICacheWriteEvent
127
+ | ICacheRemoveEvent
128
+ | ICachePruneEvent
129
+ | ICacheErrorEvent;
130
+
131
+ /**
132
+ * Callback for receiving cache events.
133
+ */
134
+ export type TCacheEventHandler = (event: TCacheEvent) => void;
135
+
3
136
  // ─────────────────────────────────────────────────────────────────
4
137
  // Cache Configuration
5
138
  // ─────────────────────────────────────────────────────────────────
@@ -39,6 +172,29 @@ export interface ICacheConfig {
39
172
  * @default SHA-256 hex
40
173
  */
41
174
  readonly hashFn?: (input: string) => string | Promise<string>;
175
+
176
+ /**
177
+ * Event handler for observability/instrumentation.
178
+ * Called on cache hits, misses, writes, removes, and errors.
179
+ * @default undefined (no events emitted)
180
+ */
181
+ readonly onEvent?: TCacheEventHandler;
182
+
183
+ /**
184
+ * Custom time source function for testing.
185
+ * Returns current time in milliseconds since epoch.
186
+ * @default Date.now
187
+ */
188
+ readonly now?: () => number;
189
+
190
+ /**
191
+ * Debounce time for index writes in milliseconds.
192
+ * When > 0, index writes are batched and written after this delay.
193
+ * Use 0 for immediate writes (default behavior).
194
+ * Call flush() to force immediate persistence when using debounce.
195
+ * @default 0 (immediate writes)
196
+ */
197
+ readonly indexWriteDebounceMs?: number;
42
198
  }
43
199
 
44
200
  // ─────────────────────────────────────────────────────────────────
@@ -163,3 +319,41 @@ export interface ICacheStats {
163
319
  readonly oldestEntry?: ICacheEntryMeta;
164
320
  readonly newestEntry?: ICacheEntryMeta;
165
321
  }
322
+
323
+ // ─────────────────────────────────────────────────────────────────
324
+ // Read-Through Cache (getOrPut)
325
+ // ─────────────────────────────────────────────────────────────────
326
+
327
+ /**
328
+ * Fetcher result for getOrPut operations.
329
+ * Provides the data to cache along with optional configuration.
330
+ */
331
+ export interface IFetcherResult {
332
+ /** The data to cache, as Uint8Array bytes. */
333
+ readonly bytes: Uint8Array;
334
+ /** Optional TTL override for this entry. */
335
+ readonly ttlMs?: number;
336
+ /** Optional file extension (e.g., ".json"). */
337
+ readonly ext?: string;
338
+ /** Optional user metadata. */
339
+ readonly metadata?: Readonly<Record<string, unknown>>;
340
+ }
341
+
342
+ /**
343
+ * Fetcher function type for getOrPut.
344
+ * Called when the key doesn't exist in cache.
345
+ * Should return the data to be cached, or throw to skip caching.
346
+ */
347
+ export type TFetcher = (key: string) => Promise<IFetcherResult>;
348
+
349
+ /**
350
+ * Result of a getOrPut operation.
351
+ */
352
+ export interface IGetOrPutResult {
353
+ /** The cached entry. */
354
+ readonly entry: ICacheEntry;
355
+ /** The public URI for accessing the entry. */
356
+ readonly uri: string;
357
+ /** Whether the entry was fetched (true) or already cached (false). */
358
+ readonly fetched: boolean;
359
+ }
package/src/index.ts CHANGED
@@ -12,12 +12,15 @@ export type {
12
12
  IStorageAdapter,
13
13
  TAdapterPath,
14
14
  TBinarySource,
15
+ TBinarySourceType,
15
16
  IBinaryWriteResult,
16
17
  IBinaryWriteOptions,
17
18
  IFileStat,
18
19
  TProgressCallback,
19
20
  } from "./core/adapter";
20
21
 
22
+ export { adapterSupportsSource } from "./core/adapter";
23
+
21
24
  export type {
22
25
  ICacheConfig,
23
26
  ICacheEntry,
@@ -26,14 +29,32 @@ export type {
26
29
  IPutOptions,
27
30
  IPutResult,
28
31
  IGetResult,
32
+ IGetOrPutResult,
33
+ TFetcher,
34
+ IFetcherResult,
29
35
  IListOptions,
30
36
  IPruneResult,
31
37
  ICacheStats,
32
38
  TPutStatus,
33
39
  TSortField,
34
40
  TSortOrder,
41
+ // Result type utilities
42
+ TResult,
43
+ IOk,
44
+ IErr,
45
+ // Event types for observability
46
+ TCacheEvent,
47
+ TCacheEventHandler,
48
+ ICacheHitEvent,
49
+ ICacheMissEvent,
50
+ ICacheWriteEvent,
51
+ ICacheRemoveEvent,
52
+ ICachePruneEvent,
53
+ ICacheErrorEvent,
35
54
  } from "./core/types";
36
55
 
56
+ export { ok, err } from "./core/types";
57
+
37
58
  // ─────────────────────────────────────────────────────────────────
38
59
  // Errors
39
60
  // ─────────────────────────────────────────────────────────────────
@@ -80,3 +101,4 @@ export type { IMemoryAdapter } from "./adapters/memoryAdapter";
80
101
 
81
102
  export { hash, hashSync } from "./core/hash";
82
103
  export { Mutex, KeyedMutex } from "./core/mutex";
104
+ export { isEntryExpired, isEntryValid } from "./core/prune";