@orkify/cli 1.0.0-beta.5 → 1.0.0-beta.6

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 (90) hide show
  1. package/README.md +10 -5
  2. package/package.json +8 -31
  3. package/packages/cache/README.md +0 -114
  4. package/packages/cache/dist/CacheClient.d.ts +0 -26
  5. package/packages/cache/dist/CacheClient.d.ts.map +0 -1
  6. package/packages/cache/dist/CacheClient.js +0 -174
  7. package/packages/cache/dist/CacheClient.js.map +0 -1
  8. package/packages/cache/dist/CacheFileStore.d.ts +0 -45
  9. package/packages/cache/dist/CacheFileStore.d.ts.map +0 -1
  10. package/packages/cache/dist/CacheFileStore.js +0 -446
  11. package/packages/cache/dist/CacheFileStore.js.map +0 -1
  12. package/packages/cache/dist/CachePersistence.d.ts +0 -9
  13. package/packages/cache/dist/CachePersistence.d.ts.map +0 -1
  14. package/packages/cache/dist/CachePersistence.js +0 -67
  15. package/packages/cache/dist/CachePersistence.js.map +0 -1
  16. package/packages/cache/dist/CachePrimary.d.ts +0 -25
  17. package/packages/cache/dist/CachePrimary.d.ts.map +0 -1
  18. package/packages/cache/dist/CachePrimary.js +0 -155
  19. package/packages/cache/dist/CachePrimary.js.map +0 -1
  20. package/packages/cache/dist/CacheStore.d.ts +0 -50
  21. package/packages/cache/dist/CacheStore.d.ts.map +0 -1
  22. package/packages/cache/dist/CacheStore.js +0 -271
  23. package/packages/cache/dist/CacheStore.js.map +0 -1
  24. package/packages/cache/dist/constants.d.ts +0 -6
  25. package/packages/cache/dist/constants.d.ts.map +0 -1
  26. package/packages/cache/dist/constants.js +0 -9
  27. package/packages/cache/dist/constants.js.map +0 -1
  28. package/packages/cache/dist/index.d.ts +0 -16
  29. package/packages/cache/dist/index.d.ts.map +0 -1
  30. package/packages/cache/dist/index.js +0 -86
  31. package/packages/cache/dist/index.js.map +0 -1
  32. package/packages/cache/dist/serialize.d.ts +0 -9
  33. package/packages/cache/dist/serialize.d.ts.map +0 -1
  34. package/packages/cache/dist/serialize.js +0 -40
  35. package/packages/cache/dist/serialize.js.map +0 -1
  36. package/packages/cache/dist/types.d.ts +0 -123
  37. package/packages/cache/dist/types.d.ts.map +0 -1
  38. package/packages/cache/dist/types.js +0 -2
  39. package/packages/cache/dist/types.js.map +0 -1
  40. package/packages/cache/package.json +0 -27
  41. package/packages/cache/src/CacheClient.ts +0 -227
  42. package/packages/cache/src/CacheFileStore.ts +0 -528
  43. package/packages/cache/src/CachePersistence.ts +0 -89
  44. package/packages/cache/src/CachePrimary.ts +0 -172
  45. package/packages/cache/src/CacheStore.ts +0 -308
  46. package/packages/cache/src/constants.ts +0 -10
  47. package/packages/cache/src/index.ts +0 -100
  48. package/packages/cache/src/serialize.ts +0 -49
  49. package/packages/cache/src/types.ts +0 -156
  50. package/packages/cache/tsconfig.json +0 -18
  51. package/packages/cache/tsconfig.tsbuildinfo +0 -1
  52. package/packages/next/README.md +0 -166
  53. package/packages/next/dist/error-capture.d.ts +0 -34
  54. package/packages/next/dist/error-capture.d.ts.map +0 -1
  55. package/packages/next/dist/error-capture.js +0 -130
  56. package/packages/next/dist/error-capture.js.map +0 -1
  57. package/packages/next/dist/error-handler.d.ts +0 -10
  58. package/packages/next/dist/error-handler.d.ts.map +0 -1
  59. package/packages/next/dist/error-handler.js +0 -186
  60. package/packages/next/dist/error-handler.js.map +0 -1
  61. package/packages/next/dist/isr-cache.d.ts +0 -9
  62. package/packages/next/dist/isr-cache.d.ts.map +0 -1
  63. package/packages/next/dist/isr-cache.js +0 -86
  64. package/packages/next/dist/isr-cache.js.map +0 -1
  65. package/packages/next/dist/stream.d.ts +0 -5
  66. package/packages/next/dist/stream.d.ts.map +0 -1
  67. package/packages/next/dist/stream.js +0 -22
  68. package/packages/next/dist/stream.js.map +0 -1
  69. package/packages/next/dist/types.d.ts +0 -33
  70. package/packages/next/dist/types.d.ts.map +0 -1
  71. package/packages/next/dist/types.js +0 -6
  72. package/packages/next/dist/types.js.map +0 -1
  73. package/packages/next/dist/use-cache.d.ts +0 -4
  74. package/packages/next/dist/use-cache.d.ts.map +0 -1
  75. package/packages/next/dist/use-cache.js +0 -86
  76. package/packages/next/dist/use-cache.js.map +0 -1
  77. package/packages/next/dist/utils.d.ts +0 -32
  78. package/packages/next/dist/utils.d.ts.map +0 -1
  79. package/packages/next/dist/utils.js +0 -88
  80. package/packages/next/dist/utils.js.map +0 -1
  81. package/packages/next/package.json +0 -52
  82. package/packages/next/src/error-capture.ts +0 -177
  83. package/packages/next/src/error-handler.ts +0 -221
  84. package/packages/next/src/isr-cache.ts +0 -100
  85. package/packages/next/src/stream.ts +0 -23
  86. package/packages/next/src/types.ts +0 -33
  87. package/packages/next/src/use-cache.ts +0 -99
  88. package/packages/next/src/utils.ts +0 -102
  89. package/packages/next/tsconfig.json +0 -19
  90. package/packages/next/tsconfig.tsbuildinfo +0 -1
