pdf-oxide 0.3.24

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 (62) hide show
  1. package/README.md +218 -0
  2. package/binding.gyp +35 -0
  3. package/package.json +78 -0
  4. package/src/builders/annotation-builder.ts +367 -0
  5. package/src/builders/conversion-options-builder.ts +257 -0
  6. package/src/builders/index.ts +12 -0
  7. package/src/builders/metadata-builder.ts +317 -0
  8. package/src/builders/pdf-builder.ts +386 -0
  9. package/src/builders/search-options-builder.ts +151 -0
  10. package/src/document-editor-manager.ts +318 -0
  11. package/src/errors.ts +1629 -0
  12. package/src/form-field-manager.ts +666 -0
  13. package/src/hybrid-ml-manager.ts +283 -0
  14. package/src/index.ts +453 -0
  15. package/src/managers/accessibility-manager.ts +338 -0
  16. package/src/managers/annotation-manager.ts +439 -0
  17. package/src/managers/barcode-manager.ts +235 -0
  18. package/src/managers/batch-manager.ts +533 -0
  19. package/src/managers/cache-manager.ts +486 -0
  20. package/src/managers/compliance-manager.ts +375 -0
  21. package/src/managers/content-manager.ts +339 -0
  22. package/src/managers/document-utility-manager.ts +922 -0
  23. package/src/managers/dom-pdf-creator.ts +365 -0
  24. package/src/managers/editing-manager.ts +514 -0
  25. package/src/managers/enterprise-manager.ts +478 -0
  26. package/src/managers/extended-managers.ts +437 -0
  27. package/src/managers/extraction-manager.ts +583 -0
  28. package/src/managers/final-utilities.ts +429 -0
  29. package/src/managers/hybrid-ml-advanced.ts +479 -0
  30. package/src/managers/index.ts +239 -0
  31. package/src/managers/layer-manager.ts +500 -0
  32. package/src/managers/metadata-manager.ts +303 -0
  33. package/src/managers/ocr-manager.ts +756 -0
  34. package/src/managers/optimization-manager.ts +262 -0
  35. package/src/managers/outline-manager.ts +196 -0
  36. package/src/managers/page-manager.ts +289 -0
  37. package/src/managers/pattern-detection.ts +440 -0
  38. package/src/managers/rendering-manager.ts +863 -0
  39. package/src/managers/search-manager.ts +385 -0
  40. package/src/managers/security-manager.ts +345 -0
  41. package/src/managers/signature-manager.ts +1664 -0
  42. package/src/managers/streams.ts +618 -0
  43. package/src/managers/xfa-manager.ts +500 -0
  44. package/src/pdf-creator-manager.ts +494 -0
  45. package/src/properties.ts +522 -0
  46. package/src/result-accessors-manager.ts +867 -0
  47. package/src/tests/advanced-features.test.ts +414 -0
  48. package/src/tests/advanced.test.ts +266 -0
  49. package/src/tests/extended-managers.test.ts +316 -0
  50. package/src/tests/final-utilities.test.ts +455 -0
  51. package/src/tests/foundation.test.ts +315 -0
  52. package/src/tests/high-demand.test.ts +257 -0
  53. package/src/tests/specialized.test.ts +97 -0
  54. package/src/thumbnail-manager.ts +272 -0
  55. package/src/types/common.ts +142 -0
  56. package/src/types/document-types.ts +457 -0
  57. package/src/types/index.ts +6 -0
  58. package/src/types/manager-types.ts +284 -0
  59. package/src/types/native-bindings.ts +517 -0
  60. package/src/workers/index.ts +7 -0
  61. package/src/workers/pool.ts +274 -0
  62. package/src/workers/worker.ts +131 -0
