bun-memory 1.0.3 → 1.1.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/README.md +42 -39
- package/index.ts +1 -0
- package/package.json +1 -1
- package/structs/Memory.ts +1053 -904
- package/structs/Win32Error.ts +261 -24
package/structs/Win32Error.ts
CHANGED
|
@@ -10,47 +10,189 @@
|
|
|
10
10
|
* https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes#system-error-codes
|
|
11
11
|
*
|
|
12
12
|
* @remarks
|
|
13
|
-
* - Requires Windows.
|
|
14
|
-
* - Requires Bun runtime (uses `bun:ffi`).
|
|
15
|
-
* - Designed to be fast and allocation
|
|
13
|
+
* - Requires Windows operating system.
|
|
14
|
+
* - Requires Bun runtime environment (uses `bun:ffi`).
|
|
15
|
+
* - Designed to be fast and allocation-conscious.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import Win32Error from './Win32Error';
|
|
20
|
+
*
|
|
21
|
+
* // Typical usage in error handling
|
|
22
|
+
* try {
|
|
23
|
+
* const result = someWin32Operation();
|
|
24
|
+
* if (result === INVALID_HANDLE_VALUE) {
|
|
25
|
+
* throw new Win32Error('CreateFile', GetLastError());
|
|
26
|
+
* }
|
|
27
|
+
* } catch (error) {
|
|
28
|
+
* if (error instanceof Win32Error) {
|
|
29
|
+
* console.log(`Operation: ${error.what}`);
|
|
30
|
+
* console.log(`Error code: ${error.code}`);
|
|
31
|
+
* console.log(`Message: ${error.message}`);
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
16
35
|
*/
|
|
17
36
|
|
|
18
37
|
import { dlopen, FFIType } from 'bun:ffi';
|
|
19
38
|
|
|
20
39
|
/**
|
|
21
|
-
* Minimal Kernel32
|
|
22
|
-
*
|
|
40
|
+
* Minimal Kernel32 Windows API functions imported via Foreign Function Interface (FFI).
|
|
41
|
+
* This module only imports FormatMessageW for converting error codes to human-readable messages.
|
|
42
|
+
*
|
|
43
|
+
* @remarks
|
|
44
|
+
* FormatMessageW is used instead of FormatMessageA to properly handle Unicode characters
|
|
45
|
+
* in error messages, which is important for internationalization support.
|
|
23
46
|
*/
|
|
24
|
-
|
|
25
47
|
const { symbols: Kernel32 } = dlopen('kernel32.dll', {
|
|
48
|
+
/**
|
|
49
|
+
* FormatMessageW - Formats a message string using a message definition from a message table resource.
|
|
50
|
+
*
|
|
51
|
+
* @param dwFlags - Formatting options (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS)
|
|
52
|
+
* @param lpSource - Location of the message definition (unused when FROM_SYSTEM is specified)
|
|
53
|
+
* @param dwMessageId - Message identifier (Win32 error code)
|
|
54
|
+
* @param dwLanguageId - Language identifier (0 for system default)
|
|
55
|
+
* @param lpBuffer - Buffer to receive the formatted message
|
|
56
|
+
* @param nSize - Size of the buffer in TCHARs
|
|
57
|
+
* @param Arguments - Array of values for message insertion (unused with IGNORE_INSERTS)
|
|
58
|
+
* @returns Number of TCHARs stored in the buffer, 0 on failure
|
|
59
|
+
*/
|
|
26
60
|
FormatMessageW: { args: [FFIType.u32, FFIType.u64, FFIType.u32, FFIType.u32, FFIType.ptr, FFIType.u32, FFIType.u64], returns: FFIType.u32 },
|
|
27
61
|
});
|
|
28
62
|
|
|
29
63
|
/**
|
|
30
|
-
* Error
|
|
64
|
+
* Error class representing a Windows (Win32) system error.
|
|
65
|
+
*
|
|
66
|
+
* This class extends the standard JavaScript Error class to provide enhanced
|
|
67
|
+
* error reporting for Windows API failures. It automatically formats Win32
|
|
68
|
+
* error codes into human-readable messages using the Windows FormatMessageW API.
|
|
69
|
+
*
|
|
70
|
+
* Key features:
|
|
71
|
+
* - Automatic error message formatting using Windows system messages
|
|
72
|
+
* - Message caching for improved performance on repeated error codes
|
|
73
|
+
* - Preserves both the operation name and numeric error code
|
|
74
|
+
* - Provides stack trace capture for debugging
|
|
31
75
|
*
|
|
32
76
|
* @example
|
|
33
|
-
* ```
|
|
77
|
+
* ```typescript
|
|
78
|
+
* // Basic usage with a Win32 API call
|
|
34
79
|
* const hSnapshot = Kernel32.CreateToolhelp32Snapshot(dwFlags, th32ProcessID);
|
|
35
80
|
*
|
|
36
|
-
* if(hSnapshot === -1) {
|
|
81
|
+
* if (hSnapshot === -1) {
|
|
37
82
|
* // Wrap with context and the last error code you observed
|
|
38
83
|
* throw new Win32Error('CreateToolhelp32Snapshot', Kernel32.GetLastError());
|
|
39
84
|
* }
|
|
40
85
|
* ```
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```typescript
|
|
89
|
+
* // Error handling and inspection
|
|
90
|
+
* try {
|
|
91
|
+
* const hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, processId);
|
|
92
|
+
* if (hProcess === 0n) {
|
|
93
|
+
* throw new Win32Error('OpenProcess', GetLastError());
|
|
94
|
+
* }
|
|
95
|
+
* } catch (error) {
|
|
96
|
+
* if (error instanceof Win32Error) {
|
|
97
|
+
* console.error(`Failed operation: ${error.what}`);
|
|
98
|
+
* console.error(`Win32 error code: ${error.code}`);
|
|
99
|
+
* console.error(`System message: ${error.message}`);
|
|
100
|
+
*
|
|
101
|
+
* // Handle specific error codes
|
|
102
|
+
* switch (error.code) {
|
|
103
|
+
* case 5: // ERROR_ACCESS_DENIED
|
|
104
|
+
* console.log('Access denied - try running as administrator');
|
|
105
|
+
* break;
|
|
106
|
+
* case 87: // ERROR_INVALID_PARAMETER
|
|
107
|
+
* console.log('Invalid parameter provided to the function');
|
|
108
|
+
* break;
|
|
109
|
+
* default:
|
|
110
|
+
* console.log('Unexpected error occurred');
|
|
111
|
+
* }
|
|
112
|
+
* }
|
|
113
|
+
* }
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```typescript
|
|
118
|
+
* // Custom error handling utility
|
|
119
|
+
* function handleWin32Result<T>(
|
|
120
|
+
* result: T,
|
|
121
|
+
* invalidValue: T,
|
|
122
|
+
* operation: string,
|
|
123
|
+
* getLastError: () => number
|
|
124
|
+
* ): T {
|
|
125
|
+
* if (result === invalidValue) {
|
|
126
|
+
* throw new Win32Error(operation, getLastError());
|
|
127
|
+
* }
|
|
128
|
+
* return result;
|
|
129
|
+
* }
|
|
130
|
+
*
|
|
131
|
+
* // Usage
|
|
132
|
+
* const handle = handleWin32Result(
|
|
133
|
+
* CreateFileW(filename, access, share, null, disposition, flags, null),
|
|
134
|
+
* INVALID_HANDLE_VALUE,
|
|
135
|
+
* 'CreateFileW',
|
|
136
|
+
* GetLastError
|
|
137
|
+
* );
|
|
138
|
+
* ```
|
|
41
139
|
*/
|
|
42
|
-
|
|
43
140
|
class Win32Error extends Error {
|
|
44
141
|
/**
|
|
45
|
-
*
|
|
142
|
+
* Creates a new Win32Error instance with formatted error message.
|
|
143
|
+
*
|
|
144
|
+
* The constructor automatically formats the Win32 error code into a human-readable
|
|
145
|
+
* message using the Windows FormatMessageW API. Messages are cached to improve
|
|
146
|
+
* performance when the same error code is encountered multiple times.
|
|
147
|
+
*
|
|
148
|
+
* The resulting error message follows the format:
|
|
149
|
+
* "{operation} failed ({code}): {system_message}"
|
|
150
|
+
*
|
|
151
|
+
* @param what - Name of the operation that failed (e.g., "OpenProcess", "CreateFile")
|
|
152
|
+
* @param code - The Win32 error code (DWORD) associated with the failure
|
|
153
|
+
*
|
|
154
|
+
* @throws {Error} This constructor does not throw, but may produce "Unknown error"
|
|
155
|
+
* messages if FormatMessageW fails to format the error code
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```typescript
|
|
159
|
+
* // Handle a file operation failure
|
|
160
|
+
* const hFile = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ,
|
|
161
|
+
* null, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, null);
|
|
162
|
+
* if (hFile === INVALID_HANDLE_VALUE) {
|
|
163
|
+
* throw new Win32Error('CreateFileW', GetLastError());
|
|
164
|
+
* }
|
|
165
|
+
* // Result: "CreateFileW failed (2): The system cannot find the file specified."
|
|
166
|
+
* ```
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* ```typescript
|
|
170
|
+
* // Handle process access failure
|
|
171
|
+
* const hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, 1234);
|
|
172
|
+
* if (hProcess === 0n) {
|
|
173
|
+
* throw new Win32Error('OpenProcess', GetLastError());
|
|
174
|
+
* }
|
|
175
|
+
* // Result: "OpenProcess failed (5): Access is denied."
|
|
176
|
+
* ```
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* ```typescript
|
|
180
|
+
* // Handle memory allocation failure
|
|
181
|
+
* const hHeap = GetProcessHeap();
|
|
182
|
+
* const ptr = HeapAlloc(hHeap, 0, 1024);
|
|
183
|
+
* if (ptr === 0n) {
|
|
184
|
+
* throw new Win32Error('HeapAlloc', GetLastError());
|
|
185
|
+
* }
|
|
186
|
+
* // Result: "HeapAlloc failed (8): Not enough memory resources are available to process this command."
|
|
187
|
+
* ```
|
|
46
188
|
*
|
|
47
|
-
* @param what Name of the operation that failed (e.g., `"OpenProcess"`).
|
|
48
|
-
* @param code The Win32 error code (DWORD) associated with the failure.
|
|
49
189
|
* @remarks
|
|
50
|
-
* The constructor formats the error message using `FormatMessageW` and memoizes
|
|
51
|
-
*
|
|
190
|
+
* - The constructor formats the error message using `FormatMessageW` and memoizes
|
|
191
|
+
* the message text per code to avoid repeated system calls
|
|
192
|
+
* - Messages are cleaned of newline characters and trimmed for consistent formatting
|
|
193
|
+
* - If FormatMessageW fails, "Unknown error" is used as the message
|
|
194
|
+
* - Stack trace is captured using Error.captureStackTrace when available
|
|
52
195
|
*/
|
|
53
|
-
|
|
54
196
|
constructor(what: string, code: number) {
|
|
55
197
|
let message = Win32Error.formatMessageWCache.get(code);
|
|
56
198
|
|
|
@@ -83,30 +225,125 @@ class Win32Error extends Error {
|
|
|
83
225
|
}
|
|
84
226
|
|
|
85
227
|
/**
|
|
86
|
-
* Cache of formatted messages keyed by Win32 error code
|
|
228
|
+
* Cache of formatted error messages keyed by Win32 error code.
|
|
229
|
+
*
|
|
230
|
+
* This cache stores the human-readable error messages returned by FormatMessageW
|
|
231
|
+
* to avoid repeated FFI calls for the same error codes. This optimization is
|
|
232
|
+
* particularly beneficial in scenarios where the same errors occur frequently.
|
|
233
|
+
*
|
|
234
|
+
* The cache is implemented as a static Map and persists for the lifetime of the
|
|
235
|
+
* application. Memory usage is typically minimal as there are a finite number
|
|
236
|
+
* of possible Win32 error codes, and most applications encounter only a subset.
|
|
237
|
+
*
|
|
238
|
+
* @remarks
|
|
239
|
+
* - Messages are cached after the first successful FormatMessageW call
|
|
240
|
+
* - Failed FormatMessageW calls result in "Unknown error" being cached
|
|
241
|
+
* - The cache is never cleared, ensuring consistent performance throughout application lifetime
|
|
242
|
+
* - Thread-safe in Node.js/Bun single-threaded environment
|
|
243
|
+
*
|
|
87
244
|
* @private
|
|
88
245
|
*/
|
|
89
|
-
|
|
90
246
|
private static readonly formatMessageWCache = new Map<number, string>();
|
|
91
247
|
|
|
92
248
|
/**
|
|
93
|
-
*
|
|
94
|
-
*
|
|
249
|
+
* Static buffer used for FormatMessageW system calls.
|
|
250
|
+
*
|
|
251
|
+
* This buffer is allocated once and reused for all FormatMessageW calls to minimize
|
|
252
|
+
* memory allocations. The buffer size of 4,096 bytes (2,048 UTF-16 characters) is
|
|
253
|
+
* sufficient for typical Windows system error messages.
|
|
254
|
+
*
|
|
255
|
+
* The buffer is allocated as unsafe (uninitialized) memory for performance, as
|
|
256
|
+
* FormatMessageW will overwrite its contents completely.
|
|
257
|
+
*
|
|
258
|
+
* @remarks
|
|
259
|
+
* - Buffer size: 4,096 bytes (2,048 UTF-16 characters)
|
|
260
|
+
* - Shared across all Win32Error instances to minimize memory footprint
|
|
261
|
+
* - Contents are overwritten on each FormatMessageW call
|
|
262
|
+
* - Adequate size for all standard Windows system error messages
|
|
263
|
+
*
|
|
95
264
|
* @private
|
|
96
265
|
*/
|
|
97
|
-
|
|
98
266
|
private static readonly scratch4096 = Buffer.allocUnsafe(4_096);
|
|
99
267
|
|
|
100
268
|
/**
|
|
101
269
|
* The Win32 error code associated with this failure.
|
|
270
|
+
*
|
|
271
|
+
* This property contains the numeric error code that was returned by the Windows API
|
|
272
|
+
* function GetLastError() at the time of the failure. This code can be used for
|
|
273
|
+
* programmatic error handling and logging.
|
|
274
|
+
*
|
|
275
|
+
* Common Win32 error codes include:
|
|
276
|
+
* - 2: ERROR_FILE_NOT_FOUND - The system cannot find the file specified
|
|
277
|
+
* - 3: ERROR_PATH_NOT_FOUND - The system cannot find the path specified
|
|
278
|
+
* - 5: ERROR_ACCESS_DENIED - Access is denied
|
|
279
|
+
* - 6: ERROR_INVALID_HANDLE - The handle is invalid
|
|
280
|
+
* - 87: ERROR_INVALID_PARAMETER - The parameter is incorrect
|
|
281
|
+
* - 122: ERROR_INSUFFICIENT_BUFFER - The data area passed to a system call is too small
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* ```typescript
|
|
285
|
+
* try {
|
|
286
|
+
* // Some Win32 operation that might fail
|
|
287
|
+
* performWin32Operation();
|
|
288
|
+
* } catch (error) {
|
|
289
|
+
* if (error instanceof Win32Error) {
|
|
290
|
+
* switch (error.code) {
|
|
291
|
+
* case 2: // ERROR_FILE_NOT_FOUND
|
|
292
|
+
* console.log('File not found, creating new file...');
|
|
293
|
+
* break;
|
|
294
|
+
* case 5: // ERROR_ACCESS_DENIED
|
|
295
|
+
* console.log('Access denied, requesting elevated privileges...');
|
|
296
|
+
* break;
|
|
297
|
+
* default:
|
|
298
|
+
* console.log(`Unexpected error code: ${error.code}`);
|
|
299
|
+
* }
|
|
300
|
+
* }
|
|
301
|
+
* }
|
|
302
|
+
* ```
|
|
303
|
+
*
|
|
304
|
+
* @readonly
|
|
102
305
|
*/
|
|
103
|
-
|
|
104
306
|
public readonly code: number;
|
|
105
307
|
|
|
106
308
|
/**
|
|
107
|
-
* The operation or API name that failed.
|
|
309
|
+
* The operation or Windows API function name that failed.
|
|
310
|
+
*
|
|
311
|
+
* This property contains the name of the operation or API function that was being
|
|
312
|
+
* executed when the error occurred. It provides context for debugging and logging,
|
|
313
|
+
* making it easier to identify which specific operation failed in complex code.
|
|
314
|
+
*
|
|
315
|
+
* The operation name is typically the exact name of the Win32 API function that
|
|
316
|
+
* was called, but can also be a descriptive name for higher-level operations.
|
|
317
|
+
*
|
|
318
|
+
* @example
|
|
319
|
+
* ```typescript
|
|
320
|
+
* try {
|
|
321
|
+
* const hFile = CreateFileW(filename, access, share, null, disposition, flags, null);
|
|
322
|
+
* if (hFile === INVALID_HANDLE_VALUE) {
|
|
323
|
+
* throw new Win32Error('CreateFileW', GetLastError());
|
|
324
|
+
* }
|
|
325
|
+
* } catch (error) {
|
|
326
|
+
* if (error instanceof Win32Error) {
|
|
327
|
+
* console.log(`Failed operation: ${error.what}`); // "CreateFileW"
|
|
328
|
+
* console.log(`Error details: ${error.message}`); // "CreateFileW failed (2): The system cannot find the file specified."
|
|
329
|
+
* }
|
|
330
|
+
* }
|
|
331
|
+
* ```
|
|
332
|
+
*
|
|
333
|
+
* @example
|
|
334
|
+
* ```typescript
|
|
335
|
+
* // Using descriptive operation names for complex operations
|
|
336
|
+
* try {
|
|
337
|
+
* // Multiple API calls involved in process enumeration
|
|
338
|
+
* enumerateProcesses();
|
|
339
|
+
* } catch (apiError) {
|
|
340
|
+
* // Re-throw with more descriptive operation name
|
|
341
|
+
* throw new Win32Error('ProcessEnumeration', apiError.code);
|
|
342
|
+
* }
|
|
343
|
+
* ```
|
|
344
|
+
*
|
|
345
|
+
* @readonly
|
|
108
346
|
*/
|
|
109
|
-
|
|
110
347
|
public readonly what: string;
|
|
111
348
|
}
|
|
112
349
|
|