@scriptc/runtime 0.0.21 → 0.0.22

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.
@@ -360,14 +360,17 @@ static JSValue fx_host_start(JSContext *ctx, JSValueConst this_val, int argc,
360
360
  curl_easy_setopt(e, CURLOPT_ACCEPT_ENCODING, ""); /* gzip in, transparent out */
361
361
  curl_easy_setopt(e, CURLOPT_PROTOCOLS_STR, "http,https");
362
362
  curl_easy_setopt(e, CURLOPT_SUPPRESS_CONNECT_HEADERS, 1L);
363
- /* Node parity: undici's global fetch IGNORES http_proxy/https_proxy/
364
- * all_proxy environment variables unless NODE_USE_ENV_PROXY=1 opts in
365
- * (Node 24's EnvHttpProxyAgent). libcurl's default is the opposite
366
- * honor them so an empty CURLOPT_PROXY switches the env lookup off;
367
- * with the opt-in set, libcurl's own env handling stands in for
368
- * undici's agent (same variables, same no_proxy exclusions). */
369
- const char *env_proxy = getenv("NODE_USE_ENV_PROXY");
370
- if (env_proxy == NULL || strcmp(env_proxy, "1") != 0) {
363
+ /* Node's global opt-in and Vercel's request-local EnvProxyDispatcher both
364
+ * activate libcurl's environment proxy lookup. Otherwise explicitly turn
365
+ * it off, because libcurl enables proxy variables by default. */
366
+ JSValue envproxyv = JS_GetPropertyStr(ctx, req, "useEnvProxy");
367
+ bool use_env_proxy = JS_ToBool(ctx, envproxyv) > 0;
368
+ JS_FreeValue(ctx, envproxyv);
369
+ const char *global_env_proxy = getenv("NODE_USE_ENV_PROXY");
370
+ use_env_proxy = use_env_proxy ||
371
+ (global_env_proxy != NULL &&
372
+ strcmp(global_env_proxy, "1") == 0);
373
+ if (!use_env_proxy) {
371
374
  curl_easy_setopt(e, CURLOPT_PROXY, "");
372
375
  }
373
376
 
@@ -486,6 +489,15 @@ static const char fx_glue[] =
486
489
  " url = String(input);\n"
487
490
  " }\n"
488
491
  " init = init === undefined || init === null ? {} : init;\n"
492
+ " let useEnvProxy = g.process?.env?.NODE_USE_ENV_PROXY === '1';\n"
493
+ " const initDispatcher = init.dispatcher;\n"
494
+ " if (initDispatcher !== undefined) {\n"
495
+ " const dispatcher = initDispatcher;\n"
496
+ " const methods = ['dispatch', 'close', 'destroy', 'agents', 'getAgent', 'shouldProxy', 'parseNoProxy'];\n"
497
+ " const known = dispatcher !== null && typeof dispatcher === 'object' && dispatcher.constructor?.name === 'EnvProxyDispatcher' && methods.every((member) => typeof dispatcher[member] === 'function');\n"
498
+ " if (!known) throw new TypeError('unsupported RequestInit option: dispatcher');\n"
499
+ " useEnvProxy = true;\n"
500
+ " }\n"
489
501
  " // An explicit init.signal overrides the Request's, null included.\n"
490
502
  " if (init.signal !== undefined) {\n"
491
503
  " if (init.signal !== null && !(init.signal instanceof g.AbortSignal)) {\n"
@@ -546,7 +558,7 @@ static const char fx_glue[] =
546
558
  " const flat = [];\n"
547
559
  " for (const pair of headers) { flat.push(pair[0], pair[1]); }\n"
548
560
  " id = host.start(\n"
549
- " { url, method, headers: flat, body: body === null ? undefined : body },\n"
561
+ " { url, method, headers: flat, body: body === null ? undefined : body, useEnvProxy },\n"
550
562
  " {\n"
551
563
  " onResponse(status, statusText, raw, finalUrl, redirected) {\n"
552
564
  " resolved = true;\n"
package/src/scr_http.c CHANGED
@@ -2608,6 +2608,19 @@ static bool scr_http_client_parse_head(ScrHttpConn *conn, size_t head_len) {
2608
2608
  return false;
2609
2609
  }
2610
2610
 
2611
+ /*
2612
+ * Informational responses do not settle the request. Node emits an
2613
+ * `information` event for these and keeps parsing until the final
2614
+ * response; this runtime has no information listener surface yet, so
2615
+ * consume the head and continue. 101 is the upgrade case below.
2616
+ */
2617
+ if (status >= 100 && status < 200 && status != 101) {
2618
+ memmove(conn->buf, conn->buf + head_len, conn->len - head_len);
2619
+ conn->len -= head_len;
2620
+ scr_http_req_release(res);
2621
+ return true;
2622
+ }
2623
+
2611
2624
  /* framing: HEAD requests and 204/304 responses have NO body; chunked
2612
2625
  * wins over Content-Length; neither means EOF-delimited */
2613
2626
  bool chunked = false;
package/src/scr_inspect.c CHANGED
@@ -849,6 +849,12 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) {
849
849
  scr_throw_error(SCR_ERR_ERROR, ib_take(&out));
850
850
  return scr_str_new("", 0);
851
851
  }
852
+ case SCR_DYN_TYPED_REF: {
853
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(d);
854
+ ScrStr *out = scr_insp_dyn(materialized, recurse, depth);
855
+ scr_dyn_release(materialized);
856
+ return out;
857
+ }
852
858
  case SCR_DYN_PROMISE: {
853
859
  /* Node renders Promise { <pending> } / Promise { value } — the
854
860
  * settled-value rendering pulls in the whole inspector; fence
package/src/scr_island.c CHANGED
@@ -1635,6 +1635,25 @@ ScrJsval *scr_jsval_call(ScrJsval *f, int argc, ScrJsval **argv) {
1635
1635
  return isl_cell_new(r);
1636
1636
  }
1637
1637
 
1638
+ /* Call an already-resolved computed member with its original receiver. The
1639
+ * frontend performs GetValue before argument evaluation, then arrives here
1640
+ * after the arguments are ready; keeping callee and receiver separate avoids
1641
+ * a second getter read while preserving method `this`. */
1642
+ ScrJsval *scr_jsval_call_this(ScrJsval *f, ScrJsval *receiver, int argc,
1643
+ ScrJsval **argv) {
1644
+ isl_entry();
1645
+ JSValue stack_args[8];
1646
+ JSValue *args = argc <= 8 ? stack_args : malloc((size_t)argc * sizeof(JSValue));
1647
+ for (int i = 0; i < argc; i++) args[i] = argv[i]->v;
1648
+ JSValue r = JS_Call(isl_ctx, f->v, receiver->v, argc, args);
1649
+ if (args != stack_args) free(args);
1650
+ if (JS_IsException(r)) {
1651
+ isl_bridge_exception();
1652
+ return NULL;
1653
+ }
1654
+ return isl_cell_new(r);
1655
+ }
1656
+
1638
1657
  /* Spread application on an island callee (jsOp callSpread) — the prelude
1639
1658
  * helper's real `f(...pre, ...spread)`, so iterator protocols are the
1640
1659
  * engine's own and the guards front-run V8's exact spread-call TypeError
package/src/scr_json.c CHANGED
@@ -333,6 +333,8 @@ static ScrDyn *scr_dyn_alloc(ScrDynKind kind) {
333
333
  d->v.arr.len = 0; /* cap/items preserved from the node's last life */
334
334
  } else if (kind == SCR_DYN_OBJ) {
335
335
  d->v.obj.len = 0; /* cap/entries preserved */
336
+ d->v.obj.source_identity = NULL;
337
+ d->v.obj.source_access = NULL;
336
338
  } else {
337
339
  memset(&d->v, 0, sizeof d->v);
338
340
  }
@@ -369,6 +371,9 @@ void scr_dyn_release(ScrDyn *d) {
369
371
  free(d->v.obj.entries[i].key);
370
372
  scr_dyn_release(d->v.obj.entries[i].value);
371
373
  }
374
+ if (d->v.obj.source_identity) {
375
+ d->v.obj.source_access(d->v.obj.source_identity, false);
376
+ }
372
377
  break;
373
378
  case SCR_DYN_FUNC:
374
379
  scr_closure_release(d->v.fn.clo); /* sig/name are static literals */
@@ -387,6 +392,16 @@ void scr_dyn_release(ScrDyn *d) {
387
392
  * only producer) — same story as the promise arm. */
388
393
  scr_dyn_jsval_ops()->release(d->v.jsval.cell);
389
394
  break;
395
+ case SCR_DYN_TYPED_REF:
396
+ scr_dyn_release(d->v.typed_ref.materialized);
397
+ while (d->v.typed_ref.casts) {
398
+ ScrDynTypedCast *cast = d->v.typed_ref.casts;
399
+ d->v.typed_ref.casts = cast->next;
400
+ cast->release(cast->ptr);
401
+ free(cast);
402
+ }
403
+ d->v.typed_ref.release(d->v.typed_ref.ptr);
404
+ break;
390
405
  default:
391
406
  break; /* null/bool/num have no children */
392
407
  }