@@ -1,528 +0,0 @@
1
- import { createHash } from 'node:crypto';
2
- import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
3
- import { mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
4
- import { join } from 'node:path';
5
- import type { CacheConfig, CacheEntry, CacheSnapshot, CacheStats, ICacheStore } from './types.js';
6
- import { CACHE_CLEANUP_INTERVAL } from './constants.js';
7
- import { CacheStore } from './CacheStore.js';
8
- import { deserialize, serialize, type Serialized, serializedByteLength } from './serialize.js';
9
-
10
- /** Metadata stored in the disk index (no values — those are in individual files) */
11
- interface DiskMeta {
12
- expiresAt?: number;
13
- file: string; // sha256.json
14
- tags?: string[];
15
- timestamp: number; // epoch ms when entry was written to disk
16
- }
17
-
18
- /** On-disk entry file format */
19
- interface DiskEntry {
20
- expiresAt?: number;
21
- key: string;
22
- tags?: string[];
23
- timestamp: number; // epoch ms when entry was written to disk
24
- value: Serialized;
25
- }
26
-
27
- /** On-disk index format */
28
- interface DiskIndex {
29
- entries: Record<string, DiskMeta>;
30
- tagTimestamps: Array<[string, number]>;
31
- }
32
-
33
- export class CacheFileStore implements ICacheStore {
34
- private cacheDir: string;
35
- private diskIndex = new Map<string, DiskMeta>();
36
- private diskSweepTimer: ReturnType<typeof setInterval> | undefined;
37
- private diskTagIndex = new Map<string, Set<string>>(); // tag → disk keys
38
- private entriesDir: string;
39
- private indexPath: string;
40
- private indexDirty = false;
41
- private loadIndexPromise: Promise<void> | undefined;
42
- private persistPromise: Promise<void> | undefined;
43
- private readOnly: boolean;
44
- private store: CacheStore;
45
- private tagTimestamps = new Map<string, number>(); // disk-only tag timestamps
46
-
47
- constructor(processName: string, config?: CacheConfig, options?: { readOnly?: boolean }) {
48
- this.readOnly = options?.readOnly ?? false;
49
- this.cacheDir = join(
50
- process.env.HOME ?? process.env.USERPROFILE ?? '.',
51
- '.orkify',
52
- 'cache',
53
- processName
54
- );
55
- this.entriesDir = join(this.cacheDir, 'entries');
56
- this.indexPath = join(this.cacheDir, 'index.json');
57
-
58
- this.store = new CacheStore(
59
- config,
60
- this.readOnly
61
- ? undefined
62
- : (key, entry, reason) => {
63
- if (reason === 'lru') {
64
- // Spill evicted entry to disk (fire-and-forget)
65
- void this.writeToDisk(key, entry);
66
- } else if (reason === 'expired') {
67
- // Clean expired entry from disk
68
- void this.deleteFromDisk(key);
69
- }
70
- }
71
- );
72
-
73
- if (!this.readOnly) {
74
- // Periodic disk sweep for expired entries
75
- this.diskSweepTimer = setInterval(() => void this.sweepDisk(), CACHE_CLEANUP_INTERVAL);
76
- this.diskSweepTimer.unref();
77
-
78
- // Load disk index from any previous session (entries promoted lazily via getAsync).
79
- // Store the promise so getAsync can await it before checking the index.
80
- this.loadIndexPromise = this.loadIndex();
81
- }
82
- }
83
-
84
- // --- ICacheStore public API ---
85
-
86
- get<T>(key: string): T | undefined {
87
- return this.store.get<T>(key);
88
- }
89
-
90
- async getAsync<T>(key: string): Promise<T | undefined> {
91
- // Hot path: sync memory lookup
92
- const memValue = this.store.get<T>(key);
93
- if (memValue !== undefined) return memValue;
94
-
95
- // Ensure disk index is loaded from any previous session before checking it
96
- if (this.loadIndexPromise) {
97
- await this.loadIndexPromise;
98
- this.loadIndexPromise = undefined;
99
- }
100
-
101
- // Cold path: check disk index (populated in full mode, empty in readOnly mode)
102
- const meta = this.diskIndex.get(key);
103
-
104
- // In full mode, if not in index it's a true miss.
105
- // In readOnly mode, diskIndex is empty — always try file directly.
106
- if (!meta && !this.readOnly) return undefined;
107
-
108
- // Compute file path: from index if available, otherwise derive from key hash
109
- const fileName = meta?.file ?? createHash('sha256').update(key).digest('hex') + '.json';
110
- const filePath = join(this.entriesDir, fileName);
111
-
112
- try {
113
- const content = await readFile(filePath, 'utf-8');
114
- const disk: DiskEntry = JSON.parse(content);
115
-
116
- // Check TTL expiration
117
- if (disk.expiresAt !== undefined && disk.expiresAt < Date.now()) {
118
- if (!this.readOnly) void this.deleteFromDisk(key);
119
- return undefined;
120
- }
121
-
122
- // Check tag timestamps — if any tag was invalidated after the entry was written, it's stale
123
- if (disk.tags && disk.tags.length > 0) {
124
- const tagExp = this.getTagExpiration(disk.tags);
125
- if (tagExp > disk.timestamp) {
126
- if (!this.readOnly) void this.deleteFromDisk(key);
127
- return undefined;
128
- }
129
- }
130
-
131
- // Deserialize the value
132
- const value = deserialize(disk.value) as T;
133
-
134
- // Promote to memory (may evict other entries to disk via callback in full mode;
135
- // in readOnly mode evictions just drop since there's no onEvict)
136
- this.store.set(key, value, disk.expiresAt, disk.tags);
137
-
138
- // In full mode, remove from disk index (it's now in memory)
139
- if (meta && !this.readOnly) {
140
- this.removeDiskMeta(key);
141
- }
142
-
143
- return value;
144
- } catch {
145
- // File read/parse failed — remove stale index entry in full mode
146
- if (meta && !this.readOnly) {
147
- this.removeDiskMeta(key);
148
- }
149
- return undefined;
150
- }
151
- }
152
-
153
- set(
154
- key: string,
155
- value: unknown,
156
- expiresAt?: number,
157
- tags?: string[],
158
- precomputedByteSize?: number
159
- ): void {
160
- // Remove from disk if it exists there (we're overwriting)
161
- if (this.diskIndex.has(key)) {
162
- void this.deleteFromDisk(key);
163
- }
164
- this.store.set(key, value, expiresAt, tags, precomputedByteSize);
165
- }
166
-
167
- delete(key: string): boolean {
168
- const memDeleted = this.store.delete(key);
169
- const diskHad = this.diskIndex.has(key);
170
- if (diskHad) {
171
- void this.deleteFromDisk(key);
172
- }
173
- return memDeleted || diskHad;
174
- }
175
-
176
- clear(): void {
177
- this.store.clear();
178
- // Clear all disk entries
179
- const diskKeys = [...this.diskIndex.keys()];
180
- for (const key of diskKeys) {
181
- void this.deleteFromDisk(key);
182
- }
183
- this.diskIndex.clear();
184
- this.diskTagIndex.clear();
185
- this.tagTimestamps.clear();
186
- this.indexDirty = true;
187
- void this.persistIndex();
188
- }
189
-
190
- has(key: string): boolean {
191
- if (this.store.has(key)) return true;
192
- const meta = this.diskIndex.get(key);
193
- if (!meta) return false;
194
- if (meta.expiresAt !== undefined && meta.expiresAt < Date.now()) {
195
- if (!this.readOnly) void this.deleteFromDisk(key);
196
- return false;
197
- }
198
- return true;
199
- }
200
-
201
- stats(): CacheStats {
202
- const base = this.store.stats();
203
- return { ...base, diskSize: this.diskIndex.size };
204
- }
205
-
206
- invalidateTag(tag: string): string[] {
207
- // Invalidate in memory
208
- const deleted = this.store.invalidateTag(tag);
209
-
210
- // Record tag timestamp for disk entries
211
- this.tagTimestamps.set(tag, Date.now());
212
-
213
- // Delete disk entries with this tag
214
- const diskKeys = this.diskTagIndex.get(tag);
215
- if (diskKeys && diskKeys.size > 0) {
216
- for (const key of [...diskKeys]) {
217
- void this.deleteFromDisk(key);
218
- }
219
- }
220
-
221
- return deleted;
222
- }
223
-
224
- getTagExpiration(tags: string[]): number {
225
- // Check both in-memory store and disk-level tag timestamps
226
- const memExp = this.store.getTagExpiration(tags);
227
- let diskExp = 0;
228
- for (const tag of tags) {
229
- const ts = this.tagTimestamps.get(tag);
230
- if (ts !== undefined && ts > diskExp) {
231
- diskExp = ts;
232
- }
233
- }
234
- return Math.max(memExp, diskExp);
235
- }
236
-
237
- applyTagTimestamp(tag: string, timestamp: number): void {
238
- this.store.applyTagTimestamp(tag, timestamp);
239
- this.tagTimestamps.set(tag, timestamp);
240
- }
241
-
242
- applySet(key: string, value: unknown, expiresAt?: number, tags?: string[]): void {
243
- if (this.diskIndex.has(key)) {
244
- void this.deleteFromDisk(key);
245
- }
246
- this.store.applySet(key, value, expiresAt, tags);
247
- }
248
-
249
- applyDelete(key: string): void {
250
- this.store.applyDelete(key);
251
- if (this.diskIndex.has(key)) {
252
- void this.deleteFromDisk(key);
253
- }
254
- }
255
-
256
- applySnapshot(snapshot: CacheSnapshot): void {
257
- this.store.applySnapshot(snapshot);
258
- // Snapshot replaces everything — clear disk too
259
- for (const key of [...this.diskIndex.keys()]) {
260
- void this.deleteFromDisk(key);
261
- }
262
- this.diskIndex.clear();
263
- this.diskTagIndex.clear();
264
- // Merge tag timestamps from snapshot
265
- this.tagTimestamps.clear();
266
- for (const [tag, ts] of snapshot.tagTimestamps) {
267
- this.tagTimestamps.set(tag, ts);
268
- }
269
- this.indexDirty = true;
270
- void this.persistIndex();
271
- }
272
-
273
- serialize(): CacheSnapshot {
274
- return this.store.serialize();
275
- }
276
-
277
- destroy(): void {
278
- if (this.diskSweepTimer) {
279
- clearInterval(this.diskSweepTimer);
280
- this.diskSweepTimer = undefined;
281
- }
282
- this.store.destroy();
283
- this.diskIndex.clear();
284
- this.diskTagIndex.clear();
285
- this.tagTimestamps.clear();
286
- }
287
-
288
- // --- File-backed specific methods ---
289
-
290
- /** Flush all in-memory entries to disk (called on graceful shutdown). No-op in readOnly mode. */
291
- async flush(): Promise<void> {
292
- if (this.readOnly) return;
293
- const snapshot = this.store.serialize();
294
- for (const [key, entry] of snapshot.entries) {
295
- const byteSize = serializedByteLength(serialize(entry.value));
296
- await this.writeToDisk(key, {
297
- byteSize,
298
- value: entry.value,
299
- expiresAt: entry.expiresAt,
300
- lastAccessedAt: Date.now(),
301
- tags: entry.tags,
302
- });
303
- }
304
- // Force a final index write — fire-and-forget calls from writeToDisk may have
305
- // already persisted a partial index and cleared the dirty flag
306
- this.indexDirty = true;
307
- await this.persistIndex();
308
- }
309
-
310
- /** Synchronous flush for use in process 'exit' handlers where async I/O is unavailable. */
311
- flushSync(): void {
312
- if (this.readOnly) return;
313
- const snapshot = this.store.serialize();
314
- if (snapshot.entries.length === 0) return;
315
-
316
- mkdirSync(this.entriesDir, { recursive: true });
317
-
318
- for (const [key, entry] of snapshot.entries) {
319
- const fileName = createHash('sha256').update(key).digest('hex') + '.json';
320
- const filePath = join(this.entriesDir, fileName);
321
- const now = Date.now();
322
- const disk: DiskEntry = {
323
- key,
324
- value: serialize(entry.value),
325
- expiresAt: entry.expiresAt,
326
- tags: entry.tags,
327
- timestamp: now,
328
- };
329
- writeFileSync(filePath, JSON.stringify(disk), 'utf-8');
330
-
331
- const meta: DiskMeta = { file: fileName, expiresAt: entry.expiresAt, timestamp: now };
332
- if (entry.tags && entry.tags.length > 0) meta.tags = entry.tags;
333
- this.diskIndex.set(key, meta);
334
- if (entry.tags) {
335
- for (const tag of entry.tags) {
336
- let keys = this.diskTagIndex.get(tag);
337
- if (!keys) {
338
- keys = new Set();
339
- this.diskTagIndex.set(tag, keys);
340
- }
341
- keys.add(key);
342
- }
343
- }
344
- }
345
-
346
- mkdirSync(this.cacheDir, { recursive: true });
347
- const data: DiskIndex = {
348
- entries: Object.fromEntries(this.diskIndex),
349
- tagTimestamps: [...this.tagTimestamps],
350
- };
351
- writeFileSync(this.indexPath, JSON.stringify(data), 'utf-8');
352
- }
353
-
354
- /** Load disk index on startup (values are loaded lazily on access). No-op in readOnly mode. */
355
- async loadIndex(): Promise<void> {
356
- if (this.readOnly) return;
357
- if (!existsSync(this.indexPath)) return;
358
-
359
- try {
360
- const content = await readFile(this.indexPath, 'utf-8');
361
- const data: DiskIndex = JSON.parse(content);
362
-
363
- this.diskIndex.clear();
364
- this.diskTagIndex.clear();
365
-
366
- const now = Date.now();
367
- for (const [key, meta] of Object.entries(data.entries)) {
368
- // Skip expired entries
369
- if (meta.expiresAt !== undefined && meta.expiresAt < now) continue;
370
- // Ensure timestamp exists (older indexes may lack it)
371
- if (!meta.timestamp) meta.timestamp = now;
372
- this.diskIndex.set(key, meta);
373
- if (meta.tags) {
374
- for (const tag of meta.tags) {
375
- let keys = this.diskTagIndex.get(tag);
376
- if (!keys) {
377
- keys = new Set();
378
- this.diskTagIndex.set(tag, keys);
379
- }
380
- keys.add(key);
381
- }
382
- }
383
- }
384
-
385
- // Restore tag timestamps
386
- if (data.tagTimestamps) {
387
- for (const [tag, ts] of data.tagTimestamps) {
388
- this.tagTimestamps.set(tag, ts);
389
- this.store.applyTagTimestamp(tag, ts);
390
- }
391
- }
392
- } catch {
393
- // Corrupted index — start fresh
394
- this.diskIndex.clear();
395
- this.diskTagIndex.clear();
396
- }
397
- }
398
-
399
- // --- Private helpers ---
400
-
401
- private async writeToDisk(key: string, entry: CacheEntry): Promise<void> {
402
- try {
403
- await mkdir(this.entriesDir, { recursive: true });
404
-
405
- const fileName = createHash('sha256').update(key).digest('hex') + '.json';
406
- const filePath = join(this.entriesDir, fileName);
407
-
408
- const now = Date.now();
409
- const disk: DiskEntry = {
410
- key,
411
- value: serialize(entry.value),
412
- expiresAt: entry.expiresAt,
413
- tags: entry.tags,
414
- timestamp: now,
415
- };
416
-
417
- // Atomic write
418
- const tmpPath = filePath + '.tmp';
419
- await writeFile(tmpPath, JSON.stringify(disk), 'utf-8');
420
- await rename(tmpPath, filePath);
421
-
422
- // Update disk index
423
- const meta: DiskMeta = { file: fileName, expiresAt: entry.expiresAt, timestamp: now };
424
- if (entry.tags && entry.tags.length > 0) {
425
- meta.tags = entry.tags;
426
- }
427
- this.diskIndex.set(key, meta);
428
-
429
- // Update disk tag index
430
- if (entry.tags) {
431
- for (const tag of entry.tags) {
432
- let keys = this.diskTagIndex.get(tag);
433
- if (!keys) {
434
- keys = new Set();
435
- this.diskTagIndex.set(tag, keys);
436
- }
437
- keys.add(key);
438
- }
439
- }
440
-
441
- this.indexDirty = true;
442
- void this.persistIndex();
443
- } catch {
444
- // Disk write failed — entry stays in-memory only
445
- }
446
- }
447
-
448
- private async deleteFromDisk(key: string): Promise<void> {
449
- const meta = this.diskIndex.get(key);
450
- if (!meta) return;
451
-
452
- this.removeDiskMeta(key);
453
-
454
- try {
455
- const filePath = join(this.entriesDir, meta.file);
456
- if (existsSync(filePath)) {
457
- await unlink(filePath);
458
- }
459
- } catch {
460
- // File already gone or inaccessible — ok
461
- }
462
-
463
- this.indexDirty = true;
464
- void this.persistIndex();
465
- }
466
-
467
- private removeDiskMeta(key: string): void {
468
- const meta = this.diskIndex.get(key);
469
- if (!meta) return;
470
-
471
- // Remove from disk tag index
472
- if (meta.tags) {
473
- for (const tag of meta.tags) {
474
- const keys = this.diskTagIndex.get(tag);
475
- if (keys) {
476
- keys.delete(key);
477
- if (keys.size === 0) {
478
- this.diskTagIndex.delete(tag);
479
- }
480
- }
481
- }
482
- }
483
-
484
- this.diskIndex.delete(key);
485
- this.indexDirty = true;
486
- }
487
-
488
- private async sweepDisk(): Promise<void> {
489
- const now = Date.now();
490
- for (const [key, meta] of [...this.diskIndex]) {
491
- if (meta.expiresAt !== undefined && meta.expiresAt < now) {
492
- await this.deleteFromDisk(key);
493
- }
494
- }
495
- }
496
-
497
- private async persistIndex(): Promise<void> {
498
- if (this.readOnly || !this.indexDirty) return;
499
- this.indexDirty = false;
500
-
501
- const doWrite = async (): Promise<void> => {
502
- try {
503
- await mkdir(this.cacheDir, { recursive: true });
504
-
505
- const data: DiskIndex = {
506
- entries: Object.fromEntries(this.diskIndex),
507
- tagTimestamps: [...this.tagTimestamps],
508
- };
509
-
510
- const tmpPath = this.indexPath + '.tmp';
511
- await writeFile(tmpPath, JSON.stringify(data), 'utf-8');
512
- await rename(tmpPath, this.indexPath);
513
- } catch {
514
- // Index write failed — will retry on next change
515
- this.indexDirty = true;
516
- }
517
- };
518
-
519
- // Chain onto any in-flight write so they don't overlap
520
- this.persistPromise = (this.persistPromise ?? Promise.resolve()).then(doWrite);
521
- await this.persistPromise;
522
-
523
- // If new changes arrived during the write, persist again
524
- if (this.indexDirty) {
525
- void this.persistIndex();
526
- }
527
- }
528
- }
@@ -1,89 +0,0 @@
1
- import { existsSync } from 'node:fs';
2
- import { mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
3
- import { dirname, join } from 'node:path';
4
- import type { CacheSnapshot, SerializedCacheEntry } from './types.js';
5
- import { CACHE_DIR } from './constants.js';
6
- import { deserialize, serialize, type Serialized } from './serialize.js';
7
-
8
- /** On-disk format — values are encoded as { data, encoding } */
9
- interface DiskEntry {
10
- expiresAt?: number;
11
- tags?: string[];
12
- value: Serialized;
13
- }
14
-
15
- interface DiskSnapshot {
16
- entries: Array<[string, DiskEntry]>;
17
- tagTimestamps: Array<[string, number]>;
18
- }
19
-
20
- export class CachePersistence {
21
- private filePath: string;
22
-
23
- constructor(processName: string) {
24
- this.filePath = join(CACHE_DIR, `${processName}.json`);
25
- }
26
-
27
- async save(snapshot: CacheSnapshot): Promise<void> {
28
- const dir = dirname(this.filePath);
29
- if (!existsSync(dir)) {
30
- await mkdir(dir, { recursive: true });
31
- }
32
-
33
- const diskEntries: Array<[string, DiskEntry]> = snapshot.entries.map(([key, entry]) => {
34
- const disk: DiskEntry = {
35
- value: serialize(entry.value),
36
- expiresAt: entry.expiresAt,
37
- };
38
- if (entry.tags && entry.tags.length > 0) {
39
- disk.tags = entry.tags;
40
- }
41
- return [key, disk];
42
- });
43
-
44
- const diskData: DiskSnapshot = { entries: diskEntries, tagTimestamps: snapshot.tagTimestamps };
45
-
46
- // Atomic write: temp file → rename
47
- const tmpPath = this.filePath + '.tmp';
48
- await writeFile(tmpPath, JSON.stringify(diskData), 'utf-8');
49
- await rename(tmpPath, this.filePath);
50
- }
51
-
52
- async load(): Promise<CacheSnapshot> {
53
- if (!existsSync(this.filePath)) {
54
- return { entries: [], tagTimestamps: [] };
55
- }
56
-
57
- try {
58
- const content = await readFile(this.filePath, 'utf-8');
59
- const raw: DiskSnapshot = JSON.parse(content);
60
- const diskEntries = raw.entries;
61
- const tagTimestamps = raw.tagTimestamps;
62
-
63
- const now = Date.now();
64
- const entries: Array<[string, SerializedCacheEntry]> = diskEntries
65
- .filter(([, entry]) => entry.expiresAt === undefined || entry.expiresAt > now)
66
- .map(([key, entry]) => {
67
- const result: SerializedCacheEntry = {
68
- value: deserialize(entry.value),
69
- expiresAt: entry.expiresAt,
70
- };
71
- if (entry.tags) {
72
- result.tags = entry.tags;
73
- }
74
- return [key, result] as [string, SerializedCacheEntry];
75
- });
76
-
77
- return { entries, tagTimestamps };
78
- } catch (err) {
79
- console.warn(`[orkify:cache] Failed to load cache from ${this.filePath}:`, err);
80
- return { entries: [], tagTimestamps: [] };
81
- }
82
- }
83
-
84
- async clear(): Promise<void> {
85
- if (existsSync(this.filePath)) {
86
- await unlink(this.filePath);
87
- }
88
- }
89
- }