@scriptc/runtime 0.0.23 → 0.0.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.
@@ -0,0 +1,491 @@
1
+ /* The fs/promises FileHandle slice. Kept in its own gated translation unit
2
+ * so programs that never open a handle retain the base runtime's size class.
3
+ * Operations settle through scr_async.c's ordinary promise helpers: a pending
4
+ * synchronous exception becomes a rejection with the same Error payload. */
5
+ #include "scr_runtime.h"
6
+
7
+ #include <errno.h>
8
+ #include <fcntl.h>
9
+ #include <math.h>
10
+ #include <stdio.h>
11
+ #include <stdlib.h>
12
+ #include <string.h>
13
+ #include <sys/stat.h>
14
+
15
+ #ifdef _WIN32
16
+ #include <io.h>
17
+ #include <windows.h>
18
+ #else
19
+ #include <unistd.h>
20
+ #endif
21
+
22
+ #ifndef O_BINARY
23
+ #define O_BINARY 0
24
+ #endif
25
+ #ifndef O_SYNC
26
+ #define O_SYNC 0
27
+ #endif
28
+
29
+ /* One shared mutable descriptor slot gives aliases Node's close/fd behavior.
30
+ * The last native reference closes a still-open descriptor; explicit close
31
+ * reports errors through its promise wrapper instead. */
32
+ struct ScrFileHandle {
33
+ size_t rc;
34
+ int fd;
35
+ };
36
+
37
+ /* ScrStats is opaque at the public runtime boundary. FileHandle's fstat
38
+ * snapshot completes the same layout privately in this translation unit. */
39
+ struct ScrStats {
40
+ size_t rc;
41
+ bool is_file;
42
+ bool is_dir;
43
+ bool is_symlink;
44
+ double size;
45
+ double blocks;
46
+ double nlink;
47
+ double atime_ms;
48
+ double mtime_ms;
49
+ };
50
+
51
+ #ifdef _WIN32
52
+ /* Keep the FILETIME conversion byte-for-byte with scr_lib.c's path-stat arm:
53
+ * libuv splits the 100ns count into Unix seconds and nanoseconds before Node
54
+ * combines it into the public millisecond value. */
55
+ static double scr_file_handle_filetime_ms(FILETIME ft) {
56
+ ULARGE_INTEGER raw;
57
+ raw.LowPart = ft.dwLowDateTime;
58
+ raw.HighPart = ft.dwHighDateTime;
59
+ int64_t ticks = (int64_t)raw.QuadPart - INT64_C(116444736000000000);
60
+ int64_t sec = ticks / INT64_C(10000000);
61
+ int64_t rem = ticks % INT64_C(10000000);
62
+ if (rem < 0) {
63
+ sec--;
64
+ rem += INT64_C(10000000);
65
+ }
66
+ return (double)sec * 1000.0 + (double)rem / 10000.0;
67
+ }
68
+ #endif
69
+
70
+ typedef struct {
71
+ char *data;
72
+ size_t len;
73
+ size_t cap;
74
+ } ScrFileHandleBuf;
75
+
76
+ static void scr_file_handle_buf_grow(ScrFileHandleBuf *b, size_t need) {
77
+ if (need <= b->cap - b->len) return;
78
+ if (need > SIZE_MAX - b->len) scr_trap("scriptc: out of memory\n");
79
+ size_t want = b->len + need;
80
+ size_t cap = b->cap ? b->cap : 64;
81
+ while (cap < want) {
82
+ if (cap > SIZE_MAX / 2) {
83
+ cap = want;
84
+ break;
85
+ }
86
+ cap *= 2;
87
+ }
88
+ char *data = realloc(b->data, cap);
89
+ if (!data) scr_trap("scriptc: out of memory\n");
90
+ b->data = data;
91
+ b->cap = cap;
92
+ }
93
+
94
+ static void scr_file_handle_buf_bytes(ScrFileHandleBuf *b, const char *data,
95
+ size_t len) {
96
+ scr_file_handle_buf_grow(b, len);
97
+ memcpy(b->data + b->len, data, len);
98
+ b->len += len;
99
+ }
100
+
101
+ static void scr_file_handle_buf_cstr(ScrFileHandleBuf *b, const char *data) {
102
+ scr_file_handle_buf_bytes(b, data, strlen(data));
103
+ }
104
+
105
+ static void scr_file_handle_buf_char(ScrFileHandleBuf *b, char c) {
106
+ scr_file_handle_buf_bytes(b, &c, 1);
107
+ }
108
+
109
+ static bool scr_file_handle_has_dollar_brace(const ScrStr *value) {
110
+ for (size_t i = 0; i + 1 < value->len; i++) {
111
+ if (value->data[i] == '$' && value->data[i + 1] == '{') return true;
112
+ }
113
+ return false;
114
+ }
115
+
116
+ static char scr_file_handle_inspect_quote(const ScrStr *value) {
117
+ if (!memchr(value->data, '\'', value->len)) return '\'';
118
+ if (!memchr(value->data, '"', value->len)) return '"';
119
+ if (!memchr(value->data, '`', value->len) &&
120
+ !scr_file_handle_has_dollar_brace(value)) {
121
+ return '`';
122
+ }
123
+ return '\'';
124
+ }
125
+
126
+ /* Node's ERR_INVALID_ARG_VALUE renderer runs util.inspect on the value and
127
+ * truncates that rendered text to 128 UTF-16 units before appending "...".
128
+ * FileHandle flags and paths use the static ScrStr representation, so this
129
+ * scalar slice is all this translation unit needs from the optional inspect
130
+ * runtime. */
131
+ static ScrStr *scr_file_handle_inspect(const ScrStr *value) {
132
+ ScrFileHandleBuf b = {0};
133
+ char quote = scr_file_handle_inspect_quote(value);
134
+ scr_file_handle_buf_char(&b, quote);
135
+ for (size_t i = 0; i < value->len; i++) {
136
+ unsigned char c = (unsigned char)value->data[i];
137
+ if (c == '\\' || (c == '\'' && quote == '\'')) {
138
+ scr_file_handle_buf_char(&b, '\\');
139
+ scr_file_handle_buf_char(&b, (char)c);
140
+ } else if (c == '\b') {
141
+ scr_file_handle_buf_cstr(&b, "\\b");
142
+ } else if (c == '\t') {
143
+ scr_file_handle_buf_cstr(&b, "\\t");
144
+ } else if (c == '\n') {
145
+ scr_file_handle_buf_cstr(&b, "\\n");
146
+ } else if (c == '\f') {
147
+ scr_file_handle_buf_cstr(&b, "\\f");
148
+ } else if (c == '\r') {
149
+ scr_file_handle_buf_cstr(&b, "\\r");
150
+ } else if (c < 0x20 || c == 0x7f) {
151
+ char escaped[5];
152
+ int len = snprintf(escaped, sizeof escaped, "\\x%02X", c);
153
+ scr_file_handle_buf_bytes(&b, escaped, (size_t)len);
154
+ } else if (c == 0xc2 && i + 1 < value->len &&
155
+ (unsigned char)value->data[i + 1] >= 0x80 &&
156
+ (unsigned char)value->data[i + 1] <= 0x9f) {
157
+ char escaped[5];
158
+ int len = snprintf(escaped, sizeof escaped, "\\x%02X",
159
+ (unsigned char)value->data[++i]);
160
+ scr_file_handle_buf_bytes(&b, escaped, (size_t)len);
161
+ } else {
162
+ scr_file_handle_buf_char(&b, (char)c);
163
+ }
164
+ }
165
+ scr_file_handle_buf_char(&b, quote);
166
+ ScrStr *full = scr_str_new(b.data ? b.data : "", b.len);
167
+ free(b.data);
168
+ if (scr_str_utf16_len(full) <= 128) return full;
169
+ ScrStr *head = scr_str_slice(full, 0, 128);
170
+ scr_str_release(full);
171
+ ScrFileHandleBuf truncated = {0};
172
+ scr_file_handle_buf_bytes(&truncated, head->data, head->len);
173
+ scr_file_handle_buf_cstr(&truncated, "...");
174
+ scr_str_release(head);
175
+ ScrStr *out = scr_str_new(truncated.data, truncated.len);
176
+ free(truncated.data);
177
+ return out;
178
+ }
179
+
180
+ static void scr_file_handle_arg_value_error(const char *prefix,
181
+ const ScrStr *value) {
182
+ ScrStr *inspected = scr_file_handle_inspect(value);
183
+ ScrFileHandleBuf msg = {0};
184
+ scr_file_handle_buf_cstr(&msg, prefix);
185
+ scr_file_handle_buf_bytes(&msg, inspected->data, inspected->len);
186
+ scr_throw_error_msg_code(SCR_ERR_TYPE, msg.data, msg.len,
187
+ "ERR_INVALID_ARG_VALUE");
188
+ free(msg.data);
189
+ scr_str_release(inspected);
190
+ }
191
+
192
+ static bool scr_file_handle_flag_eq(const ScrStr *flags, const char *want) {
193
+ size_t len = strlen(want);
194
+ return flags->len == len && memcmp(flags->data, want, len) == 0;
195
+ }
196
+
197
+ static void scr_file_handle_invalid_flags(const ScrStr *flags) {
198
+ scr_file_handle_arg_value_error(
199
+ "The argument 'flags' is invalid. Received ", flags);
200
+ }
201
+
202
+ static bool scr_file_handle_path_valid(const ScrStr *path) {
203
+ if (!memchr(path->data, 0, path->len)) return true;
204
+ scr_file_handle_arg_value_error(
205
+ "The argument 'path' must be a string, Uint8Array, or URL without null bytes. Received ",
206
+ path);
207
+ return false;
208
+ }
209
+
210
+ static int scr_file_handle_open_flags(ScrStr *flags) {
211
+ int of;
212
+ if (scr_file_handle_flag_eq(flags, "r")) of = O_RDONLY;
213
+ else if (scr_file_handle_flag_eq(flags, "rs") || scr_file_handle_flag_eq(flags, "sr")) of = O_RDONLY | O_SYNC;
214
+ else if (scr_file_handle_flag_eq(flags, "r+")) of = O_RDWR;
215
+ else if (scr_file_handle_flag_eq(flags, "rs+") || scr_file_handle_flag_eq(flags, "sr+")) of = O_RDWR | O_SYNC;
216
+ else if (scr_file_handle_flag_eq(flags, "w")) of = O_TRUNC | O_CREAT | O_WRONLY;
217
+ else if (scr_file_handle_flag_eq(flags, "wx") || scr_file_handle_flag_eq(flags, "xw")) of = O_TRUNC | O_CREAT | O_WRONLY | O_EXCL;
218
+ else if (scr_file_handle_flag_eq(flags, "w+")) of = O_TRUNC | O_CREAT | O_RDWR;
219
+ else if (scr_file_handle_flag_eq(flags, "wx+") || scr_file_handle_flag_eq(flags, "xw+")) of = O_TRUNC | O_CREAT | O_RDWR | O_EXCL;
220
+ else if (scr_file_handle_flag_eq(flags, "a")) of = O_APPEND | O_CREAT | O_WRONLY;
221
+ else if (scr_file_handle_flag_eq(flags, "ax") || scr_file_handle_flag_eq(flags, "xa")) of = O_APPEND | O_CREAT | O_WRONLY | O_EXCL;
222
+ else if (scr_file_handle_flag_eq(flags, "as") || scr_file_handle_flag_eq(flags, "sa")) of = O_APPEND | O_CREAT | O_WRONLY | O_SYNC;
223
+ else if (scr_file_handle_flag_eq(flags, "a+")) of = O_APPEND | O_CREAT | O_RDWR;
224
+ else if (scr_file_handle_flag_eq(flags, "ax+") || scr_file_handle_flag_eq(flags, "xa+")) of = O_APPEND | O_CREAT | O_RDWR | O_EXCL;
225
+ else if (scr_file_handle_flag_eq(flags, "as+") || scr_file_handle_flag_eq(flags, "sa+")) of = O_APPEND | O_CREAT | O_RDWR | O_SYNC;
226
+ else {
227
+ scr_file_handle_invalid_flags(flags);
228
+ return -1;
229
+ }
230
+ return of;
231
+ }
232
+
233
+ static bool scr_file_handle_mode_valid(double mode) {
234
+ char msg[160];
235
+ char recv[48];
236
+ scr_num_received(mode, recv);
237
+ if (!(isfinite(mode) && trunc(mode) == mode)) {
238
+ int len = snprintf(msg, sizeof msg,
239
+ "The value of \"mode\" is out of range. It must be an integer. Received %s",
240
+ recv);
241
+ scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
242
+ return false;
243
+ }
244
+ if (mode < 0 || mode > 4294967295.0) {
245
+ int len = snprintf(msg, sizeof msg,
246
+ "The value of \"mode\" is out of range. It must be >= 0 && <= 4294967295. Received %s",
247
+ recv);
248
+ scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
249
+ return false;
250
+ }
251
+ return true;
252
+ }
253
+
254
+ ScrFileHandle *scr_file_handle_open(ScrStr *path, ScrStr *flags, double mode) {
255
+ if (!scr_file_handle_path_valid(path)) return NULL;
256
+ int of = scr_file_handle_open_flags(flags);
257
+ if (of < 0) return NULL;
258
+ if (!scr_file_handle_mode_valid(mode)) return NULL;
259
+ int fd = open(path->data, of | O_BINARY, (mode_t)mode);
260
+ if (fd < 0) {
261
+ scr_fs_throw(errno, "open", path);
262
+ return NULL;
263
+ }
264
+ ScrFileHandle *h = malloc(sizeof(ScrFileHandle));
265
+ if (!h) scr_trap("scriptc: out of memory\n");
266
+ h->rc = 1;
267
+ h->fd = fd;
268
+ return h;
269
+ }
270
+
271
+ ScrFileHandle *scr_file_handle_retain(ScrFileHandle *h) {
272
+ if (h->rc != SIZE_MAX) h->rc++;
273
+ return h;
274
+ }
275
+
276
+ void scr_file_handle_release(ScrFileHandle *h) {
277
+ if (!h || h->rc == SIZE_MAX) return;
278
+ if (--h->rc != 0) return;
279
+ if (h->fd >= 0) (void)close(h->fd);
280
+ free(h);
281
+ }
282
+
283
+ void *scr_file_handle_retain_v(void *p) { return scr_file_handle_retain(p); }
284
+ void scr_file_handle_release_v(void *p) { scr_file_handle_release(p); }
285
+
286
+ double scr_file_handle_fd(ScrFileHandle *h) { return (double)h->fd; }
287
+
288
+ static bool scr_file_handle_require_open(ScrFileHandle *h) {
289
+ if (h->fd >= 0) return true;
290
+ static const char msg[] = "file closed";
291
+ scr_throw_error_msg_code(SCR_ERR_ERROR, msg, sizeof msg - 1, "EBADF");
292
+ return false;
293
+ }
294
+
295
+ void scr_file_handle_close(ScrFileHandle *h) {
296
+ if (h->fd < 0) return; /* Node's FileHandle.close() is idempotent. */
297
+ int fd = h->fd;
298
+ h->fd = -1;
299
+ scr_fs_close((double)fd);
300
+ }
301
+
302
+ double scr_file_handle_read(ScrFileHandle *h, ScrBytes *buf, double offset,
303
+ double length, double position,
304
+ bool length_default) {
305
+ if (!scr_file_handle_require_open(h)) return 0;
306
+ if (buf->len == 0) {
307
+ /* FileHandle.read's empty-buffer ladder differs from readSync's generic
308
+ * window check: validate offset intrinsically first, then a zero request
309
+ * succeeds without consulting position/the descriptor; every other
310
+ * request rejects with Node's dedicated invalid-buffer error. Reusing a
311
+ * zero-length read performs exactly that offset-only validation. */
312
+ double checked = scr_fs_read_sync((double)h->fd, buf, offset, 0, position);
313
+ if (scr_exc_pending()) return 0;
314
+ if ((!length_default && length >= 0 && length < 1) ||
315
+ (length_default && offset == 0)) {
316
+ return checked;
317
+ }
318
+ static const char msg[] =
319
+ "The argument 'buffer' is empty and cannot be written. Received <Buffer >";
320
+ scr_throw_error_msg_code(SCR_ERR_TYPE, msg, sizeof msg - 1,
321
+ "ERR_INVALID_ARG_VALUE");
322
+ return 0;
323
+ }
324
+ if (length_default && isfinite(offset) && trunc(offset) == offset &&
325
+ offset >= 0 && offset <= (double)buf->len) {
326
+ length = (double)buf->len - offset;
327
+ }
328
+ return scr_fs_read_sync((double)h->fd, buf, offset, length, position);
329
+ }
330
+
331
+ double scr_file_handle_write_bytes(ScrFileHandle *h, ScrBytes *buf,
332
+ double offset, double length,
333
+ double position, bool length_default) {
334
+ if (!scr_file_handle_require_open(h)) return 0;
335
+ /* Node completes an empty Buffer write before validating offset/length/
336
+ * position or testing whether the descriptor is writable. */
337
+ if (buf->len == 0) return 0;
338
+ if (length_default && isfinite(offset) && trunc(offset) == offset &&
339
+ offset >= 0 && offset <= (double)buf->len) {
340
+ length = (double)buf->len - offset;
341
+ }
342
+ return scr_fs_write_sync((double)h->fd, buf, offset, length, position);
343
+ }
344
+
345
+ double scr_file_handle_write_str(ScrFileHandle *h, ScrStr *data,
346
+ double position, ScrStr *encoding) {
347
+ if (!scr_file_handle_require_open(h)) return 0;
348
+ return scr_fs_write_str_sync((double)h->fd, data, position, encoding);
349
+ }
350
+
351
+ ScrStr *scr_file_handle_read_file(ScrFileHandle *h) {
352
+ if (!scr_file_handle_require_open(h)) return NULL;
353
+ return scr_fs_read_fd((double)h->fd);
354
+ }
355
+
356
+ ScrBytes *scr_file_handle_read_file_bytes(ScrFileHandle *h) {
357
+ if (!scr_file_handle_require_open(h)) return NULL;
358
+ return scr_fs_read_fd_bytes((double)h->fd);
359
+ }
360
+
361
+ static void scr_file_handle_write_all(ScrFileHandle *h, const void *data,
362
+ size_t length) {
363
+ if (!scr_file_handle_require_open(h)) return;
364
+ ScrBytes bytes = {SIZE_MAX, length, SCR_BYTES_U8, (uint8_t *)data, NULL};
365
+ size_t at = 0;
366
+ while (at < length) {
367
+ double count = scr_fs_write_sync((double)h->fd, &bytes, (double)at,
368
+ (double)(length - at), -1);
369
+ if (scr_exc_pending()) return;
370
+ if (count <= 0) {
371
+ static const char msg[] = "EIO: i/o error, write";
372
+ scr_throw_error_msg_code(SCR_ERR_ERROR, msg, sizeof msg - 1, "EIO");
373
+ return;
374
+ }
375
+ at += (size_t)count;
376
+ }
377
+ }
378
+
379
+ void scr_file_handle_write_file(ScrFileHandle *h, ScrStr *data) {
380
+ scr_file_handle_write_all(h, data->data, data->len);
381
+ }
382
+
383
+ void scr_file_handle_write_file_bytes(ScrFileHandle *h, ScrBytes *data) {
384
+ scr_file_handle_write_all(h, data->data,
385
+ data->len * scr_bytes_elem_size(data->elem));
386
+ }
387
+
388
+ ScrStats *scr_file_handle_stat(ScrFileHandle *h) {
389
+ if (!scr_file_handle_require_open(h)) return NULL;
390
+ struct stat st;
391
+ if (fstat(h->fd, &st) != 0) {
392
+ int err = errno;
393
+ const char *name = err == EBADF ? "EBADF" : err == EIO ? "EIO" : "EUNKNOWN";
394
+ const char *text = err == EBADF ? "bad file descriptor" :
395
+ err == EIO ? "i/o error" : strerror(err);
396
+ char msg[256];
397
+ int len = snprintf(msg, sizeof msg, "%s: %s, fstat", name, text);
398
+ scr_throw_error_msg_code(SCR_ERR_ERROR, msg, (size_t)len, name);
399
+ return NULL;
400
+ }
401
+ ScrStats *out = malloc(sizeof(ScrStats));
402
+ if (!out) scr_trap("scriptc: out of memory\n");
403
+ out->rc = 1;
404
+ out->is_file = S_ISREG(st.st_mode);
405
+ out->is_dir = S_ISDIR(st.st_mode);
406
+ out->is_symlink = false; /* fstat follows the open descriptor. */
407
+ out->size = (double)st.st_size;
408
+ #if defined(_WIN32)
409
+ out->blocks = st.st_size <= 0 ? 0.0 : (double)(((uint64_t)st.st_size + 511) >> 9);
410
+ out->nlink = (double)st.st_nlink;
411
+ out->atime_ms = (double)st.st_atime * 1000.0;
412
+ out->mtime_ms = (double)st.st_mtime * 1000.0;
413
+ HANDLE os_handle = (HANDLE)_get_osfhandle(h->fd);
414
+ if (os_handle != INVALID_HANDLE_VALUE) {
415
+ BY_HANDLE_FILE_INFORMATION basic;
416
+ if (GetFileInformationByHandle(os_handle, &basic)) {
417
+ out->nlink = (double)basic.nNumberOfLinks;
418
+ out->atime_ms = scr_file_handle_filetime_ms(basic.ftLastAccessTime);
419
+ out->mtime_ms = scr_file_handle_filetime_ms(basic.ftLastWriteTime);
420
+ }
421
+ FILE_STANDARD_INFO standard;
422
+ if (GetFileInformationByHandleEx(
423
+ os_handle, FileStandardInfo, &standard, sizeof standard)) {
424
+ out->blocks = (double)((uint64_t)standard.AllocationSize.QuadPart >> 9);
425
+ out->nlink = (double)standard.NumberOfLinks;
426
+ }
427
+ }
428
+ #elif defined(__APPLE__)
429
+ out->blocks = (double)st.st_blocks;
430
+ out->nlink = (double)st.st_nlink;
431
+ out->atime_ms = (double)st.st_atimespec.tv_sec * 1000.0 +
432
+ (double)st.st_atimespec.tv_nsec / 1e6;
433
+ out->mtime_ms = (double)st.st_mtimespec.tv_sec * 1000.0 +
434
+ (double)st.st_mtimespec.tv_nsec / 1e6;
435
+ #else
436
+ out->blocks = (double)st.st_blocks;
437
+ out->nlink = (double)st.st_nlink;
438
+ out->atime_ms = (double)st.st_atim.tv_sec * 1000.0 +
439
+ (double)st.st_atim.tv_nsec / 1e6;
440
+ out->mtime_ms = (double)st.st_mtim.tv_sec * 1000.0 +
441
+ (double)st.st_mtim.tv_nsec / 1e6;
442
+ #endif
443
+ return out;
444
+ }
445
+
446
+ ScrPromise *scr_fsp_open(ScrStr *path, ScrStr *flags, double mode) {
447
+ ScrFileHandle *h = scr_file_handle_open(path, flags, mode);
448
+ return scr_promise_settled_ref(h, &scr_file_handle_retain_v,
449
+ &scr_file_handle_release_v, NULL);
450
+ }
451
+
452
+ ScrPromise *scr_file_handle_close_promise(ScrFileHandle *h) {
453
+ scr_file_handle_close(h);
454
+ return scr_promise_settled_void();
455
+ }
456
+
457
+ ScrPromise *scr_file_handle_read_file_promise(ScrFileHandle *h,
458
+ ScrStr *encoding) {
459
+ (void)encoding;
460
+ return scr_promise_settled_str(scr_file_handle_read_file(h));
461
+ }
462
+
463
+ ScrPromise *scr_file_handle_read_file_bytes_promise(ScrFileHandle *h,
464
+ ScrStr *encoding) {
465
+ (void)encoding; /* evaluates the explicit undefined/null default in order */
466
+ ScrBytes *data = scr_file_handle_read_file_bytes(h);
467
+ return scr_promise_settled_ref(data, &scr_bytes_retain_v,
468
+ &scr_bytes_release_v, NULL);
469
+ }
470
+
471
+ ScrPromise *scr_file_handle_write_file_promise(ScrFileHandle *h,
472
+ ScrStr *data,
473
+ ScrStr *encoding) {
474
+ (void)encoding; /* frontend admits utf8 only; evaluation is observable */
475
+ scr_file_handle_write_file(h, data);
476
+ return scr_promise_settled_void();
477
+ }
478
+
479
+ ScrPromise *scr_file_handle_write_file_bytes_promise(ScrFileHandle *h,
480
+ ScrBytes *data,
481
+ ScrStr *encoding) {
482
+ (void)encoding;
483
+ scr_file_handle_write_file_bytes(h, data);
484
+ return scr_promise_settled_void();
485
+ }
486
+
487
+ ScrPromise *scr_file_handle_stat_promise(ScrFileHandle *h) {
488
+ ScrStats *st = scr_file_handle_stat(h);
489
+ return scr_promise_settled_ref(st, &scr_stats_retain_v,
490
+ &scr_stats_release_v, NULL);
491
+ }
package/src/scr_island.c CHANGED
@@ -2978,6 +2978,9 @@ static JSValue isl_host_fs(JSContext *ctx, JSValueConst this_val, int argc,
2978
2978
  JS_SetPropertyUint32(ctx, arr, 2, JS_NewBool(ctx, scr_stats_is_symlink(st)));
2979
2979
  JS_SetPropertyUint32(ctx, arr, 3, JS_NewFloat64(ctx, scr_stats_size(st)));
2980
2980
  JS_SetPropertyUint32(ctx, arr, 4, JS_NewFloat64(ctx, scr_stats_mtime_ms(st)));
2981
+ JS_SetPropertyUint32(ctx, arr, 5, JS_NewFloat64(ctx, scr_stats_blocks(st)));
2982
+ JS_SetPropertyUint32(ctx, arr, 6, JS_NewFloat64(ctx, scr_stats_nlink(st)));
2983
+ JS_SetPropertyUint32(ctx, arr, 7, JS_NewFloat64(ctx, scr_stats_atime_ms(st)));
2981
2984
  scr_stats_release(st);
2982
2985
  ret = arr;
2983
2986
  }