@@ -440,6 +455,12 @@ void scr_dyn_arr_push(ScrDyn *arr, ScrDyn *item) {
440
455
  * the generic "Spread syntax requires ...iterable[Symbol.iterator] to be
441
456
  * a function". Borrows src. */
442
457
  void scr_dyn_arr_push_spread(ScrDyn *arr, const ScrDyn *src, const char *what) {
458
+ if (src->kind == SCR_DYN_TYPED_REF) {
459
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(src);
460
+ scr_dyn_arr_push_spread(arr, materialized, what);
461
+ scr_dyn_release(materialized);
462
+ return;
463
+ }
443
464
  if (src->kind == SCR_DYN_ARR) {
444
465
  for (size_t i = 0; i < src->v.arr.len; i++) {
445
466
  scr_dyn_arr_push(arr, scr_dyn_retain(src->v.arr.items[i]));
@@ -502,6 +523,12 @@ void scr_dyn_arr_push_spread(ScrDyn *arr, const ScrDyn *src, const char *what) {
502
523
  * undefined no kind prefix, null V8's "object null"). Borrows both; +1 or
503
524
  * NULL with the TypeError pending. */
504
525
  ScrDyn *scr_dyn_iter_pack(const ScrDyn *src, const ScrStr *msg) {
526
+ if (src->kind == SCR_DYN_TYPED_REF) {
527
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(src);
528
+ ScrDyn *out = scr_dyn_iter_pack(materialized, msg);
529
+ scr_dyn_release(materialized);
530
+ return out;
531
+ }
505
532
  if (src->kind == SCR_DYN_ARR || src->kind == SCR_DYN_BYTES || src->kind == SCR_DYN_STR) {
506
533
  ScrDyn *out = scr_dyn_new_arr();
507
534
  scr_dyn_arr_push_spread(out, src, ""); /* iterable kinds never consult `what` */
@@ -611,6 +638,21 @@ ScrDyn *scr_dyn_new_str(ScrStr *s) {
611
638
 
612
639
  ScrDyn *scr_dyn_new_arr(void) { return scr_dyn_alloc(SCR_DYN_ARR); }
613
640
  ScrDyn *scr_dyn_new_obj(void) { return scr_dyn_alloc(SCR_DYN_OBJ); }
641
+ ScrDyn *scr_dyn_new_obj_with_identity(
642
+ void *source, void *(*source_retain)(void *),
643
+ ScrDyn *(*source_access)(void *, bool materialize)) {
644
+ ScrDyn *d = scr_dyn_alloc(SCR_DYN_OBJ);
645
+ d->v.obj.source_identity = source_retain(source);
646
+ d->v.obj.source_access = source_access;
647
+ return d;
648
+ }
649
+
650
+ bool scr_dyn_obj_same_source(const ScrDyn *a, const ScrDyn *b) {
651
+ return a && b && a->kind == SCR_DYN_OBJ && b->kind == SCR_DYN_OBJ &&
652
+ a->v.obj.source_identity && b->v.obj.source_identity &&
653
+ a->v.obj.source_identity == b->v.obj.source_identity;
654
+ }
655
+
614
656
  ScrDyn *scr_dyn_new_obj_null_proto(void) {
615
657
  ScrDyn *d = scr_dyn_alloc(SCR_DYN_OBJ);
616
658
  d->null_proto = true;
@@ -629,6 +671,136 @@ ScrDyn *scr_dyn_new_buffer_copy(const ScrBytes *b) {
629
671
  return d;
630
672
  }
631
673
 
674
+ ScrDyn *scr_dyn_new_typed_ref(
675
+ void *ptr, void *(*retain)(void *), void (*release)(void *),
676
+ const char *type_key, size_t type_key_len,
677
+ ScrDyn *(*materialize)(void *),
678
+ void (*commit)(void *, const ScrDyn *)) {
679
+ ScrDyn *d = scr_dyn_alloc(SCR_DYN_TYPED_REF);
680
+ d->v.typed_ref.ptr = retain(ptr);
681
+ d->v.typed_ref.retain = retain;
682
+ d->v.typed_ref.release = release;
683
+ d->v.typed_ref.type_key = type_key;
684
+ d->v.typed_ref.type_key_len = type_key_len;
685
+ d->v.typed_ref.materialize = materialize;
686
+ d->v.typed_ref.commit = commit;
687
+ d->v.typed_ref.materialized = NULL;
688
+ d->v.typed_ref.casts = NULL;
689
+ return d;
690
+ }
691
+
692
+ bool scr_dyn_typed_ref_is(
693
+ const ScrDyn *d, const char *type_key, size_t type_key_len) {
694
+ return d && d->kind == SCR_DYN_TYPED_REF &&
695
+ d->v.typed_ref.type_key_len == type_key_len &&
696
+ memcmp(d->v.typed_ref.type_key, type_key, type_key_len) == 0;
697
+ }
698
+
699
+ void *scr_dyn_typed_ref_unbox(const ScrDyn *d) {
700
+ return d->v.typed_ref.retain(d->v.typed_ref.ptr);
701
+ }
702
+
703
+ static void scr_dyn_typed_ref_preserve_child(
704
+ ScrDyn **fresh_slot, ScrDyn *cached_child) {
705
+ ScrDyn *fresh_child = *fresh_slot;
706
+ if (!cached_child || !fresh_child ||
707
+ cached_child->kind != SCR_DYN_TYPED_REF ||
708
+ fresh_child->kind != SCR_DYN_TYPED_REF ||
709
+ !scr_dyn_strict_eq(cached_child, fresh_child)) {
710
+ return;
711
+ }
712
+ *fresh_slot = scr_dyn_retain(cached_child);
713
+ scr_dyn_release(fresh_child);
714
+ }
715
+
716
+ /* A live record/array materializer creates typed capsules for mutable
717
+ * children. Preserve an existing child capsule when the fresh view points
718
+ * at the same static source, so retaining `value.child` across parent
719
+ * refreshes keeps JavaScript reference identity as well as liveness. */
720
+ static void scr_dyn_typed_ref_preserve_children(
721
+ ScrDyn *cached, ScrDyn *fresh) {
722
+ if (cached->kind != fresh->kind) return;
723
+ if (cached->kind == SCR_DYN_ARR) {
724
+ size_t n = cached->v.arr.len < fresh->v.arr.len
725
+ ? cached->v.arr.len
726
+ : fresh->v.arr.len;
727
+ for (size_t i = 0; i < n; i++) {
728
+ scr_dyn_typed_ref_preserve_child(
729
+ &fresh->v.arr.items[i], cached->v.arr.items[i]);
730
+ }
731
+ return;
732
+ }
733
+ if (cached->kind == SCR_DYN_OBJ) {
734
+ for (size_t i = 0; i < fresh->v.obj.len; i++) {
735
+ ScrDynEntry *entry = &fresh->v.obj.entries[i];
736
+ ScrDyn *cached_child = scr_dyn_obj_get(
737
+ cached, entry->key, entry->key_len);
738
+ scr_dyn_typed_ref_preserve_child(&entry->value, cached_child);
739
+ }
740
+ }
741
+ }
742
+
743
+ ScrDyn *scr_dyn_typed_ref_materialize(const ScrDyn *d) {
744
+ ScrDyn *capsule = (ScrDyn *)d;
745
+ if (!capsule->v.typed_ref.materialized) {
746
+ capsule->v.typed_ref.materialized =
747
+ capsule->v.typed_ref.materialize(capsule->v.typed_ref.ptr);
748
+ } else {
749
+ /* Keep the stable dyn object identity while refreshing its contents
750
+ * from the live typed source. The fresh snapshot owns exactly one
751
+ * reference; swapping payloads lets its release dispose the old
752
+ * detached contents without changing the cached node's address. */
753
+ ScrDyn *fresh =
754
+ capsule->v.typed_ref.materialize(capsule->v.typed_ref.ptr);
755
+ scr_dyn_typed_ref_preserve_children(
756
+ capsule->v.typed_ref.materialized, fresh);
757
+ size_t cached_rc = capsule->v.typed_ref.materialized->rc;
758
+ size_t fresh_rc = fresh->rc;
759
+ ScrDyn old = *capsule->v.typed_ref.materialized;
760
+ *capsule->v.typed_ref.materialized = *fresh;
761
+ capsule->v.typed_ref.materialized->rc = cached_rc;
762
+ *fresh = old;
763
+ fresh->rc = fresh_rc;
764
+ scr_dyn_release(fresh);
765
+ }
766
+ return scr_dyn_retain(capsule->v.typed_ref.materialized);
767
+ }
768
+
769
+ void scr_dyn_typed_ref_commit(ScrDyn *d) {
770
+ if (d && d->kind == SCR_DYN_TYPED_REF && d->v.typed_ref.commit &&
771
+ d->v.typed_ref.materialized) {
772
+ d->v.typed_ref.commit(d->v.typed_ref.ptr,
773
+ d->v.typed_ref.materialized);
774
+ }
775
+ }
776
+
777
+ void *scr_dyn_typed_ref_cached_cast(
778
+ const ScrDyn *d, const char *type_key, size_t type_key_len) {
779
+ for (ScrDynTypedCast *cast = d->v.typed_ref.casts; cast;
780
+ cast = cast->next) {
781
+ if (cast->type_key_len == type_key_len &&
782
+ memcmp(cast->type_key, type_key, type_key_len) == 0) {
783
+ return cast->retain(cast->ptr);
784
+ }
785
+ }
786
+ return NULL;
787
+ }
788
+
789
+ void scr_dyn_typed_ref_cache_cast(
790
+ ScrDyn *d, const char *type_key, size_t type_key_len, void *ptr,
791
+ void *(*retain)(void *), void (*release)(void *)) {
792
+ if (!ptr) return;
793
+ ScrDynTypedCast *cast = malloc(sizeof *cast);
794
+ if (!cast) scr_trap("scriptc: out of memory\n");
795
+ cast->type_key = type_key;
796
+ cast->type_key_len = type_key_len;
797
+ cast->ptr = retain(ptr);
798
+ cast->retain = retain;
799
+ cast->release = release;
800
+ cast->next = d->v.typed_ref.casts;
801
+ d->v.typed_ref.casts = cast;
802
+ }
803
+
632
804
  ScrBytes *scr_dyn_bytes_copy_out(const ScrDyn *d) {
633
805
  return scr_bytes_copy(d->v.bytes); /* extraction copies too (+1) */
634
806
  }
@@ -760,6 +932,14 @@ const char *scr_dyn_specific_type(const ScrDyn *cb, char *detail, size_t cap) {
760
932
  * normalization — pick by the engine's typeof. */
761
933
  d = scr_dyn_isl_typeof_is(cb, "function") ? "function" : "an instance of Object";
762
934
  break;
935
+ case SCR_DYN_TYPED_REF: {
936
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(cb);
937
+ const char *specific = scr_dyn_specific_type(materialized, detail, cap);
938
+ if (specific != detail) snprintf(detail, cap, "%s", specific);
939
+ scr_dyn_release(materialized);
940
+ d = detail;
941
+ break;
942
+ }
763
943
  case SCR_DYN_BOOL:
764
944
  snprintf(detail, cap, "type boolean (%s)", cb->v.b ? "true" : "false");
765
945
  break;
@@ -931,6 +1111,24 @@ const ScrDynJsvalOps *scr_dyn_jsval_ops(void) {
931
1111
  }
932
1112
 
933
1113
  bool scr_dyn_isl_typeof_is(const ScrDyn *d, const char *name) {
1114
+ if (d->kind == SCR_DYN_TYPED_REF) {
1115
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(d);
1116
+ bool out = scr_dyn_isl_typeof_is(materialized, name);
1117
+ if (materialized->kind != SCR_DYN_JSVAL) {
1118
+ bool object =
1119
+ materialized->kind == SCR_DYN_NULL ||
1120
+ materialized->kind == SCR_DYN_OBJ ||
1121
+ materialized->kind == SCR_DYN_ARR ||
1122
+ materialized->kind == SCR_DYN_BYTES ||
1123
+ materialized->kind == SCR_DYN_HANDLE ||
1124
+ materialized->kind == SCR_DYN_PROMISE;
1125
+ out = (strcmp(name, "object") == 0 && object) ||
1126
+ (strcmp(name, "function") == 0 &&
1127
+ materialized->kind == SCR_DYN_FUNC);
1128
+ }
1129
+ scr_dyn_release(materialized);
1130
+ return out;
1131
+ }
934
1132
  if (d->kind != SCR_DYN_JSVAL) return false;
935
1133
  ScrStr *t = scr_dyn_jsval_ops()->type_of(d->v.jsval.cell);
936
1134
  size_t n = strlen(name);
@@ -940,10 +1138,26 @@ bool scr_dyn_isl_typeof_is(const ScrDyn *d, const char *name) {
940
1138
  }
941
1139
 
942
1140
  bool scr_dyn_isl_is_array(const ScrDyn *d) {
1141
+ if (d->kind == SCR_DYN_TYPED_REF) {
1142
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(d);
1143
+ bool out = materialized->kind == SCR_DYN_ARR ||
1144
+ scr_dyn_isl_is_array(materialized);
1145
+ scr_dyn_release(materialized);
1146
+ return out;
1147
+ }
943
1148
  return d->kind == SCR_DYN_JSVAL && scr_dyn_jsval_ops()->is_array(d->v.jsval.cell);
944
1149
  }
945
1150
 
946
1151
  bool scr_dyn_isl_is_error(const ScrDyn *d) {
1152
+ if (d->kind == SCR_DYN_TYPED_REF) {
1153
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(d);
1154
+ bool out =
1155
+ (materialized->kind == SCR_DYN_OBJ &&
1156
+ scr_dyn_obj_get(materialized, "%error", 6) != NULL) ||
1157
+ scr_dyn_isl_is_error(materialized);
1158
+ scr_dyn_release(materialized);
1159
+ return out;
1160
+ }
947
1161
  return d->kind == SCR_DYN_JSVAL && scr_dyn_jsval_ops()->is_error(d->v.jsval.cell);
948
1162
  }
949
1163
 
@@ -1103,7 +1317,8 @@ bool scr_dyn_truthy(const ScrDyn *d) {
1103
1317
  case SCR_DYN_BYTES:
1104
1318
  case SCR_DYN_FUNC:
1105
1319
  case SCR_DYN_HANDLE:
1106
- case SCR_DYN_PROMISE: return true;
1320
+ case SCR_DYN_PROMISE:
1321
+ case SCR_DYN_TYPED_REF: return true;
1107
1322
  case SCR_DYN_JSVAL:
1108
1323
  /* Route to the engine's ToBoolean: objects/arrays/functions are
1109
1324
  * true, but the symbol/bigint edge (0n is falsy) needs the engine. */
@@ -1116,6 +1331,12 @@ bool scr_dyn_truthy(const ScrDyn *d) {
1116
1331
  * null answers "object" — JS's oldest wart, preserved. */
1117
1332
  ScrStr *scr_dyn_typeof(const ScrDyn *d) {
1118
1333
  const char *s;
1334
+ if (d->kind == SCR_DYN_TYPED_REF) {
1335
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(d);
1336
+ ScrStr *out = scr_dyn_typeof(materialized);
1337
+ scr_dyn_release(materialized);
1338
+ return out;
1339
+ }
1119
1340
  /* An island value answers the ENGINE's typeof — "object" for the
1120
1341
  * wrapped objects/arrays, "function" for engine functions (row 1 of
1121
1342
  * the jsval→dyn op table; scalars normalized away at wrap time). */
@@ -1127,7 +1348,8 @@ ScrStr *scr_dyn_typeof(const ScrDyn *d) {
1127
1348
  case SCR_DYN_ARR:
1128
1349
  case SCR_DYN_BYTES:
1129
1350
  case SCR_DYN_HANDLE:
1130
- case SCR_DYN_PROMISE: s = "object"; break;
1351
+ case SCR_DYN_PROMISE:
1352
+ case SCR_DYN_TYPED_REF: s = "object"; break; /* handled above */
1131
1353
  case SCR_DYN_BOOL: s = "boolean"; break;
1132
1354
  case SCR_DYN_NUM: s = "number"; break;
1133
1355
  case SCR_DYN_STR: s = "string"; break;
@@ -1222,6 +1444,12 @@ static bool scr_dyn_json_write(ScrJsonBuf *b, const ScrDyn *d) {
1222
1444
  scr_str_release(j);
1223
1445
  return true;
1224
1446
  }
1447
+ case SCR_DYN_TYPED_REF: {
1448
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(d);
1449
+ bool present = scr_dyn_json_write(b, materialized);
1450
+ scr_dyn_release(materialized);
1451
+ return present;
1452
+ }
1225
1453
  case SCR_DYN_HANDLE:
1226
1454
  default: {
1227
1455
  const char *msg = "JSON.stringify of a runtime handle is not supported yet";
@@ -1447,6 +1675,12 @@ ScrStr *scr_dyn_to_string(const ScrDyn *d, const ScrStr *enc) {
1447
1675
  ScrStr *s = scr_dyn_jsval_ops()->to_str(d->v.jsval.cell);
1448
1676
  return s ? s : scr_str_new("", 0);
1449
1677
  }
1678
+ case SCR_DYN_TYPED_REF: {
1679
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(d);
1680
+ ScrStr *s = scr_dyn_to_string(materialized, enc);
1681
+ scr_dyn_release(materialized);
1682
+ return s;
1683
+ }
1450
1684
  case SCR_DYN_UNDEF:
1451
1685
  case SCR_DYN_NULL:
1452
1686
  default: {
@@ -1487,25 +1721,60 @@ ScrStr *scr_dyn_string_coerce(const ScrDyn *d) {
1487
1721
  return scr_dyn_to_string(d, NULL);
1488
1722
  }
1489
1723
 
1724
+ static bool scr_dyn_to_primitive_result_is_object(const ScrDyn *d) {
1725
+ switch (d->kind) {
1726
+ case SCR_DYN_OBJ:
1727
+ case SCR_DYN_ARR:
1728
+ case SCR_DYN_BYTES:
1729
+ case SCR_DYN_FUNC:
1730
+ case SCR_DYN_HANDLE:
1731
+ case SCR_DYN_PROMISE:
1732
+ case SCR_DYN_TYPED_REF:
1733
+ return true;
1734
+ case SCR_DYN_JSVAL:
1735
+ /* Engine scalars normally normalize into their native dyn kinds on
1736
+ * ingress, but preserve the correct answer for any producer that keeps
1737
+ * a wrapped object/function (or a defensively wrapped null). */
1738
+ return !scr_dyn_is_nullish(d) &&
1739
+ (scr_dyn_isl_typeof_is(d, "object") ||
1740
+ scr_dyn_isl_typeof_is(d, "function"));
1741
+ default:
1742
+ return false;
1743
+ }
1744
+ }
1745
+
1490
1746
  /* JS ToString over a dyn value WITH the object protocol (the WHATWG
1491
1747
  * USVString conversions — URLSearchParams names/values): an OBJ whose
1492
1748
  * own 'toString' member is callable is invoked with zero arguments (its
1493
- * throw propagates, catchably); a non-primitive answer falls through to
1494
- * 'valueOf' (ToPrimitive's string hint); exhaustion is the spec's
1749
+ * throw propagates, catchably); without an own member, an ordinary object
1750
+ * inherits Object.prototype.toString and answers "[object Object]". A
1751
+ * non-primitive answer falls through to 'valueOf' (ToPrimitive's string
1752
+ * hint); exhaustion is the spec's
1495
1753
  * "Cannot convert object to primitive value" TypeError. Every other
1496
1754
  * kind matches scr_dyn_string_coerce (units RENDER — ToString(null) is
1497
1755
  * "null"). Borrows; +1, or NULL with the exception pending. */
1498
1756
  ScrStr *scr_dyn_string_coerce_js(const ScrDyn *d) {
1757
+ if (d->kind == SCR_DYN_TYPED_REF) {
1758
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(d);
1759
+ ScrStr *out = scr_dyn_string_coerce_js(materialized);
1760
+ scr_dyn_release(materialized);
1761
+ return out;
1762
+ }
1499
1763
  if (d->kind == SCR_DYN_OBJ) {
1500
1764
  static const char *const hint[2] = { "toString", "valueOf" };
1501
1765
  for (int i = 0; i < 2; i++) {
1502
1766
  ScrDyn *m = scr_dyn_obj_get(d, hint[i], strlen(hint[i])); /* borrowed */
1767
+ if (!m && i == 0 && !d->null_proto) {
1768
+ return scr_str_new("[object Object]", 15);
1769
+ }
1503
1770
  if (!m || m->kind != SCR_DYN_FUNC) continue;
1771
+ /* OrdinaryToPrimitive performs a method call, not a bare function
1772
+ * call: an own coercion hook observes the source object as `this`. */
1773
+ scr_dyn_this_push_dyn(d);
1504
1774
  ScrDyn *r = scr_dyn_call(m, NULL, 0, hint[i]);
1775
+ scr_dyn_this_pop();
1505
1776
  if (!r) return NULL; /* the method threw — pending */
1506
- if (r->kind == SCR_DYN_OBJ || r->kind == SCR_DYN_ARR ||
1507
- r->kind == SCR_DYN_FUNC || r->kind == SCR_DYN_HANDLE ||
1508
- r->kind == SCR_DYN_PROMISE) {
1777
+ if (scr_dyn_to_primitive_result_is_object(r)) {
1509
1778
  scr_dyn_release(r); /* non-primitive answer: try the next method */
1510
1779
  continue;
1511
1780
  }
@@ -1532,6 +1801,12 @@ static const char *scr_dyn_kind_name(const ScrDyn *d);
1532
1801
  * valid dense index, every other kind false (tsc admits `in` only on
1533
1802
  * object-typed operands). Borrows both; never throws. */
1534
1803
  bool scr_dyn_has_key(const ScrDyn *v, const ScrStr *key) {
1804
+ if (v->kind == SCR_DYN_TYPED_REF) {
1805
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(v);
1806
+ bool out = scr_dyn_has_key(materialized, key);
1807
+ scr_dyn_release(materialized);
1808
+ return out;
1809
+ }
1535
1810
  if (v->kind == SCR_DYN_OBJ) return scr_dyn_obj_get(v, key->data, key->len) != NULL;
1536
1811
  if (v->kind == SCR_DYN_ARR) {
1537
1812
  if (key->len == 6 && memcmp(key->data, "length", 6) == 0) return true;
@@ -1549,6 +1824,13 @@ bool scr_dyn_has_key(const ScrDyn *v, const ScrStr *key) {
1549
1824
  }
1550
1825
 
1551
1826
  void scr_dyn_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value) {
1827
+ if (recv->kind == SCR_DYN_TYPED_REF) {
1828
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(recv);
1829
+ scr_dyn_key_set(materialized, key, value);
1830
+ if (!scr_exc_pending()) scr_dyn_typed_ref_commit(recv);
1831
+ scr_dyn_release(materialized);
1832
+ return;
1833
+ }
1552
1834
  if (recv->kind == SCR_DYN_OBJ) {
1553
1835
  scr_dyn_obj_set(recv, key->data, key->len, scr_dyn_retain(value));
1554
1836
  return;
@@ -1689,6 +1971,12 @@ void scr_jb_put_dyn(ScrJsonBuf *b, const ScrDyn *d) {
1689
1971
  scr_str_release(j);
1690
1972
  return;
1691
1973
  }
1974
+ case SCR_DYN_TYPED_REF: {
1975
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(d);
1976
+ scr_jb_put_dyn(b, materialized);
1977
+ scr_dyn_release(materialized);
1978
+ return;
1979
+ }
1692
1980
  case SCR_DYN_OBJ: {
1693
1981
  scr_jb_putc(b, '{');
1694
1982
  bool first = true;
@@ -1747,6 +2035,7 @@ static const char *scr_dyn_kind_name(const ScrDyn *d) {
1747
2035
  case SCR_DYN_JSVAL: return "an island value"; /* "got an island value" — validated
1748
2036
  * extraction of engine-held values has no armed route yet (lane
1749
2037
  * dom-jsval-long-tail); the failure names the world honestly. */
2038
+ case SCR_DYN_TYPED_REF: return "object";
1750
2039
  }
1751
2040
  return "unknown";
1752
2041
  }
@@ -2307,6 +2596,11 @@ bool scr_dyn_strict_eq(const ScrDyn *a, const ScrDyn *b) {
2307
2596
  * strict equality answers). Mixed kinds already answered false above
2308
2597
  * — a dyn copy is a different object, which is Node's answer too. */
2309
2598
  return a == b || scr_dyn_jsval_ops()->strict_eq(a->v.jsval.cell, b->v.jsval.cell);
2599
+ case SCR_DYN_TYPED_REF:
2600
+ return a->v.typed_ref.ptr == b->v.typed_ref.ptr &&
2601
+ a->v.typed_ref.type_key_len == b->v.typed_ref.type_key_len &&
2602
+ memcmp(a->v.typed_ref.type_key, b->v.typed_ref.type_key,
2603
+ a->v.typed_ref.type_key_len) == 0;
2310
2604
  default: return a == b;
2311
2605
  }
2312
2606
  }
@@ -2433,6 +2727,12 @@ static ScrDyn *scr_sc_clone(const ScrDyn *v, const ScrScParent *up) {
2433
2727
  * silent wrong answer. Loud fence (lane dom-jsval-long-tail). */
2434
2728
  scr_dyn_isl_fence(v, "structuredClone");
2435
2729
  return NULL;
2730
+ case SCR_DYN_TYPED_REF: {
2731
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(v);
2732
+ ScrDyn *out = scr_sc_clone(materialized, up);
2733
+ scr_dyn_release(materialized);
2734
+ return out;
2735
+ }
2436
2736
  case SCR_DYN_FUNC:
2437
2737
  case SCR_DYN_HANDLE:
2438
2738
  default: {
@@ -2544,6 +2844,12 @@ static void scr_dyn_objwalk_push(ScrDyn *out, ScrObjWalk mode, const char *key,
2544
2844
  }
2545
2845
 
2546
2846
  static ScrDyn *scr_dyn_objwalk(const ScrDyn *v, ScrObjWalk mode) {
2847
+ if (v->kind == SCR_DYN_TYPED_REF) {
2848
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(v);
2849
+ ScrDyn *out = scr_dyn_objwalk(materialized, mode);
2850
+ scr_dyn_release(materialized);
2851
+ return out;
2852
+ }
2547
2853
  if (v->kind == SCR_DYN_UNDEF || v->kind == SCR_DYN_NULL) {
2548
2854
  static const char msg[] = "Cannot convert undefined or null to object";
2549
2855
  scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
@@ -2667,6 +2973,12 @@ ScrDyn *scr_dyn_obj_keys(const ScrDyn *v) { return scr_dyn_objwalk(v, SCR_OBJWAL
2667
2973
  * existing two-arg stance — a dyn array target has no property table). */
2668
2974
  static void scr_dyn_assign_from(ScrDyn *target, const ScrDyn *src) {
2669
2975
  if (target->kind != SCR_DYN_OBJ) return;
2976
+ if (src->kind == SCR_DYN_TYPED_REF) {
2977
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(src);
2978
+ scr_dyn_assign_from(target, materialized);
2979
+ scr_dyn_release(materialized);
2980
+ return;
2981
+ }
2670
2982
  if (src->kind == SCR_DYN_UNDEF || src->kind == SCR_DYN_NULL) return;
2671
2983
  if (src->kind == SCR_DYN_OBJ) {
2672
2984
  for (size_t i = 0; i < src->v.obj.len; i++) {
@@ -2714,6 +3026,14 @@ ScrDyn *scr_dyn_assign(ScrDyn *target, const ScrDyn *src) {
2714
3026
  if (!scr_dyn_jsval_ops()->assign(target->v.jsval.cell, src)) return NULL;
2715
3027
  return scr_dyn_retain(target);
2716
3028
  }
3029
+ if (target->kind == SCR_DYN_TYPED_REF) {
3030
+ ScrDyn *materialized = scr_dyn_typed_ref_materialize(target);
3031
+ ScrDyn *assigned = scr_dyn_assign(materialized, src);
3032
+ if (!scr_exc_pending()) scr_dyn_typed_ref_commit(target);
3033
+ scr_dyn_release(assigned);
3034
+ scr_dyn_release(materialized);
3035
+ return scr_exc_pending() ? NULL : scr_dyn_retain(target);
3036
+ }
2717
3037
  if (src->kind == SCR_DYN_JSVAL) {
2718
3038
  /* A wrapped SOURCE onto a dyn target: the engine lists its own
2719
3039
  * [key, value] pairs (getters running) and each lands as a dyn