@ebowwa/bun-native-page 0.2.0

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.
package/dist/index.ts ADDED
@@ -0,0 +1,433 @@
1
+ /**
2
+ * @ebowwa/bun-native-page
3
+ *
4
+ * Cross-platform page-size aware memory operations for Bun
5
+ */
6
+
7
+ export * from './errors.js';
8
+ export * from './oom.js';
9
+ export * from './allocator.js';
10
+
11
+ import { ffi, ptr, toArrayBuffer } from './ffi.js';
12
+ import { PageSizeError, MapError, ProtectionError, LockError } from './errors.js';
13
+ import { handleOOM, OOMOptions } from './oom.js';
14
+
15
+ // ============================================================================
16
+ // Types
17
+ // ============================================================================
18
+
19
+ export interface PageSizeInfo {
20
+ /** System page size in bytes */
21
+ readonly size: number;
22
+ /** true if page size is 4096 bytes */
23
+ readonly is4k: boolean;
24
+ /** true if page size is 16384 bytes */
25
+ readonly is16k: boolean;
26
+ /** true if page size is 65536 bytes */
27
+ readonly is64k: boolean;
28
+ /** Huge page size in bytes (Linux only, 0 if unavailable) */
29
+ readonly hugePageSize: number;
30
+ /** Platform identifier */
31
+ readonly platform: PlatformName;
32
+ }
33
+
34
+ export type PlatformName =
35
+ | 'darwin-arm64'
36
+ | 'darwin-x64'
37
+ | 'linux-x64'
38
+ | 'linux-x86'
39
+ | 'linux-arm64'
40
+ | 'linux-arm'
41
+ | 'windows-x64'
42
+ | 'windows-x86'
43
+ | 'windows-arm64'
44
+ | 'unknown';
45
+
46
+ export type MemoryProtection =
47
+ | 'none'
48
+ | 'read'
49
+ | 'write'
50
+ | 'exec'
51
+ | 'read-write'
52
+ | 'read-exec'
53
+ | 'write-exec'
54
+ | 'all';
55
+
56
+ export type MemoryAdvice =
57
+ | 'normal'
58
+ | 'random'
59
+ | 'sequential'
60
+ | 'willneed'
61
+ | 'dontneed'
62
+ | 'free'
63
+ | 'hugepage'
64
+ | 'nohugepage';
65
+
66
+ export interface AllocatedBuffer {
67
+ /** Pointer to allocated memory */
68
+ readonly ptr: bigint;
69
+ /** Requested size */
70
+ readonly size: number;
71
+ /** Actual allocated size (may be larger due to alignment) */
72
+ readonly allocatedSize: number;
73
+ /** ArrayBuffer view of the memory */
74
+ readonly buffer: ArrayBuffer;
75
+ /** Free the allocation */
76
+ free(): void;
77
+ }
78
+
79
+ export interface MappedRegion {
80
+ /** Pointer to mapped memory */
81
+ readonly ptr: bigint;
82
+ /** Mapped size */
83
+ readonly size: number;
84
+ /** ArrayBuffer view */
85
+ readonly buffer: ArrayBuffer;
86
+ /** Unmap the region */
87
+ unmap(): void;
88
+ /** Change memory protection */
89
+ protect(prot: MemoryProtection): void;
90
+ /** Advise kernel about usage pattern */
91
+ advise(advice: MemoryAdvice): void;
92
+ /** Lock pages in memory (prevent swap) */
93
+ lock(): void;
94
+ /** Unlock pages */
95
+ unlock(): void;
96
+ }
97
+
98
+ // ============================================================================
99
+ // Constants
100
+ // ============================================================================
101
+
102
+ const PROTECTION_MAP: Record<MemoryProtection, number> = {
103
+ 'none': 0,
104
+ 'read': 1,
105
+ 'write': 2,
106
+ 'exec': 4,
107
+ 'read-write': 3,
108
+ 'read-exec': 5,
109
+ 'write-exec': 6,
110
+ 'all': 7,
111
+ };
112
+
113
+ const ADVICE_MAP: Record<MemoryAdvice, number> = {
114
+ 'normal': 0,
115
+ 'random': 1,
116
+ 'sequential': 2,
117
+ 'willneed': 3,
118
+ 'dontneed': 4,
119
+ 'free': 5,
120
+ 'hugepage': 6,
121
+ 'nohugepage': 7,
122
+ };
123
+
124
+ const PLATFORM_NAMES: Record<number, PlatformName> = {
125
+ 0: 'darwin-arm64',
126
+ 1: 'darwin-x64',
127
+ 2: 'linux-x64',
128
+ 3: 'linux-x86',
129
+ 4: 'linux-arm64',
130
+ 5: 'linux-arm',
131
+ 6: 'windows-x64',
132
+ 7: 'windows-x86',
133
+ 8: 'windows-arm64',
134
+ 99: 'unknown',
135
+ };
136
+
137
+ // Cached page size info
138
+ let cachedPageSizeInfo: PageSizeInfo | null = null;
139
+
140
+ // ============================================================================
141
+ // Page Size API
142
+ // ============================================================================
143
+
144
+ /**
145
+ * Get system page size information
146
+ */
147
+ export function getPageSize(): PageSizeInfo {
148
+ if (cachedPageSizeInfo) {
149
+ return cachedPageSizeInfo;
150
+ }
151
+
152
+ const size = Number(ffi.getSize());
153
+ const hugePageSize = Number(ffi.getHugePageSize());
154
+ const platformId = Number(ffi.getPlatform());
155
+
156
+ cachedPageSizeInfo = {
157
+ size,
158
+ is4k: size === 4096,
159
+ is16k: size === 16384,
160
+ is64k: size === 65536,
161
+ hugePageSize,
162
+ platform: PLATFORM_NAMES[platformId] ?? 'unknown',
163
+ };
164
+
165
+ return cachedPageSizeInfo;
166
+ }
167
+
168
+ /**
169
+ * Round size up to nearest page boundary
170
+ */
171
+ export function alignToPage(size: number): number {
172
+ return Number(ffi.alignSize(BigInt(size)));
173
+ }
174
+
175
+ /**
176
+ * Round size down to nearest page boundary
177
+ */
178
+ export function truncateToPage(size: number): number {
179
+ return Number(ffi.truncateSize(BigInt(size)));
180
+ }
181
+
182
+ /**
183
+ * Check if a pointer is page-aligned
184
+ */
185
+ export function isPageAligned(ptr: bigint | number): boolean {
186
+ const result = ffi.isAligned(BigInt(ptr));
187
+ return result !== 0;
188
+ }
189
+
190
+ // ============================================================================
191
+ // Allocation API
192
+ // ============================================================================
193
+
194
+ /**
195
+ * Allocate page-aligned memory
196
+ */
197
+ export function allocPageAligned(
198
+ size: number,
199
+ options?: OOMOptions
200
+ ): AllocatedBuffer | null {
201
+ if (size <= 0) {
202
+ throw new PageSizeError('Size must be positive');
203
+ }
204
+
205
+ if (size > Number.MAX_SAFE_INTEGER) {
206
+ throw new PageSizeError('Size exceeds safe integer range');
207
+ }
208
+
209
+ const allocHandle = ffi.alloc(BigInt(size));
210
+
211
+ if (allocHandle === null || allocHandle === 0n) {
212
+ return handleOOM(size, options) as never;
213
+ }
214
+
215
+ const actualPtr = ffi.allocPtr(allocHandle);
216
+ const actualSize = Number(ffi.allocSize(allocHandle));
217
+
218
+ if (actualPtr === null || actualPtr === 0n) {
219
+ ffi.free(allocHandle);
220
+ return handleOOM(size, options) as never;
221
+ }
222
+
223
+ let freed = false;
224
+
225
+ return {
226
+ ptr: BigInt(actualPtr as unknown as number),
227
+ size,
228
+ allocatedSize: actualSize,
229
+ get buffer() {
230
+ if (freed) {
231
+ throw new PageSizeError('Cannot access freed buffer');
232
+ }
233
+ return toArrayBuffer(actualPtr as unknown as number, 0, actualSize);
234
+ },
235
+ free() {
236
+ if (!freed) {
237
+ freed = true;
238
+ ffi.free(allocHandle);
239
+ }
240
+ },
241
+ };
242
+ }
243
+
244
+ // ============================================================================
245
+ // mmap API
246
+ // ============================================================================
247
+
248
+ /**
249
+ * Map anonymous memory (no file backing)
250
+ */
251
+ export function mmapAnonymous(
252
+ size: number,
253
+ prot: MemoryProtection = 'read-write'
254
+ ): MappedRegion {
255
+ if (size <= 0) {
256
+ throw new PageSizeError('Size must be positive');
257
+ }
258
+
259
+ const protValue = PROTECTION_MAP[prot];
260
+ const regionHandle = ffi.mmapAnonymous(BigInt(size), protValue);
261
+
262
+ if (regionHandle === null || regionHandle === 0n) {
263
+ throw new MapError(size, 'mmap failed');
264
+ }
265
+
266
+ const actualPtr = ffi.mmapPtr(regionHandle);
267
+ const actualSize = Number(ffi.mmapSize(regionHandle));
268
+
269
+ if (actualPtr === null || actualPtr === 0n) {
270
+ throw new MapError(size, 'mmap returned null pointer');
271
+ }
272
+
273
+ let unmapped = false;
274
+
275
+ return {
276
+ ptr: BigInt(actualPtr as unknown as number),
277
+ size: actualSize,
278
+ get buffer() {
279
+ if (unmapped) {
280
+ throw new PageSizeError('Cannot access unmapped region');
281
+ }
282
+ return toArrayBuffer(actualPtr as unknown as number, 0, actualSize);
283
+ },
284
+ unmap() {
285
+ if (!unmapped) {
286
+ unmapped = true;
287
+ const result = ffi.munmap(regionHandle);
288
+ if (result !== 0) {
289
+ throw new PageSizeError('munmap failed');
290
+ }
291
+ }
292
+ },
293
+ protect(prot: MemoryProtection) {
294
+ const protValue = PROTECTION_MAP[prot];
295
+ const result = ffi.mprotect(actualPtr as unknown as number, BigInt(actualSize), protValue);
296
+ if (result !== 0) {
297
+ throw new ProtectionError(`mprotect failed with code ${result}`);
298
+ }
299
+ },
300
+ advise(advice: MemoryAdvice) {
301
+ const adviceValue = ADVICE_MAP[advice];
302
+ ffi.madvise(actualPtr as unknown as number, BigInt(actualSize), adviceValue);
303
+ },
304
+ lock() {
305
+ const result = ffi.mlock(actualPtr as unknown as number, BigInt(actualSize));
306
+ if (result !== 0) {
307
+ throw new LockError(`mlock failed with code ${result}`);
308
+ }
309
+ },
310
+ unlock() {
311
+ const result = ffi.munlock(actualPtr as unknown as number, BigInt(actualSize));
312
+ if (result !== 0) {
313
+ throw new LockError(`munlock failed with code ${result}`);
314
+ }
315
+ },
316
+ };
317
+ }
318
+
319
+ /**
320
+ * Map a file into memory
321
+ */
322
+ export function mmapFile(
323
+ fd: number,
324
+ size: number,
325
+ offset: number = 0,
326
+ prot: MemoryProtection = 'read-write'
327
+ ): MappedRegion {
328
+ if (size <= 0) {
329
+ throw new PageSizeError('Size must be positive');
330
+ }
331
+
332
+ const protValue = PROTECTION_MAP[prot];
333
+ const regionHandle = ffi.mmapFile(fd, BigInt(size), BigInt(offset), protValue);
334
+
335
+ if (regionHandle === null || regionHandle === 0n) {
336
+ throw new MapError(size, 'mmap file failed');
337
+ }
338
+
339
+ const actualPtr = ffi.mmapPtr(regionHandle);
340
+ const actualSize = Number(ffi.mmapSize(regionHandle));
341
+
342
+ if (actualPtr === null || actualPtr === 0n) {
343
+ throw new MapError(size, 'mmap file returned null pointer');
344
+ }
345
+
346
+ let unmapped = false;
347
+
348
+ return {
349
+ ptr: BigInt(actualPtr as unknown as number),
350
+ size: actualSize,
351
+ get buffer() {
352
+ if (unmapped) {
353
+ throw new PageSizeError('Cannot access unmapped region');
354
+ }
355
+ return toArrayBuffer(actualPtr as unknown as number, 0, actualSize);
356
+ },
357
+ unmap() {
358
+ if (!unmapped) {
359
+ unmapped = true;
360
+ ffi.munmap(regionHandle);
361
+ }
362
+ },
363
+ protect(prot: MemoryProtection) {
364
+ const protValue = PROTECTION_MAP[prot];
365
+ ffi.mprotect(actualPtr as unknown as number, BigInt(actualSize), protValue);
366
+ },
367
+ advise(advice: MemoryAdvice) {
368
+ const adviceValue = ADVICE_MAP[advice];
369
+ ffi.madvise(actualPtr as unknown as number, BigInt(actualSize), adviceValue);
370
+ },
371
+ lock() {
372
+ ffi.mlock(actualPtr as unknown as number, BigInt(actualSize));
373
+ },
374
+ unlock() {
375
+ ffi.munlock(actualPtr as unknown as number, BigInt(actualSize));
376
+ },
377
+ };
378
+ }
379
+
380
+ /**
381
+ * Map huge pages (Linux only, falls back to regular pages on other platforms)
382
+ */
383
+ export function mmapHuge(size: number): MappedRegion | null {
384
+ if (size <= 0) {
385
+ throw new PageSizeError('Size must be positive');
386
+ }
387
+
388
+ const regionHandle = ffi.mmapHuge(BigInt(size));
389
+
390
+ if (regionHandle === null || regionHandle === 0n) {
391
+ return null;
392
+ }
393
+
394
+ const actualPtr = ffi.mmapPtr(regionHandle);
395
+ const actualSize = Number(ffi.mmapSize(regionHandle));
396
+
397
+ if (actualPtr === null || actualPtr === 0n) {
398
+ return null;
399
+ }
400
+
401
+ let unmapped = false;
402
+
403
+ return {
404
+ ptr: BigInt(actualPtr as unknown as number),
405
+ size: actualSize,
406
+ get buffer() {
407
+ if (unmapped) {
408
+ throw new PageSizeError('Cannot access unmapped region');
409
+ }
410
+ return toArrayBuffer(actualPtr as unknown as number, 0, actualSize);
411
+ },
412
+ unmap() {
413
+ if (!unmapped) {
414
+ unmapped = true;
415
+ ffi.munmap(regionHandle);
416
+ }
417
+ },
418
+ protect(prot: MemoryProtection) {
419
+ const protValue = PROTECTION_MAP[prot];
420
+ ffi.mprotect(actualPtr as unknown as number, BigInt(actualSize), protValue);
421
+ },
422
+ advise(advice: MemoryAdvice) {
423
+ const adviceValue = ADVICE_MAP[advice];
424
+ ffi.madvise(actualPtr as unknown as number, BigInt(actualSize), adviceValue);
425
+ },
426
+ lock() {
427
+ ffi.mlock(actualPtr as unknown as number, BigInt(actualSize));
428
+ },
429
+ unlock() {
430
+ ffi.munlock(actualPtr as unknown as number, BigInt(actualSize));
431
+ },
432
+ };
433
+ }
package/dist/oom.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * OOM (Out of Memory) handling strategies
3
+ */
4
+ export type OOMStrategy = 'throw' | 'null' | 'callback' | 'fallback';
5
+ export interface OOMOptions {
6
+ strategy?: OOMStrategy;
7
+ onOOM?: (requestedSize: number) => number | null;
8
+ fallbackSize?: number;
9
+ }
10
+ /**
11
+ * Set global OOM strategy
12
+ */
13
+ export declare function setOOMStrategy(strategy: OOMStrategy): void;
14
+ /**
15
+ * Get current global OOM strategy
16
+ */
17
+ export declare function getOOMStrategy(): OOMStrategy;
18
+ /**
19
+ * Set global OOM callback (for 'callback' strategy)
20
+ */
21
+ export declare function setOOMCallback(callback: (size: number) => number | null): void;
22
+ /**
23
+ * Set global fallback size (for 'fallback' strategy)
24
+ */
25
+ export declare function setFallbackSize(size: number): void;
26
+ /**
27
+ * Handle OOM according to strategy
28
+ * Returns null if strategy allows, otherwise throws
29
+ */
30
+ export declare function handleOOM(requestedSize: number, options?: OOMOptions): number | null;
31
+ /**
32
+ * Check if allocation would succeed (probe)
33
+ * Useful for 'fallback' strategy
34
+ */
35
+ export declare function wouldAllocSucceed(size: number): boolean;
36
+ //# sourceMappingURL=oom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oom.d.ts","sourceRoot":"","sources":["oom.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,UAAU,CAAC;AAErE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,KAAK,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACjD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAOD;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAE1D;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,WAAW,CAE5C;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,GAAG,IAAI,CAE9E;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAElD;AAED;;;GAGG;AACH,wBAAgB,SAAS,CACvB,aAAa,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE,UAAU,GACnB,MAAM,GAAG,IAAI,CAyBf;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAYvD"}
package/dist/oom.js ADDED
@@ -0,0 +1,73 @@
1
+ /**
2
+ * OOM (Out of Memory) handling strategies
3
+ */
4
+ // Global OOM strategy
5
+ let globalOOMStrategy = 'throw';
6
+ let globalOOMCallback = null;
7
+ let globalFallbackSize = 4096;
8
+ /**
9
+ * Set global OOM strategy
10
+ */
11
+ export function setOOMStrategy(strategy) {
12
+ globalOOMStrategy = strategy;
13
+ }
14
+ /**
15
+ * Get current global OOM strategy
16
+ */
17
+ export function getOOMStrategy() {
18
+ return globalOOMStrategy;
19
+ }
20
+ /**
21
+ * Set global OOM callback (for 'callback' strategy)
22
+ */
23
+ export function setOOMCallback(callback) {
24
+ globalOOMCallback = callback;
25
+ }
26
+ /**
27
+ * Set global fallback size (for 'fallback' strategy)
28
+ */
29
+ export function setFallbackSize(size) {
30
+ globalFallbackSize = size;
31
+ }
32
+ /**
33
+ * Handle OOM according to strategy
34
+ * Returns null if strategy allows, otherwise throws
35
+ */
36
+ export function handleOOM(requestedSize, options) {
37
+ const strategy = options?.strategy ?? globalOOMStrategy;
38
+ switch (strategy) {
39
+ case 'null':
40
+ return null;
41
+ case 'callback': {
42
+ const callback = options?.onOOM ?? globalOOMCallback;
43
+ if (callback) {
44
+ return callback(requestedSize);
45
+ }
46
+ return null;
47
+ }
48
+ case 'fallback': {
49
+ return options?.fallbackSize ?? globalFallbackSize;
50
+ }
51
+ case 'throw':
52
+ default:
53
+ throw new Error(`Out of memory: failed to allocate ${requestedSize} bytes`);
54
+ }
55
+ }
56
+ /**
57
+ * Check if allocation would succeed (probe)
58
+ * Useful for 'fallback' strategy
59
+ */
60
+ export function wouldAllocSucceed(size) {
61
+ try {
62
+ // This is a heuristic - actual allocation may still fail
63
+ const totalMem = process.memoryUsage().heapTotal;
64
+ const usedMem = process.memoryUsage().heapUsed;
65
+ const available = totalMem - usedMem;
66
+ // Be conservative - require 2x the requested size
67
+ return available >= size * 2;
68
+ }
69
+ catch {
70
+ return false;
71
+ }
72
+ }
73
+ //# sourceMappingURL=oom.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oom.js","sourceRoot":"","sources":["oom.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,sBAAsB;AACtB,IAAI,iBAAiB,GAAgB,OAAO,CAAC;AAC7C,IAAI,iBAAiB,GAA6C,IAAI,CAAC;AACvE,IAAI,kBAAkB,GAAW,IAAI,CAAC;AAEtC;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,QAAqB;IAClD,iBAAiB,GAAG,QAAQ,CAAC;AAC/B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc;IAC5B,OAAO,iBAAiB,CAAC;AAC3B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,QAAyC;IACtE,iBAAiB,GAAG,QAAQ,CAAC;AAC/B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,kBAAkB,GAAG,IAAI,CAAC;AAC5B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CACvB,aAAqB,EACrB,OAAoB;IAEpB,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,iBAAiB,CAAC;IAExD,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,MAAM;YACT,OAAO,IAAI,CAAC;QAEd,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,MAAM,QAAQ,GAAG,OAAO,EAAE,KAAK,IAAI,iBAAiB,CAAC;YACrD,IAAI,QAAQ,EAAE,CAAC;gBACb,OAAO,QAAQ,CAAC,aAAa,CAAC,CAAC;YACjC,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,OAAO,OAAO,EAAE,YAAY,IAAI,kBAAkB,CAAC;QACrD,CAAC;QAED,KAAK,OAAO,CAAC;QACb;YACE,MAAM,IAAI,KAAK,CACb,qCAAqC,aAAa,QAAQ,CAC3D,CAAC;IACN,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,IAAI,CAAC;QACH,yDAAyD;QACzD,MAAM,QAAQ,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,SAAS,CAAC;QACjD,MAAM,OAAO,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC;QAC/C,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;QAErC,kDAAkD;QAClD,OAAO,SAAS,IAAI,IAAI,GAAG,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
package/dist/oom.ts ADDED
@@ -0,0 +1,96 @@
1
+ /**
2
+ * OOM (Out of Memory) handling strategies
3
+ */
4
+
5
+ export type OOMStrategy = 'throw' | 'null' | 'callback' | 'fallback';
6
+
7
+ export interface OOMOptions {
8
+ strategy?: OOMStrategy;
9
+ onOOM?: (requestedSize: number) => number | null;
10
+ fallbackSize?: number;
11
+ }
12
+
13
+ // Global OOM strategy
14
+ let globalOOMStrategy: OOMStrategy = 'throw';
15
+ let globalOOMCallback: ((size: number) => number | null) | null = null;
16
+ let globalFallbackSize: number = 4096;
17
+
18
+ /**
19
+ * Set global OOM strategy
20
+ */
21
+ export function setOOMStrategy(strategy: OOMStrategy): void {
22
+ globalOOMStrategy = strategy;
23
+ }
24
+
25
+ /**
26
+ * Get current global OOM strategy
27
+ */
28
+ export function getOOMStrategy(): OOMStrategy {
29
+ return globalOOMStrategy;
30
+ }
31
+
32
+ /**
33
+ * Set global OOM callback (for 'callback' strategy)
34
+ */
35
+ export function setOOMCallback(callback: (size: number) => number | null): void {
36
+ globalOOMCallback = callback;
37
+ }
38
+
39
+ /**
40
+ * Set global fallback size (for 'fallback' strategy)
41
+ */
42
+ export function setFallbackSize(size: number): void {
43
+ globalFallbackSize = size;
44
+ }
45
+
46
+ /**
47
+ * Handle OOM according to strategy
48
+ * Returns null if strategy allows, otherwise throws
49
+ */
50
+ export function handleOOM(
51
+ requestedSize: number,
52
+ options?: OOMOptions
53
+ ): number | null {
54
+ const strategy = options?.strategy ?? globalOOMStrategy;
55
+
56
+ switch (strategy) {
57
+ case 'null':
58
+ return null;
59
+
60
+ case 'callback': {
61
+ const callback = options?.onOOM ?? globalOOMCallback;
62
+ if (callback) {
63
+ return callback(requestedSize);
64
+ }
65
+ return null;
66
+ }
67
+
68
+ case 'fallback': {
69
+ return options?.fallbackSize ?? globalFallbackSize;
70
+ }
71
+
72
+ case 'throw':
73
+ default:
74
+ throw new Error(
75
+ `Out of memory: failed to allocate ${requestedSize} bytes`
76
+ );
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Check if allocation would succeed (probe)
82
+ * Useful for 'fallback' strategy
83
+ */
84
+ export function wouldAllocSucceed(size: number): boolean {
85
+ try {
86
+ // This is a heuristic - actual allocation may still fail
87
+ const totalMem = process.memoryUsage().heapTotal;
88
+ const usedMem = process.memoryUsage().heapUsed;
89
+ const available = totalMem - usedMem;
90
+
91
+ // Be conservative - require 2x the requested size
92
+ return available >= size * 2;
93
+ } catch {
94
+ return false;
95
+ }
96
+ }