@@ -3996,6 +3999,10 @@ static const char isl_modules_bootstrap[] =
3996
3999
  " this._l = row[2];\n"
3997
4000
  " this.size = row[3];\n"
3998
4001
  " this.mtimeMs = row[4];\n"
4002
+ " this.blocks = row[5];\n"
4003
+ " this.nlink = row[6];\n"
4004
+ " this.atimeMs = row[7];\n"
4005
+ " this.atime = new Date(row[7]);\n"
3999
4006
  " this.mtime = new Date(row[4]);\n"
4000
4007
  " this.mode = (this._f ? constants.S_IFREG : this._d ? constants.S_IFDIR : this._l ? (constants.S_IFLNK || 0) : 0);\n"
4001
4008
  " }\n"
@@ -4194,6 +4201,9 @@ static const char isl_modules_bootstrap[] =
4194
4201
  " readSync: () => {\n"
4195
4202
  " throw new Error(\"fs.readSync is not available in the scriptc island (whole-file reads/writes only)\");\n"
4196
4203
  " },\n"
4204
+ " writeSync: () => {\n"
4205
+ " throw new Error(\"fs.writeSync is not available in the scriptc island (whole-file reads/writes only)\");\n"
4206
+ " },\n"
4197
4207
  " read: () => {\n"
