@react-native-gpui/fs 0.0.0-stage → 0.0.1-alpha.1
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/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +36 -2
- package/desktop/Cargo.toml +29 -0
- package/desktop/build.rs +3 -0
- package/desktop/src/dialogs.rs +394 -0
- package/desktop/src/dirs.rs +363 -0
- package/desktop/src/download.rs +329 -0
- package/desktop/src/errors.rs +149 -0
- package/desktop/src/files.rs +640 -0
- package/desktop/src/handles.rs +697 -0
- package/desktop/src/lib.rs +286 -0
- package/desktop/src/links.rs +336 -0
- package/desktop/src/module.rs +192 -0
- package/desktop/src/paths.rs +231 -0
- package/desktop/src/upload.rs +237 -0
- package/desktop/src/watching.rs +392 -0
- package/package.json +38 -4
- package/react-native.config.js +9 -0
- package/src/NativeFs.ts +235 -0
- package/src/constants.ts +43 -0
- package/src/download.ts +110 -0
- package/src/index.ts +788 -0
- package/src/promises.ts +771 -0
- package/src/shared.ts +539 -0
- package/src/stats.ts +196 -0
- package/src/upload.ts +97 -0
- package/src/watch.ts +141 -0
package/src/shared.ts
ADDED
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pieces every face of the API needs: Node error objects, `file://` path
|
|
3
|
+
* conversion, the encodings Node supports, and the flag/mode translation.
|
|
4
|
+
*
|
|
5
|
+
* Nothing here touches the native module, so it stays usable from the sync,
|
|
6
|
+
* callback and promise layers without a cycle.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Bytes in any of the shapes a caller may hand a writer. */
|
|
10
|
+
export type BinaryData = string | ArrayBuffer | ArrayBufferView;
|
|
11
|
+
|
|
12
|
+
/** The encodings this module implements, after aliasing. */
|
|
13
|
+
export type NormalizedEncoding =
|
|
14
|
+
'utf8' | 'utf16le' | 'latin1' | 'base64' | 'hex';
|
|
15
|
+
|
|
16
|
+
/** The encodings accepted in an options object, with Node's aliases. */
|
|
17
|
+
export type Encoding =
|
|
18
|
+
| 'utf8'
|
|
19
|
+
| 'utf-8'
|
|
20
|
+
| 'utf16le'
|
|
21
|
+
| 'utf-16le'
|
|
22
|
+
| 'ucs2'
|
|
23
|
+
| 'ucs-2'
|
|
24
|
+
| 'latin1'
|
|
25
|
+
| 'binary'
|
|
26
|
+
| 'ascii'
|
|
27
|
+
| 'base64'
|
|
28
|
+
| 'hex';
|
|
29
|
+
|
|
30
|
+
/** What a rejected call looks like: Node's `ErrnoException`. */
|
|
31
|
+
export interface NodeError extends Error {
|
|
32
|
+
code?: string;
|
|
33
|
+
errno?: number;
|
|
34
|
+
syscall?: string;
|
|
35
|
+
path?: string;
|
|
36
|
+
/** The second path of a two-path call, as Node names it on `rename` and `cp`. */
|
|
37
|
+
dest?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Builds an error with a code but no OS call behind it. */
|
|
41
|
+
export function nodeError(code: string, message: string): NodeError {
|
|
42
|
+
const error = new Error(message) as NodeError;
|
|
43
|
+
error.code = code;
|
|
44
|
+
return error;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The error Node raises when a path is not there, in its message format. */
|
|
48
|
+
export function missingPath(path: string, syscall: string): NodeError {
|
|
49
|
+
return nodeError(
|
|
50
|
+
'ENOENT',
|
|
51
|
+
`ENOENT: no such file or directory, ${syscall} '${path}'`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Adds the fields only the JS side knows to an error a native call rejected
|
|
57
|
+
* with.
|
|
58
|
+
*
|
|
59
|
+
* The Rust layer reports `code` and a Node-format message, but it deliberately
|
|
60
|
+
* does not guess `syscall` — one native `stat` serves `stat` and `lstat`, and it
|
|
61
|
+
* cannot know the spelling of the path the caller passed.
|
|
62
|
+
*/
|
|
63
|
+
export function withContext(
|
|
64
|
+
error: unknown,
|
|
65
|
+
syscall: string,
|
|
66
|
+
path?: string,
|
|
67
|
+
): NodeError {
|
|
68
|
+
const wrapped: NodeError =
|
|
69
|
+
error instanceof Error
|
|
70
|
+
? (error as NodeError)
|
|
71
|
+
: nodeError('EIO', String(error));
|
|
72
|
+
if (wrapped.syscall === undefined) {
|
|
73
|
+
wrapped.syscall = syscall;
|
|
74
|
+
}
|
|
75
|
+
if (path !== undefined && wrapped.path === undefined) {
|
|
76
|
+
wrapped.path = path;
|
|
77
|
+
}
|
|
78
|
+
return wrapped;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* `file://` URLs to paths, leaving plain paths alone.
|
|
83
|
+
*
|
|
84
|
+
* Node accepts `URL` objects and `file:` strings interchangeably; this API takes
|
|
85
|
+
* strings, so the conversion is textual. Percent escapes are decoded because a
|
|
86
|
+
* path with a space arrives as `file:///a%20b`.
|
|
87
|
+
*/
|
|
88
|
+
export function toPath(path: string): string {
|
|
89
|
+
if (!path.startsWith('file://')) {
|
|
90
|
+
return path;
|
|
91
|
+
}
|
|
92
|
+
let rest = path.slice('file://'.length);
|
|
93
|
+
// An empty or `localhost` authority is the local machine.
|
|
94
|
+
if (rest.startsWith('localhost/')) {
|
|
95
|
+
rest = rest.slice('localhost'.length);
|
|
96
|
+
}
|
|
97
|
+
const decoded = decodeURIComponent(rest);
|
|
98
|
+
// `file:///C:/x` is a drive path on Windows, not a path under `/`.
|
|
99
|
+
if (/^\/[A-Za-z]:[\\/]/.test(decoded)) {
|
|
100
|
+
return decoded.slice(1);
|
|
101
|
+
}
|
|
102
|
+
return decoded;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Node's bounds check for a buffer window, as `RangeError` with
|
|
107
|
+
* `code = 'ERR_OUT_OF_RANGE'`.
|
|
108
|
+
*
|
|
109
|
+
* Both `read` and `write` take a window into a caller's buffer, and the native
|
|
110
|
+
* side reads or writes `length` bytes before the result is trimmed to fit — so an
|
|
111
|
+
* oversized `length` would consume bytes from the file and silently drop them
|
|
112
|
+
* rather than failing. Node rejects the call instead, and its wording is
|
|
113
|
+
* reproduced because callers match on it:
|
|
114
|
+
*
|
|
115
|
+
* The value of "length" is out of range. It must be <= 2. Received 8
|
|
116
|
+
*/
|
|
117
|
+
export function checkBufferRange(
|
|
118
|
+
buffer: Uint8Array,
|
|
119
|
+
offset: number | undefined,
|
|
120
|
+
length: number | undefined,
|
|
121
|
+
): { offset: number; length: number } {
|
|
122
|
+
const start = offset ?? 0;
|
|
123
|
+
if (!Number.isInteger(start) || start < 0 || start > buffer.byteLength) {
|
|
124
|
+
throw nodeError(
|
|
125
|
+
'ERR_OUT_OF_RANGE',
|
|
126
|
+
`The value of "offset" is out of range. It must be >= 0 && <= ${buffer.byteLength}. Received ${start}`,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
const count = length ?? buffer.byteLength - start;
|
|
130
|
+
const available = buffer.byteLength - start;
|
|
131
|
+
if (!Number.isInteger(count) || count < 0 || count > available) {
|
|
132
|
+
throw nodeError(
|
|
133
|
+
'ERR_OUT_OF_RANGE',
|
|
134
|
+
`The value of "length" is out of range. It must be <= ${available}. Received ${count}`,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
return { offset: start, length: count };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* A recursive `readdir` entry as the path Node reports.
|
|
142
|
+
*
|
|
143
|
+
* Node's `readdir(dir, { recursive: true })` returns paths relative to `dir`
|
|
144
|
+
* — `nested/deep/leaf.txt`, not `leaf.txt` — so a caller can join them onto the
|
|
145
|
+
* root and get a real path. The native side reports each entry's absolute
|
|
146
|
+
* containing directory, so the relative name is what remains after removing the
|
|
147
|
+
* root. A non-recursive listing names the direct children only, which is already
|
|
148
|
+
* what `entry.name` holds, so this is used for the recursive case alone.
|
|
149
|
+
*
|
|
150
|
+
* The separator is taken from the paths themselves rather than from a platform
|
|
151
|
+
* check: the native side returns native separators, and a module that reaches
|
|
152
|
+
* for `process.platform` needs Node's types for a question the data already
|
|
153
|
+
* answers.
|
|
154
|
+
*/
|
|
155
|
+
export function relativeEntryName(
|
|
156
|
+
root: string,
|
|
157
|
+
parentPath: string,
|
|
158
|
+
name: string,
|
|
159
|
+
): string {
|
|
160
|
+
const separator = root.includes('\\') ? '\\' : '/';
|
|
161
|
+
const split = (value: string) => value.split(/[\\/]+/u).filter(Boolean);
|
|
162
|
+
const rootParts = split(root);
|
|
163
|
+
const parentParts = split(parentPath);
|
|
164
|
+
// Compare from the front. A parent that does not start with the root means the
|
|
165
|
+
// entry is somewhere the caller did not ask about, which should not happen, so
|
|
166
|
+
// the bare name is a safer answer than a fabricated path.
|
|
167
|
+
const inside = rootParts.every((part, index) => parentParts[index] === part);
|
|
168
|
+
if (!inside) {
|
|
169
|
+
return name;
|
|
170
|
+
}
|
|
171
|
+
return [...parentParts.slice(rootParts.length), name].join(separator);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** The encoding name, folded to the set implemented here. */
|
|
175
|
+
export function normalizeEncoding(
|
|
176
|
+
encoding: string | null | undefined,
|
|
177
|
+
): NormalizedEncoding | null {
|
|
178
|
+
if (encoding === null || encoding === undefined) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
switch (encoding.toLowerCase()) {
|
|
182
|
+
case 'utf8':
|
|
183
|
+
case 'utf-8':
|
|
184
|
+
return 'utf8';
|
|
185
|
+
case 'utf16le':
|
|
186
|
+
case 'utf-16le':
|
|
187
|
+
case 'ucs2':
|
|
188
|
+
case 'ucs-2':
|
|
189
|
+
return 'utf16le';
|
|
190
|
+
case 'latin1':
|
|
191
|
+
case 'binary':
|
|
192
|
+
case 'ascii':
|
|
193
|
+
return 'latin1';
|
|
194
|
+
case 'base64':
|
|
195
|
+
return 'base64';
|
|
196
|
+
case 'hex':
|
|
197
|
+
return 'hex';
|
|
198
|
+
default:
|
|
199
|
+
throw nodeError(
|
|
200
|
+
'ERR_INVALID_ARG_VALUE',
|
|
201
|
+
`The argument 'encoding' is invalid encoding. Received '${encoding}'`,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const BASE64_ALPHABET =
|
|
207
|
+
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
208
|
+
|
|
209
|
+
const BASE64_VALUES: Record<string, number> = (() => {
|
|
210
|
+
const values: Record<string, number> = {};
|
|
211
|
+
for (let index = 0; index < BASE64_ALPHABET.length; index += 1) {
|
|
212
|
+
values[BASE64_ALPHABET[index]] = index;
|
|
213
|
+
}
|
|
214
|
+
// The URL-safe alphabet is accepted too: `Buffer` decodes either.
|
|
215
|
+
values['-'] = 62;
|
|
216
|
+
values._ = 63;
|
|
217
|
+
return values;
|
|
218
|
+
})();
|
|
219
|
+
|
|
220
|
+
function encodeBase64(bytes: Uint8Array): string {
|
|
221
|
+
let out = '';
|
|
222
|
+
for (let index = 0; index < bytes.byteLength; index += 3) {
|
|
223
|
+
const first = bytes[index];
|
|
224
|
+
const second = index + 1 < bytes.byteLength ? bytes[index + 1] : 0;
|
|
225
|
+
const third = index + 2 < bytes.byteLength ? bytes[index + 2] : 0;
|
|
226
|
+
const triple = (first << 16) | (second << 8) | third;
|
|
227
|
+
out += BASE64_ALPHABET[(triple >> 18) & 0x3f];
|
|
228
|
+
out += BASE64_ALPHABET[(triple >> 12) & 0x3f];
|
|
229
|
+
out +=
|
|
230
|
+
index + 1 < bytes.byteLength
|
|
231
|
+
? BASE64_ALPHABET[(triple >> 6) & 0x3f]
|
|
232
|
+
: '=';
|
|
233
|
+
out += index + 2 < bytes.byteLength ? BASE64_ALPHABET[triple & 0x3f] : '=';
|
|
234
|
+
}
|
|
235
|
+
return out;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function decodeBase64(text: string): Uint8Array {
|
|
239
|
+
const bytes: number[] = [];
|
|
240
|
+
let pending = 0;
|
|
241
|
+
let bits = 0;
|
|
242
|
+
for (const character of text) {
|
|
243
|
+
if (
|
|
244
|
+
character === '=' ||
|
|
245
|
+
character === '\n' ||
|
|
246
|
+
character === '\r' ||
|
|
247
|
+
character === ' '
|
|
248
|
+
) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
const value = BASE64_VALUES[character];
|
|
252
|
+
if (value === undefined) {
|
|
253
|
+
// Node stops at the first character that is not base64 rather than
|
|
254
|
+
// failing, so `Buffer.from('abc$', 'base64')` decodes the prefix.
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
pending = (pending << 6) | value;
|
|
258
|
+
bits += 6;
|
|
259
|
+
if (bits >= 8) {
|
|
260
|
+
bits -= 8;
|
|
261
|
+
bytes.push((pending >> bits) & 0xff);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return Uint8Array.from(bytes);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function encodeHex(bytes: Uint8Array): string {
|
|
268
|
+
let out = '';
|
|
269
|
+
for (let index = 0; index < bytes.byteLength; index += 1) {
|
|
270
|
+
out += bytes[index].toString(16).padStart(2, '0');
|
|
271
|
+
}
|
|
272
|
+
return out;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function decodeHex(text: string): Uint8Array {
|
|
276
|
+
const bytes: number[] = [];
|
|
277
|
+
for (let index = 0; index + 1 < text.length; index += 2) {
|
|
278
|
+
const value = Number.parseInt(text.slice(index, index + 2), 16);
|
|
279
|
+
if (Number.isNaN(value)) {
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
bytes.push(value);
|
|
283
|
+
}
|
|
284
|
+
return Uint8Array.from(bytes);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function encodeUtf8(text: string): Uint8Array {
|
|
288
|
+
const bytes: number[] = [];
|
|
289
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
290
|
+
let code = text.charCodeAt(index);
|
|
291
|
+
// A surrogate pair is one code point; without this an emoji becomes two
|
|
292
|
+
// sequences of replacement characters.
|
|
293
|
+
if (code >= 0xd800 && code <= 0xdbff && index + 1 < text.length) {
|
|
294
|
+
const next = text.charCodeAt(index + 1);
|
|
295
|
+
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
296
|
+
code = 0x10000 + ((code - 0xd800) << 10) + (next - 0xdc00);
|
|
297
|
+
index += 1;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (code < 0x80) {
|
|
301
|
+
bytes.push(code);
|
|
302
|
+
} else if (code < 0x800) {
|
|
303
|
+
bytes.push(0xc0 | (code >> 6), 0x80 | (code & 0x3f));
|
|
304
|
+
} else if (code < 0x10000) {
|
|
305
|
+
bytes.push(
|
|
306
|
+
0xe0 | (code >> 12),
|
|
307
|
+
0x80 | ((code >> 6) & 0x3f),
|
|
308
|
+
0x80 | (code & 0x3f),
|
|
309
|
+
);
|
|
310
|
+
} else {
|
|
311
|
+
bytes.push(
|
|
312
|
+
0xf0 | (code >> 18),
|
|
313
|
+
0x80 | ((code >> 12) & 0x3f),
|
|
314
|
+
0x80 | ((code >> 6) & 0x3f),
|
|
315
|
+
0x80 | (code & 0x3f),
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return Uint8Array.from(bytes);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function decodeUtf8(bytes: Uint8Array): string {
|
|
323
|
+
let out = '';
|
|
324
|
+
let index = 0;
|
|
325
|
+
while (index < bytes.byteLength) {
|
|
326
|
+
const first = bytes[index];
|
|
327
|
+
const width =
|
|
328
|
+
first < 0x80
|
|
329
|
+
? 1
|
|
330
|
+
: first >= 0xf0
|
|
331
|
+
? 4
|
|
332
|
+
: first >= 0xe0
|
|
333
|
+
? 3
|
|
334
|
+
: first >= 0xc0
|
|
335
|
+
? 2
|
|
336
|
+
: 0;
|
|
337
|
+
if (width === 0 || index + width > bytes.byteLength) {
|
|
338
|
+
// Node decodes malformed input with U+FFFD instead of throwing, so a
|
|
339
|
+
// file that is not valid UTF-8 still reads as text.
|
|
340
|
+
out += '\ufffd';
|
|
341
|
+
index += 1;
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
let code = width === 1 ? first : first & (0xff >> (width + 1));
|
|
345
|
+
let valid = true;
|
|
346
|
+
for (let offset = 1; offset < width; offset += 1) {
|
|
347
|
+
const continuation = bytes[index + offset];
|
|
348
|
+
if ((continuation & 0xc0) !== 0x80) {
|
|
349
|
+
valid = false;
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
code = (code << 6) | (continuation & 0x3f);
|
|
353
|
+
}
|
|
354
|
+
if (!valid) {
|
|
355
|
+
out += '\ufffd';
|
|
356
|
+
index += 1;
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
out += String.fromCodePoint(code);
|
|
360
|
+
index += width;
|
|
361
|
+
}
|
|
362
|
+
return out;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function encodeUtf16le(text: string): Uint8Array {
|
|
366
|
+
const bytes = new Uint8Array(text.length * 2);
|
|
367
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
368
|
+
const code = text.charCodeAt(index);
|
|
369
|
+
bytes[index * 2] = code & 0xff;
|
|
370
|
+
bytes[index * 2 + 1] = code >> 8;
|
|
371
|
+
}
|
|
372
|
+
return bytes;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function decodeUtf16le(bytes: Uint8Array): string {
|
|
376
|
+
const codes: number[] = [];
|
|
377
|
+
for (let index = 0; index + 1 < bytes.byteLength; index += 2) {
|
|
378
|
+
codes.push(bytes[index] | (bytes[index + 1] << 8));
|
|
379
|
+
}
|
|
380
|
+
return chunkedFromCharCode(codes);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function chunkedFromCharCode(codes: number[]): string {
|
|
384
|
+
let out = '';
|
|
385
|
+
// `apply` has an argument-count limit, so long strings are built in pieces.
|
|
386
|
+
for (let index = 0; index < codes.length; index += 8192) {
|
|
387
|
+
out += String.fromCharCode.apply(null, codes.slice(index, index + 8192));
|
|
388
|
+
}
|
|
389
|
+
return out;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/** Text to bytes for the encodings implemented in JS. */
|
|
393
|
+
export function encodeString(
|
|
394
|
+
text: string,
|
|
395
|
+
encoding: NormalizedEncoding,
|
|
396
|
+
): Uint8Array {
|
|
397
|
+
switch (encoding) {
|
|
398
|
+
case 'utf8':
|
|
399
|
+
return encodeUtf8(text);
|
|
400
|
+
case 'utf16le':
|
|
401
|
+
return encodeUtf16le(text);
|
|
402
|
+
case 'latin1': {
|
|
403
|
+
const bytes = new Uint8Array(text.length);
|
|
404
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
405
|
+
bytes[index] = text.charCodeAt(index) & 0xff;
|
|
406
|
+
}
|
|
407
|
+
return bytes;
|
|
408
|
+
}
|
|
409
|
+
case 'base64':
|
|
410
|
+
return decodeBase64(text);
|
|
411
|
+
case 'hex':
|
|
412
|
+
return decodeHex(text);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/** Bytes to text for the encodings implemented in JS. */
|
|
417
|
+
export function decodeString(
|
|
418
|
+
bytes: Uint8Array,
|
|
419
|
+
encoding: NormalizedEncoding,
|
|
420
|
+
): string {
|
|
421
|
+
switch (encoding) {
|
|
422
|
+
case 'utf8':
|
|
423
|
+
return decodeUtf8(bytes);
|
|
424
|
+
case 'utf16le':
|
|
425
|
+
return decodeUtf16le(bytes);
|
|
426
|
+
case 'latin1': {
|
|
427
|
+
const codes: number[] = [];
|
|
428
|
+
for (let index = 0; index < bytes.byteLength; index += 1) {
|
|
429
|
+
codes.push(bytes[index]);
|
|
430
|
+
}
|
|
431
|
+
return chunkedFromCharCode(codes);
|
|
432
|
+
}
|
|
433
|
+
case 'base64':
|
|
434
|
+
return encodeBase64(bytes);
|
|
435
|
+
case 'hex':
|
|
436
|
+
return encodeHex(bytes);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Normalises any accepted data shape to an `ArrayBuffer` the bridge can send.
|
|
442
|
+
*
|
|
443
|
+
* A `Uint8Array` may be a window onto a larger allocation; slicing to the view's
|
|
444
|
+
* own range is what stops a writer from sending bytes that belong to something
|
|
445
|
+
* else in the same buffer.
|
|
446
|
+
*/
|
|
447
|
+
export function toArrayBuffer(
|
|
448
|
+
data: BinaryData,
|
|
449
|
+
encoding: NormalizedEncoding = 'utf8',
|
|
450
|
+
): ArrayBuffer {
|
|
451
|
+
if (typeof data === 'string') {
|
|
452
|
+
const bytes = encodeString(data, encoding);
|
|
453
|
+
return bytes.buffer.slice(
|
|
454
|
+
bytes.byteOffset,
|
|
455
|
+
bytes.byteOffset + bytes.byteLength,
|
|
456
|
+
) as ArrayBuffer;
|
|
457
|
+
}
|
|
458
|
+
if (data instanceof ArrayBuffer) {
|
|
459
|
+
return data;
|
|
460
|
+
}
|
|
461
|
+
const view = data as ArrayBufferView;
|
|
462
|
+
const buffer = view.buffer as ArrayBuffer;
|
|
463
|
+
if (view.byteOffset === 0 && view.byteLength === buffer.byteLength) {
|
|
464
|
+
return buffer;
|
|
465
|
+
}
|
|
466
|
+
return buffer.slice(view.byteOffset, view.byteOffset + view.byteLength);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/** A byte view of `toArrayBuffer`'s result, without copying. */
|
|
470
|
+
export function toBytes(
|
|
471
|
+
data: BinaryData,
|
|
472
|
+
encoding: NormalizedEncoding = 'utf8',
|
|
473
|
+
): Uint8Array {
|
|
474
|
+
return new Uint8Array(toArrayBuffer(data, encoding));
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/** Node's numeric `open` flags, by their string spelling. */
|
|
478
|
+
const FLAG_STRINGS: Record<string, number> = {
|
|
479
|
+
r: 0,
|
|
480
|
+
'r+': 2,
|
|
481
|
+
rs: 0,
|
|
482
|
+
sr: 0,
|
|
483
|
+
w: 1 | 64 | 512,
|
|
484
|
+
wx: 1 | 64 | 512 | 128,
|
|
485
|
+
xw: 1 | 64 | 512 | 128,
|
|
486
|
+
'w+': 2 | 64 | 512,
|
|
487
|
+
'wx+': 2 | 64 | 512 | 128,
|
|
488
|
+
'xw+': 2 | 64 | 512 | 128,
|
|
489
|
+
a: 1 | 64 | 1024,
|
|
490
|
+
ax: 1 | 64 | 1024 | 128,
|
|
491
|
+
xa: 1 | 64 | 1024 | 128,
|
|
492
|
+
'a+': 2 | 64 | 1024,
|
|
493
|
+
'ax+': 2 | 64 | 1024 | 128,
|
|
494
|
+
'xa+': 2 | 64 | 1024 | 128,
|
|
495
|
+
as: 1 | 64 | 1024,
|
|
496
|
+
sa: 1 | 64 | 1024,
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
/** A `flag` option as the number the native side decodes. */
|
|
500
|
+
export function flagFrom(
|
|
501
|
+
flag: string | number | undefined,
|
|
502
|
+
fallback: number,
|
|
503
|
+
): number {
|
|
504
|
+
if (flag === undefined) {
|
|
505
|
+
return fallback;
|
|
506
|
+
}
|
|
507
|
+
if (typeof flag === 'number') {
|
|
508
|
+
return flag;
|
|
509
|
+
}
|
|
510
|
+
const value = FLAG_STRINGS[flag];
|
|
511
|
+
if (value === undefined) {
|
|
512
|
+
throw nodeError(
|
|
513
|
+
'ERR_INVALID_ARG_VALUE',
|
|
514
|
+
`The argument 'flag' is invalid. Received '${flag}'`,
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
return value;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/** A `mode` option as a number. A string is octal, as in Node. */
|
|
521
|
+
export function modeFrom(
|
|
522
|
+
mode: number | string | undefined,
|
|
523
|
+
fallback = 0o666,
|
|
524
|
+
): number {
|
|
525
|
+
if (mode === undefined) {
|
|
526
|
+
return fallback;
|
|
527
|
+
}
|
|
528
|
+
if (typeof mode === 'number') {
|
|
529
|
+
return mode;
|
|
530
|
+
}
|
|
531
|
+
const parsed = Number.parseInt(mode, 8);
|
|
532
|
+
if (Number.isNaN(parsed)) {
|
|
533
|
+
throw nodeError(
|
|
534
|
+
'ERR_INVALID_ARG_VALUE',
|
|
535
|
+
`The argument 'mode' is invalid. Received '${mode}'`,
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
return parsed;
|
|
539
|
+
}
|