@@ -0,0 +1,486 @@
1
+ /**
2
+ * Cache Manager - TypeScript/Node.js Implementation
3
+ *
4
+ * Provides caching functionality for PDF operations:
5
+ * - Document-level caching
6
+ * - Page-level caching
7
+ * - Result caching with TTL
8
+ * - LRU eviction
9
+ * - Cache statistics
10
+ *
11
+ * This completes the cache coverage for 100% FFI parity.
12
+ */
13
+
14
+ import { EventEmitter } from 'events';
15
+
16
+ // ============================================================================
17
+ // Type Definitions
18
+ // ============================================================================
19
+
20
+ /**
21
+ * Cache entry with metadata
22
+ */
23
+ export interface CacheEntry<T = unknown> {
24
+ readonly key: string;
25
+ readonly value: T;
26
+ readonly timestamp: number;
27
+ readonly ttl?: number;
28
+ readonly size?: number;
29
+ readonly hits: number;
30
+ }
31
+
32
+ /**
33
+ * Cache options
34
+ */
35
+ export interface CacheOptions {
36
+ readonly maxSize?: number;
37
+ readonly maxEntries?: number;
38
+ readonly defaultTtl?: number;
39
+ readonly enableStatistics?: boolean;
40
+ readonly evictionPolicy?: 'lru' | 'lfu' | 'fifo';
41
+ }
42
+
43
+ /**
44
+ * Cache statistics
45
+ */
46
+ export interface CacheStatistics {
47
+ readonly totalEntries: number;
48
+ readonly totalSize: number;
49
+ readonly hitCount: number;
50
+ readonly missCount: number;
51
+ readonly hitRate: number;
52
+ readonly evictionCount: number;
53
+ readonly oldestEntry?: number;
54
+ readonly newestEntry?: number;
55
+ }
56
+
57
+ /**
58
+ * Cache scope enumeration
59
+ */
60
+ export enum CacheScope {
61
+ /** Cache per document */
62
+ DOCUMENT = 'document',
63
+ /** Cache per page */
64
+ PAGE = 'page',
65
+ /** Global cache */
66
+ GLOBAL = 'global',
67
+ /** Session-level cache */
68
+ SESSION = 'session',
69
+ }
70
+
71
+ /**
72
+ * Cache event data
73
+ */
74
+ export interface CacheEventData {
75
+ readonly key: string;
76
+ readonly scope?: CacheScope;
77
+ readonly operation: 'set' | 'get' | 'delete' | 'evict' | 'clear';
78
+ readonly hit?: boolean;
79
+ }
80
+
81
+ // ============================================================================
82
+ // Cache Manager Implementation
83
+ // ============================================================================
84
+
85
+ /**
86
+ * Cache Manager - Complete caching capabilities
87
+ *
88
+ * Provides 10 functions for cache management:
89
+ * 1. set - Store a value
90
+ * 2. get - Retrieve a value
91
+ * 3. has - Check if key exists
92
+ * 4. delete - Remove a key
93
+ * 5. clear - Clear all entries
94
+ * 6. clearScope - Clear entries by scope
95
+ * 7. getStatistics - Get cache statistics
96
+ * 8. setTtl - Set TTL for an entry
97
+ * 9. getKeys - Get all keys
98
+ * 10. prune - Remove expired entries
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * const cache = new CacheManager({
103
+ * maxEntries: 1000,
104
+ * defaultTtl: 60000, // 1 minute
105
+ * evictionPolicy: 'lru',
106
+ * });
107
+ *
108
+ * // Store a value
109
+ * cache.set('page:0:text', extractedText, CacheScope.PAGE);
110
+ *
111
+ * // Retrieve a value
112
+ * const text = cache.get<string>('page:0:text');
113
+ *
114
+ * // Check statistics
115
+ * const stats = cache.getStatistics();
116
+ * console.log(`Hit rate: ${stats.hitRate * 100}%`);
117
+ * ```
118
+ */
119
+ export class CacheManager extends EventEmitter {
120
+ private readonly cache: Map<string, CacheEntry> = new Map();
121
+ private readonly scopeIndex: Map<CacheScope, Set<string>> = new Map();
122
+ private readonly options: Required<CacheOptions>;
123
+
124
+ // Statistics
125
+ private hitCount = 0;
126
+ private missCount = 0;
127
+ private evictionCount = 0;
128
+
129
+ constructor(options: CacheOptions = {}) {
130
+ super();
131
+ this.options = {
132
+ maxSize: options.maxSize ?? 100 * 1024 * 1024, // 100MB default
133
+ maxEntries: options.maxEntries ?? 10000,
134
+ defaultTtl: options.defaultTtl ?? 0, // 0 = no expiry
135
+ enableStatistics: options.enableStatistics ?? true,
136
+ evictionPolicy: options.evictionPolicy ?? 'lru',
137
+ };
138
+
139
+ // Initialize scope index
140
+ for (const scope of Object.values(CacheScope)) {
141
+ this.scopeIndex.set(scope, new Set());
142
+ }
143
+ }
144
+
145
+ // ==========================================================================
146
+ // Core Cache Operations (6 functions)
147
+ // ==========================================================================
148
+
149
+ /**
150
+ * Store a value in the cache
151
+ */
152
+ set<T>(key: string, value: T, scope: CacheScope = CacheScope.GLOBAL, ttl?: number): void {
153
+ // Check if we need to evict
154
+ this.ensureCapacity();
155
+
156
+ const entry: CacheEntry<T> = {
157
+ key,
158
+ value,
159
+ timestamp: Date.now(),
160
+ ttl: ttl ?? this.options.defaultTtl,
161
+ size: this.estimateSize(value),
162
+ hits: 0,
163
+ };
164
+
165
+ // Remove from old scope if exists
166
+ if (this.cache.has(key)) {
167
+ this.removeFromScopeIndex(key);
168
+ }
169
+
170
+ this.cache.set(key, entry as CacheEntry);
171
+ this.scopeIndex.get(scope)?.add(key);
172
+
173
+ this.emit('cache-set', {
174
+ key,
175
+ scope,
176
+ operation: 'set',
177
+ } as CacheEventData);
178
+ }
179
+
180
+ /**
181
+ * Retrieve a value from the cache
182
+ */
183
+ get<T>(key: string): T | undefined {
184
+ const entry = this.cache.get(key) as CacheEntry<T> | undefined;
185
+
186
+ if (!entry) {
187
+ if (this.options.enableStatistics) {
188
+ this.missCount++;
189
+ }
190
+ this.emit('cache-miss', { key, operation: 'get', hit: false } as CacheEventData);
191
+ return undefined;
192
+ }
193
+
194
+ // Check TTL
195
+ if (entry.ttl && entry.ttl > 0 && Date.now() - entry.timestamp > entry.ttl) {
196
+ this.delete(key);
197
+ if (this.options.enableStatistics) {
198
+ this.missCount++;
199
+ }
200
+ this.emit('cache-expired', { key, operation: 'get', hit: false } as CacheEventData);
201
+ return undefined;
202
+ }
203
+
204
+ // Update hit count for LFU
205
+ const updatedEntry: CacheEntry<T> = {
206
+ ...entry,
207
+ hits: entry.hits + 1,
208
+ timestamp: this.options.evictionPolicy === 'lru' ? Date.now() : entry.timestamp,
209
+ };
210
+ this.cache.set(key, updatedEntry as CacheEntry);
211
+
212
+ if (this.options.enableStatistics) {
213
+ this.hitCount++;
214
+ }
215
+
216
+ this.emit('cache-hit', { key, operation: 'get', hit: true } as CacheEventData);
217
+ return entry.value;
218
+ }
219
+
220
+ /**
221
+ * Check if a key exists in the cache
222
+ */
223
+ has(key: string): boolean {
224
+ const entry = this.cache.get(key);
225
+ if (!entry) return false;
226
+
227
+ // Check TTL
228
+ if (entry.ttl && entry.ttl > 0 && Date.now() - entry.timestamp > entry.ttl) {
229
+ this.delete(key);
230
+ return false;
231
+ }
232
+
233
+ return true;
234
+ }
235
+
236
+ /**
237
+ * Remove a key from the cache
238
+ */
239
+ delete(key: string): boolean {
240
+ const exists = this.cache.has(key);
241
+ if (exists) {
242
+ this.removeFromScopeIndex(key);
243
+ this.cache.delete(key);
244
+ this.emit('cache-delete', { key, operation: 'delete' } as CacheEventData);
245
+ }
246
+ return exists;
247
+ }
248
+
249
+ /**
250
+ * Clear all entries from the cache
251
+ */
252
+ clear(): void {
253
+ const count = this.cache.size;
254
+ this.cache.clear();
255
+ for (const scope of this.scopeIndex.values()) {
256
+ scope.clear();
257
+ }
258
+ this.hitCount = 0;
259
+ this.missCount = 0;
260
+ this.evictionCount = 0;
261
+ this.emit('cache-clear', { key: '*', operation: 'clear' } as CacheEventData);
262
+ }
263
+
264
+ /**
265
+ * Clear entries by scope
266
+ */
267
+ clearScope(scope: CacheScope): number {
268
+ const keys = this.scopeIndex.get(scope);
269
+ if (!keys) return 0;
270
+
271
+ const count = keys.size;
272
+ for (const key of keys) {
273
+ this.cache.delete(key);
274
+ }
275
+ keys.clear();
276
+
277
+ this.emit('cache-scope-clear', { key: scope, scope, operation: 'clear' } as CacheEventData);
278
+ return count;
279
+ }
280
+
281
+ // ==========================================================================
282
+ // Statistics and Maintenance (4 functions)
283
+ // ==========================================================================
284
+
285
+ /**
286
+ * Get cache statistics
287
+ */
288
+ getStatistics(): CacheStatistics {
289
+ const entries = Array.from(this.cache.values());
290
+ const totalSize = entries.reduce((sum, e) => sum + (e.size ?? 0), 0);
291
+ const timestamps = entries.map((e) => e.timestamp);
292
+
293
+ return {
294
+ totalEntries: this.cache.size,
295
+ totalSize,
296
+ hitCount: this.hitCount,
297
+ missCount: this.missCount,
298
+ hitRate: this.hitCount + this.missCount > 0
299
+ ? this.hitCount / (this.hitCount + this.missCount)
300
+ : 0,
301
+ evictionCount: this.evictionCount,
302
+ oldestEntry: timestamps.length > 0 ? Math.min(...timestamps) : undefined,
303
+ newestEntry: timestamps.length > 0 ? Math.max(...timestamps) : undefined,
304
+ };
305
+ }
306
+
307
+ /**
308
+ * Set TTL for an existing entry
309
+ */
310
+ setTtl(key: string, ttl: number): boolean {
311
+ const entry = this.cache.get(key);
312
+ if (!entry) return false;
313
+
314
+ const updatedEntry: CacheEntry = {
315
+ ...entry,
316
+ ttl,
317
+ };
318
+ this.cache.set(key, updatedEntry);
319
+ return true;
320
+ }
321
+
322
+ /**
323
+ * Get all cache keys
324
+ */
325
+ getKeys(scope?: CacheScope): string[] {
326
+ if (scope) {
327
+ return Array.from(this.scopeIndex.get(scope) ?? []);
328
+ }
329
+ return Array.from(this.cache.keys());
330
+ }
331
+
332
+ /**
333
+ * Remove expired entries
334
+ */
335
+ prune(): number {
336
+ const now = Date.now();
337
+ let pruned = 0;
338
+
339
+ for (const [key, entry] of this.cache.entries()) {
340
+ if (entry.ttl && entry.ttl > 0 && now - entry.timestamp > entry.ttl) {
341
+ this.delete(key);
342
+ pruned++;
343
+ }
344
+ }
345
+
346
+ if (pruned > 0) {
347
+ this.emit('cache-prune', { key: '*', operation: 'delete' } as CacheEventData);
348
+ }
349
+
350
+ return pruned;
351
+ }
352
+
353
+ // ==========================================================================
354
+ // Helper Methods
355
+ // ==========================================================================
356
+
357
+ /**
358
+ * Ensure cache has capacity, evicting if needed
359
+ */
360
+ private ensureCapacity(): void {
361
+ // Check entry count
362
+ while (this.cache.size >= this.options.maxEntries) {
363
+ this.evictOne();
364
+ }
365
+
366
+ // Check total size
367
+ let totalSize = 0;
368
+ for (const entry of this.cache.values()) {
369
+ totalSize += entry.size ?? 0;
370
+ }
371
+
372
+ while (totalSize > this.options.maxSize && this.cache.size > 0) {
373
+ const evicted = this.evictOne();
374
+ totalSize -= evicted?.size ?? 0;
375
+ }
376
+ }
377
+
378
+ /**
379
+ * Evict one entry based on eviction policy
380
+ */
381
+ private evictOne(): CacheEntry | undefined {
382
+ if (this.cache.size === 0) return undefined;
383
+
384
+ let keyToEvict: string | undefined;
385
+
386
+ switch (this.options.evictionPolicy) {
387
+ case 'lru':
388
+ // Find oldest timestamp
389
+ keyToEvict = this.findOldestKey();
390
+ break;
391
+
392
+ case 'lfu':
393
+ // Find least frequently used
394
+ keyToEvict = this.findLeastUsedKey();
395
+ break;
396
+
397
+ case 'fifo':
398
+ default:
399
+ // First key
400
+ keyToEvict = this.cache.keys().next().value;
401
+ break;
402
+ }
403
+
404
+ if (keyToEvict) {
405
+ const entry = this.cache.get(keyToEvict);
406
+ this.removeFromScopeIndex(keyToEvict);
407
+ this.cache.delete(keyToEvict);
408
+ this.evictionCount++;
409
+ this.emit('cache-evict', { key: keyToEvict, operation: 'evict' } as CacheEventData);
410
+ return entry;
411
+ }
412
+
413
+ return undefined;
414
+ }
415
+
416
+ /**
417
+ * Find key with oldest timestamp (LRU)
418
+ */
419
+ private findOldestKey(): string | undefined {
420
+ let oldestKey: string | undefined;
421
+ let oldestTime = Infinity;
422
+
423
+ for (const [key, entry] of this.cache.entries()) {
424
+ if (entry.timestamp < oldestTime) {
425
+ oldestTime = entry.timestamp;
426
+ oldestKey = key;
427
+ }
428
+ }
429
+
430
+ return oldestKey;
431
+ }
432
+
433
+ /**
434
+ * Find key with least hits (LFU)
435
+ */
436
+ private findLeastUsedKey(): string | undefined {
437
+ let leastUsedKey: string | undefined;
438
+ let leastHits = Infinity;
439
+
440
+ for (const [key, entry] of this.cache.entries()) {
441
+ if (entry.hits < leastHits) {
442
+ leastHits = entry.hits;
443
+ leastUsedKey = key;
444
+ }
445
+ }
446
+
447
+ return leastUsedKey;
448
+ }
449
+
450
+ /**
451
+ * Remove key from scope index
452
+ */
453
+ private removeFromScopeIndex(key: string): void {
454
+ for (const keys of this.scopeIndex.values()) {
455
+ keys.delete(key);
456
+ }
457
+ }
458
+
459
+ /**
460
+ * Estimate size of a value in bytes
461
+ */
462
+ private estimateSize(value: unknown): number {
463
+ if (value === null || value === undefined) return 0;
464
+ if (typeof value === 'string') return value.length * 2;
465
+ if (typeof value === 'number') return 8;
466
+ if (typeof value === 'boolean') return 4;
467
+ if (Buffer.isBuffer(value)) return value.length;
468
+ if (Array.isArray(value)) {
469
+ return value.reduce((sum, v) => sum + this.estimateSize(v), 0);
470
+ }
471
+ if (typeof value === 'object') {
472
+ return JSON.stringify(value).length * 2;
473
+ }
474
+ return 0;
475
+ }
476
+
477
+ /**
478
+ * Cleanup resources
479
+ */
480
+ destroy(): void {
481
+ this.clear();
482
+ this.removeAllListeners();
483
+ }
484
+ }
485
+
486
+ export default CacheManager;