4198
4208
  " throw new Error(\"fs.read is not available in the scriptc island (whole-file reads/writes only)\");\n"
4199
4209
  " },\n"
package/src/scr_json.c CHANGED
@@ -94,6 +94,10 @@ static void scr_jb_write(ScrJsonBuf *b, const char *s, size_t n) {
94
94
  b->len += n;
95
95
  }
96
96
 
97
+ void scr_jb_put_str(ScrJsonBuf *b, const ScrStr *s) {
98
+ scr_jb_write(b, s->data, s->len);
99
+ }
100
+
97
101
  void scr_jb_puts(ScrJsonBuf *b, const char *s) { scr_jb_write(b, s, strlen(s)); }
98
102
 
99
103
  void scr_jb_put_f64(ScrJsonBuf *b, double v) {
@@ -986,10 +990,18 @@ void scr_throw_arg_type(const ScrStr *argname, const ScrStr *expected, const Scr
986
990
  void scr_dyn_arg_type_fail(const char *argname, const char *expected, const ScrDyn *got) {
987
991
  char detail[64];
988
992
  const char *d = scr_dyn_specific_type(got, detail, sizeof detail);
989
- char msg[224];
990
- int len = snprintf(msg, sizeof msg,
991
- "The \"%s\" argument must be %s. Received %s", argname, expected, d);
992
- scr_throw_error_msg_code(SCR_ERR_TYPE, msg, (size_t)len, "ERR_INVALID_ARG_TYPE");
993
+ ScrJsonBuf b;
994
+ scr_jb_init(&b);
995
+ scr_jb_puts(&b, "The \"");
996
+ scr_jb_puts(&b, argname);
997
+ scr_jb_puts(&b, "\" argument must be ");
998
+ scr_jb_puts(&b, expected);
999
+ scr_jb_puts(&b, ". Received ");
1000
+ scr_jb_puts(&b, d);
1001
+ ScrStr *msg = scr_jb_finish(&b);
1002
+ scr_throw_error_msg_code(SCR_ERR_TYPE, msg->data, msg->len,
1003
+ "ERR_INVALID_ARG_TYPE");
1004
+ scr_str_release(msg);
993
1005
  }
994
1006
 
995
1007
  /* The property flavor of the same ladder — Node renders option-bag
@@ -999,10 +1011,18 @@ void scr_dyn_arg_type_fail(const char *argname, const char *expected, const ScrD
999
1011
  void scr_dyn_prop_type_fail(const char *name, const char *expected, const ScrDyn *got) {
1000
1012
  char detail[64];
1001
1013
  const char *d = scr_dyn_specific_type(got, detail, sizeof detail);
1002
- char msg[224];
1003
- int len = snprintf(msg, sizeof msg,
1004
- "The \"%s\" property must be %s. Received %s", name, expected, d);
1005
- scr_throw_error_msg_code(SCR_ERR_TYPE, msg, (size_t)len, "ERR_INVALID_ARG_TYPE");
1014
+ ScrJsonBuf b;
1015
+ scr_jb_init(&b);
1016
+ scr_jb_puts(&b, "The \"");
1017
+ scr_jb_puts(&b, name);
1018
+ scr_jb_puts(&b, "\" property must be ");
1019
+ scr_jb_puts(&b, expected);
1020
+ scr_jb_puts(&b, ". Received ");
1021
+ scr_jb_puts(&b, d);
1022
+ ScrStr *msg = scr_jb_finish(&b);
1023
+ scr_throw_error_msg_code(SCR_ERR_TYPE, msg->data, msg->len,
1024
+ "ERR_INVALID_ARG_TYPE");
1025
+ scr_str_release(msg);
1006
1026
  }
1007
1027
 
1008
1028
  /* The compiler-resolved property-typed throw (error.propTypeThrow —
@@ -1051,11 +1071,19 @@ const char *scr_dyn_inspect_lite(const ScrDyn *v, char *buf, size_t cap) {
1051
1071
  void scr_dyn_arg_value_fail(const char *name, const char *reason, const ScrDyn *got) {
1052
1072
  char insp[64];
1053
1073
  const char *d = scr_dyn_inspect_lite(got, insp, sizeof insp);
1054
- char msg[256];
1055
- int len = snprintf(msg, sizeof msg, "The %s '%s' %s. Received %s",
1056
- strchr(name, '.') != NULL ? "property" : "argument", name,
1057
- reason != NULL ? reason : "is invalid", d);
1058
- scr_throw_error_msg_code(SCR_ERR_TYPE, msg, (size_t)len, "ERR_INVALID_ARG_VALUE");
1074
+ ScrJsonBuf b;
1075
+ scr_jb_init(&b);
1076
+ scr_jb_puts(&b, "The ");
1077
+ scr_jb_puts(&b, strchr(name, '.') != NULL ? "property '" : "argument '");
1078
+ scr_jb_puts(&b, name);
1079
+ scr_jb_puts(&b, "' ");
1080
+ scr_jb_puts(&b, reason != NULL ? reason : "is invalid");
1081
+ scr_jb_puts(&b, ". Received ");
1082
+ scr_jb_puts(&b, d);
1083
+ ScrStr *msg = scr_jb_finish(&b);
1084
+ scr_throw_error_msg_code(SCR_ERR_TYPE, msg->data, msg->len,
1085
+ "ERR_INVALID_ARG_VALUE");
1086
+ scr_str_release(msg);
1059
1087
  }
1060
1088
 
1061
1089
  /* The deferred JS lowering fence, thrown from a ladder's post-validation
@@ -1362,7 +1390,8 @@ ScrStr *scr_dyn_typeof(const ScrDyn *d) {
1362
1390
  /* ── JSON.stringify over a dyn value (util.format's %j) ───────────────
1363
1391
  * The RUNTIME walk the type-directed serializers deliberately avoid for
1364
1392
  * static values — a dyn value has no static type, so the checked-dynamic tree's own kinds
1365
- * drive it, JS-exactly: insertion-ordered objects with undefined/function
1393
+ * drive it, JS-exactly: objects in OrdinaryOwnPropertyKeys order (array
1394
+ * indices ascending, then other strings in insertion order) with undefined/function
1366
1395
  * members OMITTED, arrays rendering those as null, Buffer's toJSON shape
1367
1396
  * ({"type":"Buffer","data":[...]}), shortest-roundtrip numbers, escaped
1368
1397
  * strings. HANDLE values fence loudly (Node walks own enumerable props
@@ -1390,23 +1419,26 @@ static bool scr_dyn_json_write(ScrJsonBuf *b, const ScrDyn *d) {
1390
1419
  case SCR_DYN_OBJ: {
1391
1420
  scr_jb_putc(b, '{');
1392
1421
  bool first = true;
1393
- for (size_t i = 0; i < d->v.obj.len; i++) {
1422
+ ScrDyn *entries = scr_dyn_obj_entries(d);
1423
+ for (size_t i = 0; i < entries->v.arr.len; i++) {
1424
+ const ScrDyn *pair = entries->v.arr.items[i];
1425
+ const ScrDyn *key = pair->v.arr.items[0];
1426
+ const ScrDyn *value = pair->v.arr.items[1];
1394
1427
  ScrJsonBuf probe;
1395
1428
  scr_jb_init(&probe);
1396
- if (!scr_dyn_json_write(&probe, d->v.obj.entries[i].value)) {
1429
+ if (!scr_dyn_json_write(&probe, value)) {
1397
1430
  scr_jb_dispose(&probe);
1398
1431
  continue; /* undefined/function members drop, like Node */
1399
1432
  }
1400
1433
  if (!first) scr_jb_putc(b, ',');
1401
1434
  first = false;
1402
- ScrStr *k = scr_str_new(d->v.obj.entries[i].key, d->v.obj.entries[i].key_len);
1403
- scr_jb_put_json_str(b, k);
1404
- scr_str_release(k);
1435
+ scr_jb_put_json_str(b, key->v.str);
1405
1436
  scr_jb_putc(b, ':');
1406
1437
  ScrStr *body = scr_jb_finish(&probe);
1407
1438
  scr_jb_write(b, body->data, body->len);
1408
1439
  scr_str_release(body);
1409
1440
  }
1441
+ scr_dyn_release(entries);
1410
1442
  scr_jb_putc(b, '}');
1411
1443
  return true;
1412
1444
  }