@scriptc/runtime 0.0.11 → 0.0.13

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/src/scr_http2.c CHANGED
@@ -2192,6 +2192,7 @@ static void scr_h2_compat_dispatch(ScrH2Session *s, ScrH2Stream *st, const ScrH2
2192
2192
  scr_http_h2_req_header(req, name, value);
2193
2193
  }
2194
2194
  ScrHttpRes *res = scr_http_h2_res_new(s->sock, st);
2195
+ scr_http_res_set_req(res, req); /* res.req — the compat pair's backref */
2195
2196
  st->compat_req = scr_http_req_retain(req);
2196
2197
  st->compat_res = scr_http_res_retain(res);
2197
2198
  /* the req owns the READ side: the stream's own 'end' bookkeeping is
@@ -2302,6 +2303,55 @@ static ScrH2SrvCtx *scr_h2_server_ctx(ScrNetServer *s) {
2302
2303
  return ctx;
2303
2304
  }
2304
2305
 
2306
+ /* createSecureServer(options, handler): the eager COMPAT handler is the
2307
+ * first 'request' listener over the ALPN=h2 server — the
2308
+ * create_server_req route, secured. */
2309
+ ScrNetServer *scr_http2_create_secure_server_req(const char *cert, size_t cert_len,
2310
+ const char *key, size_t key_len,
2311
+ ScrClosure *handler /*moves, nullable*/,
2312
+ ScrHttpReqFn fn) {
2313
+ ScrNetServer *s = scr_http2_create_secure_server(cert, cert_len, key, key_len);
2314
+ if (handler != NULL) {
2315
+ ScrH2SrvCtx *ctx = scr_h2_server_ctx(s);
2316
+ if (ctx != NULL) scr_net_ls_add(&ctx->request_ls, handler, (void *)fn, false);
2317
+ else scr_closure_release(handler);
2318
+ }
2319
+ return s;
2320
+ }
2321
+
2322
+ /* createSecureServer with a RUNTIME options record (the divergence-66
2323
+ * stance): allowHTTP1 picks the flavor — truthy is the https/HTTP-1.1
2324
+ * compatibility server, absent or falsy the ALPN=h2 server — cert/key
2325
+ * ride the shared TLS server walk (scr_tls.c: out-of-bounds members
2326
+ * throw the catchable runtime fence), and the h2 session-tuning keys
2327
+ * drop exactly like the literal walk ignores them. NULL + pending
2328
+ * exception when the walk fences. */
2329
+ ScrNetServer *scr_http2_create_secure_server_dyn(const ScrDyn *opts /*borrowed*/,
2330
+ ScrClosure *handler /*moves, nullable*/,
2331
+ ScrHttpReqFn fn) {
2332
+ bool allow_http1 = false;
2333
+ if (opts != NULL && opts->kind == SCR_DYN_OBJ) {
2334
+ for (size_t i = 0; i < opts->v.obj.len; i++) {
2335
+ if (strcmp(opts->v.obj.entries[i].key, "allowHTTP1") == 0) {
2336
+ allow_http1 = scr_dyn_truthy(opts->v.obj.entries[i].value);
2337
+ }
2338
+ }
2339
+ }
2340
+ ScrBytes *cert, *key;
2341
+ if (!scr_tls_srv_opts_walk(opts, "http2.createSecureServer", &cert, &key)) {
2342
+ scr_closure_release(handler);
2343
+ return NULL;
2344
+ }
2345
+ ScrNetServer *s = allow_http1
2346
+ ? scr_https_create_server((const char *)cert->data, cert->len,
2347
+ (const char *)key->data, key->len, handler, fn)
2348
+ : scr_http2_create_secure_server_req((const char *)cert->data, cert->len,
2349
+ (const char *)key->data, key->len, handler, fn);
2350
+ scr_bytes_release(cert);
2351
+ scr_bytes_release(key);
2352
+ return s;
2353
+ }
2354
+
2305
2355
  void scr_http2_server_on_stream(ScrNetServer *s, ScrClosure *cb /*moves*/, ScrH2StreamFn fn,
2306
2356
  bool once) {
2307
2357
  ScrH2SrvCtx *ctx = scr_h2_server_ctx(s);
@@ -2589,7 +2639,7 @@ void scr_http2_settings_thunk0(ScrClosure *cb, ScrDyn *settings) {
2589
2639
  }
2590
2640
 
2591
2641
  /* The typed lane's DYN spellings (session receivers with mustCall-style
2592
- * callbacks): the raw DOM function crosses the lib boundary and fires
2642
+ * callbacks): the raw dyn function crosses the lib boundary and fires
2593
2643
  * through the same runtime-built listener closures the dyn handle ops
2594
2644
  * use — Node's exact payload shapes. */
2595
2645
  static void scr_h2_dyn_fire_settings(ScrClosure *cb, ScrDyn *settings /*+1*/);
@@ -2689,7 +2739,7 @@ void scr_http2_goaway_thunk0(ScrClosure *cb, double code, double last) {
2689
2739
  * parameter, or a handle stored in an untyped binding) dispatch their
2690
2740
  * members here, the scr_http.c req/res ops precedent: `.on(...)` wires a
2691
2741
  * runtime-built listener closure over the dyn callback, header events
2692
- * build a DOM headers object from the pairs, method calls decode dyn
2742
+ * build a dyn headers object from the pairs, method calls decode dyn
2693
2743
  * args onto the static entry points, and property reads box the scalar
2694
2744
  * answers. Real-but-unmodeled members throw the loud ladder; names the
2695
2745
  * surface never had throw Node's "<x> is not a function". */
@@ -2791,7 +2841,7 @@ static void scr_h2_dyn_fire_goaway(ScrClosure *cb, double code, double last) {
2791
2841
  scr_dyn_release(fn);
2792
2842
  }
2793
2843
 
2794
- /* Flatten a DOM headers object into the flat pairs array (numbers via
2844
+ /* Flatten a dyn headers object into the flat pairs array (numbers via
2795
2845
  * String(n); the :status/:method/... pseudo-headers pass through). */
2796
2846
  static ScrArr *scr_h2_dom_to_pairs(const ScrDyn *headers) {
2797
2847
  ScrArr *pairs = scr_arr_new(SCR_ELEM_STR, headers->v.obj.len * 2);
package/src/scr_inspect.c CHANGED
@@ -700,7 +700,7 @@ ScrStr *scr_insp_error(ScrError *e, double recurse, double depth) {
700
700
  return out;
701
701
  }
702
702
 
703
- /* ── dyn values (the checked-dynamic JSON DOM) ───────────────────────── */
703
+ /* ── dyn values (the checked-dynamic JSON dyn) ───────────────────────── */
704
704
 
705
705
  /* Property-name rendering: bare when the key matches Node's keyStrRegExp
706
706
  * (/^[a-zA-Z_][a-zA-Z_0-9]*$/, '__proto__' excepted), quoted otherwise. */
@@ -735,7 +735,7 @@ ScrStr *scr_insp_key(ScrStr *k) {
735
735
  /* The full inspect over a dyn tree — the one runtime type whose SHAPE
736
736
  * lives in the value, so the traversal lives here instead of a
737
737
  * synthesized helper. Same engine, same defaults. dyn-boxed bytes render
738
- * in the DOM's documented Uint8Array identity (SEMANTICS.md). */
738
+ * in the checked-dynamic tree's documented Uint8Array identity (SEMANTICS.md). */
739
739
  ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) {
740
740
  switch (d->kind) {
741
741
  case SCR_DYN_NULL:
@@ -837,7 +837,7 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) {
837
837
  return scr_str_new("", 0);
838
838
  }
839
839
  case SCR_DYN_JSVAL: {
840
- /* An island value inside the DOM: Node prints the engine object's
840
+ /* An island value inside the checked-dynamic tree: Node prints the engine object's
841
841
  * property dump — fence loudly, naming the engine typeof (the
842
842
  * scr_insp_jsval wording for bare 'any' composites). */
843
843
  ScrStr *t = scr_dyn_typeof(d); /* routes to the engine; +1 */
package/src/scr_island.c CHANGED
@@ -383,6 +383,7 @@ enum {
383
383
  ISL_H_OBJWALK,
384
384
  ISL_H_HASOWN,
385
385
  ISL_H_ASSIGN,
386
+ ISL_H_ITERDRAIN,
386
387
  ISL_H_COUNT,
387
388
  };
388
389
 
@@ -437,7 +438,22 @@ static const char isl_prelude[] =
437
438
  * semantics (own-key order, getters running, ToObject refusals). */
438
439
  "(o,m)=>m===0?Object.keys(o):m===1?Object.values(o):Object.entries(o),"
439
440
  "(o,k)=>Object.hasOwn(o,k),"
440
- "(t,s)=>Object.assign(t,s)]";
441
+ "(t,s)=>Object.assign(t,s),"
442
+ /* ISL_H_ITERDRAIN: the ENGINE's own iterator protocol drained into a
443
+ * fresh array (for-of/destructuring/spread over a wrapped value —
444
+ * generators, Maps, Sets, Symbol.iterator implementations all step
445
+ * exactly as Node runs them). The guard front-runs the not-iterable
446
+ * TypeError in the CALLER's wording: m=1 is V8's spread-call text,
447
+ * s (when defined) the compile-time source spelling verbatim, else
448
+ * the kind wording (iterN's). */
449
+ "(v,m,s)=>{if(v===undefined||v===null||typeof v[Symbol.iterator]!==\"function\"){"
450
+ "if(m===1)throw new TypeError(\"Spread syntax requires ...iterable[Symbol.iterator] to be a function\");"
451
+ "if(s!==undefined)throw new TypeError(s);"
452
+ "let d;if(v===undefined)d=\"undefined\";else if(v===null)d=\"object null\";"
453
+ "else if(typeof v===\"number\")d=\"number \"+v;else if(typeof v===\"boolean\")d=\"boolean \"+v;"
454
+ "else if(typeof v===\"function\")d=\"function\";else d=\"object\";"
455
+ "throw new TypeError(d+\" is not iterable (cannot read property Symbol(Symbol.iterator))\")}"
456
+ "const o=[];for(const x of v)o.push(x);return o}]";
441
457
 
442
458
  static void isl_free_boot(void);
443
459
  static void isl_prom_wraps_teardown(void);
@@ -963,11 +979,11 @@ ScrJsval *scr_jsval_from_json(const ScrStr *json) {
963
979
  return isl_cell_new(v);
964
980
  }
965
981
 
966
- /* ── marshal in: a CHECKED-DYNAMIC (DOM) value ────────────────────────
982
+ /* ── marshal in: a CHECKED-DYNAMIC (dyn) value ────────────────────────
967
983
  * `unknown` flowing into 'any'-typed code (`const cfg = isJson ?
968
- * JSON.parse(text) : islandParser(text)`): the DOM tree rebuilds as
984
+ * JSON.parse(text) : islandParser(text)`): the dyn tree rebuilds as
969
985
  * engine values — a DEEP COPY, the jsMarshal aliasing stance. Data kinds
970
- * only: a DOM carrying a boxed function, a native handle, or a promise
986
+ * only: a dyn carrying a boxed function, a native handle, or a promise
971
987
  * throws the catchable TypeError naming the kind (the box's thunk calls
972
988
  * STATIC code the engine cannot re-enter through a data copy). */
973
989
  static const char *isl_dyn_unmarshalable(const ScrDyn *d) {
@@ -996,14 +1012,14 @@ static const char *isl_dyn_unmarshalable(const ScrDyn *d) {
996
1012
  }
997
1013
  }
998
1014
 
999
- static JSValue isl_dynfn_new(const ScrDyn *d); /* the DOM-function shim, below */
1015
+ static JSValue isl_dynfn_new(const ScrDyn *d); /* the checked-dynamic tree-function shim, below */
1000
1016
 
1001
1017
  static JSValue isl_from_dyn(const ScrDyn *d) {
1002
1018
  switch (d->kind) {
1003
1019
  case SCR_DYN_FUNC:
1004
- /* A boxed DOM function enters as ONE generic host-function shim over
1005
- * its uniform call thunk (ScrDynThunk): engine args wrap as DOM
1006
- * values, the thunk runs, the DOM result converts back. Each
1020
+ /* A boxed dyn function enters as ONE generic host-function shim over
1021
+ * its uniform call thunk (ScrDynThunk): engine args wrap as dyn
1022
+ * values, the thunk runs, the dyn result converts back. Each
1007
1023
  * crossing mints a fresh engine function (documented). */
1008
1024
  return isl_dynfn_new(d);
1009
1025
  case SCR_DYN_UNDEF:
@@ -1017,7 +1033,7 @@ static JSValue isl_from_dyn(const ScrDyn *d) {
1017
1033
  case SCR_DYN_STR:
1018
1034
  return JS_NewStringLen(isl_ctx, d->v.str->data, d->v.str->len);
1019
1035
  case SCR_DYN_BYTES:
1020
- /* Only u8 payloads reach the DOM today (scr_json.c's stringify note). */
1036
+ /* Only u8 payloads reach the checked-dynamic tree today (scr_json.c's stringify note). */
1021
1037
  return JS_NewUint8ArrayCopy(isl_ctx, d->v.bytes->data, d->v.bytes->len);
1022
1038
  case SCR_DYN_ARR: {
1023
1039
  JSValue arr = JS_NewArray(isl_ctx);
@@ -1047,7 +1063,7 @@ static JSValue isl_from_dyn(const ScrDyn *d) {
1047
1063
  return obj;
1048
1064
  }
1049
1065
  case SCR_DYN_JSVAL:
1050
- /* An engine value riding inside DOM data: embed the SAME engine
1066
+ /* An engine value riding inside dyn data: embed the SAME engine
1051
1067
  * value by reference — the identity round trip, member position. */
1052
1068
  return JS_DupValue(isl_ctx, d->v.jsval.cell->v);
1053
1069
  default:
@@ -1057,7 +1073,7 @@ static JSValue isl_from_dyn(const ScrDyn *d) {
1057
1073
  }
1058
1074
 
1059
1075
  ScrJsval *scr_jsval_from_dyn(const ScrDyn *d) {
1060
- /* The identity round trip: an engine value that crossed into the DOM
1076
+ /* The identity round trip: an engine value that crossed into the checked-dynamic tree
1061
1077
  * (scr_dyn_from_jsval) and back is the SAME engine value, by reference
1062
1078
  * — the one direction that used to throw (SEMANTICS.md supersedes the
1063
1079
  * "one unbridgeable mix"). */
@@ -1079,10 +1095,10 @@ ScrJsval *scr_jsval_from_dyn(const ScrDyn *d) {
1079
1095
  return isl_cell_new(v);
1080
1096
  }
1081
1097
 
1082
- /* ── the jsval→DOM crossing (SCR_DYN_JSVAL's constructor + ops) ───────
1098
+ /* ── the jsval→dyn crossing (SCR_DYN_JSVAL's constructor + ops) ───────
1083
1099
  * The reverse direction: an 'any'-typed engine value flowing into an
1084
- * 'unknown'/'object'/JS-residue slot wraps BY REFERENCE as the DOM's
1085
- * island kind. The DOM core (scr_json.c) stays island-free — it routes
1100
+ * 'unknown'/'object'/JS-residue slot wraps BY REFERENCE as the checked-dynamic tree's
1101
+ * island kind. The dyn core (scr_json.c) stays island-free — it routes
1086
1102
  * the armed operations (typeof/truthy/String()/===, the narrowing
1087
1103
  * tests) through these installed ops; everything un-armed there keeps
1088
1104
  * the loud "not supported yet" ladder. */
@@ -1101,7 +1117,7 @@ static bool isl_dynjs_is_error(ScrJsval *cell) { return JS_IsError(cell->v); }
1101
1117
 
1102
1118
  static const ScrDynJsvalOps isl_dynjs_ops;
1103
1119
 
1104
- /* The jsval→DOM wrap over a RAW engine value (BORROWED) — the scalar
1120
+ /* The jsval→dyn wrap over a RAW engine value (BORROWED) — the scalar
1105
1121
  * normalization the cell constructor applies, shared by the routed-op
1106
1122
  * result conversions (which hold a JSValue, not a cell). Composites mint
1107
1123
  * a fresh cell over the SAME engine value (identity is the value — the
@@ -1249,7 +1265,7 @@ static ScrDyn *isl_dynjs_obj_walk(ScrJsval *cell, int mode) {
1249
1265
  isl_bridge_exception();
1250
1266
  return NULL;
1251
1267
  }
1252
- /* The engine array unpacks into a NATIVE DOM array (keys are DOM
1268
+ /* The engine array unpacks into a NATIVE dyn array (keys are dyn
1253
1269
  * strings, values wrap per element, entries become native pairs) so
1254
1270
  * the results iterate/index/measure at native speed. */
1255
1271
  int64_t len = 0;
@@ -1309,6 +1325,36 @@ static bool isl_dynjs_assign(ScrJsval *cell, const ScrDyn *src) {
1309
1325
 
1310
1326
  static ScrStr *isl_dynjs_to_json(ScrJsval *cell) { return scr_jsval_to_json(cell); }
1311
1327
 
1328
+ static ScrDyn *isl_dynjs_iter_drain(ScrJsval *cell, bool spread, const ScrStr *spell) {
1329
+ isl_entry();
1330
+ JSValue m = JS_NewInt32(isl_ctx, spread ? 1 : 0);
1331
+ JSValue s = spell && spell->len > 0
1332
+ ? JS_NewStringLen(isl_ctx, spell->data, spell->len)
1333
+ : JS_UNDEFINED;
1334
+ JSValue argv[3] = {cell->v, m, s};
1335
+ JSValue r = JS_Call(isl_ctx, isl_helpers[ISL_H_ITERDRAIN], JS_UNDEFINED, 3, argv);
1336
+ JS_FreeValue(isl_ctx, s);
1337
+ if (JS_IsException(r)) {
1338
+ isl_bridge_exception();
1339
+ return NULL;
1340
+ }
1341
+ /* The drained engine array unpacks into a fresh dyn array — elements
1342
+ * wrap back scalar-normalized (composites stay engine values by
1343
+ * reference), exactly the obj_walk unpack. */
1344
+ int64_t len = 0;
1345
+ JSValue lv = JS_GetPropertyStr(isl_ctx, r, "length");
1346
+ JS_ToInt64(isl_ctx, &len, lv);
1347
+ JS_FreeValue(isl_ctx, lv);
1348
+ ScrDyn *out = scr_dyn_new_arr();
1349
+ for (int64_t i = 0; i < len; i++) {
1350
+ JSValue e = JS_GetPropertyUint32(isl_ctx, r, (uint32_t)i);
1351
+ scr_dyn_arr_push(out, isl_dyn_from_value(e));
1352
+ JS_FreeValue(isl_ctx, e);
1353
+ }
1354
+ JS_FreeValue(isl_ctx, r);
1355
+ return out;
1356
+ }
1357
+
1312
1358
  static const ScrDynJsvalOps isl_dynjs_ops = {
1313
1359
  isl_dynjs_release,
1314
1360
  isl_dynjs_typeof,
@@ -1326,14 +1372,15 @@ static const ScrDynJsvalOps isl_dynjs_ops = {
1326
1372
  isl_dynjs_has_own,
1327
1373
  isl_dynjs_assign,
1328
1374
  isl_dynjs_to_json,
1375
+ isl_dynjs_iter_drain,
1329
1376
  };
1330
1377
 
1331
1378
  ScrDyn *scr_dyn_from_jsval(ScrJsval *cell) {
1332
1379
  isl_entry();
1333
1380
  JSValue v = cell->v;
1334
- /* Scalar normalization: engine-reported scalars become the NATIVE DOM
1381
+ /* Scalar normalization: engine-reported scalars become the NATIVE dyn
1335
1382
  * kinds at wrap time (the strict exits cannot fail on them), so every
1336
- * scalar path in the DOM core — ===, typeof tests, JSON of leaves —
1383
+ * scalar path in the dyn core — ===, typeof tests, JSON of leaves —
1337
1384
  * stays untouched and the JSVAL kind never competes with them. */
1338
1385
  if (JS_IsUndefined(v)) return scr_dyn_retain(scr_dyn_undefined());
1339
1386
  if (JS_IsNull(v)) return scr_dyn_new_null();
@@ -1653,7 +1700,7 @@ static const JSClassDef isl_hostfn_class = {
1653
1700
  .finalizer = isl_hostfn_finalizer,
1654
1701
  };
1655
1702
 
1656
- static JSClassID isl_dynfn_class_id; /* the DOM-function shim, below */
1703
+ static JSClassID isl_dynfn_class_id; /* the checked-dynamic tree-function shim, below */
1657
1704
  static const JSClassDef isl_dynfn_class;
1658
1705
 
1659
1706
  static void isl_register_hostfn_class(void) {
@@ -1818,13 +1865,13 @@ ScrJsval *scr_jsval_from_closure(ScrClosure *c, int arity,
1818
1865
  return isl_cell_new(fn);
1819
1866
  }
1820
1867
 
1821
- /* ── the generic DOM-function shim (a boxed SCR_DYN_FUNC entering the
1868
+ /* ── the generic dyn-function shim (a boxed SCR_DYN_FUNC entering the
1822
1869
  * island) ─────────────────────────────────────────────────────────────
1823
1870
  * ONE shim serves every boxed function because the box's call thunk is a
1824
- * single uniform C signature (ScrDynThunk): engine arguments wrap as DOM
1825
- * values (scalar-normalizing — the jsval→DOM constructor's stance), the
1871
+ * single uniform C signature (ScrDynThunk): engine arguments wrap as dyn
1872
+ * values (scalar-normalizing — the jsval→dyn constructor's stance), the
1826
1873
  * thunk validates them against the closure's declared parameter types
1827
- * and runs it, and the DOM result converts back through the from_dyn
1874
+ * and runs it, and the dyn result converts back through the from_dyn
1828
1875
  * rules (wrapped cells by reference, data as a deep copy, nested
1829
1876
  * functions through this same shim). OWNERSHIP: the engine function's
1830
1877
  * opaque box owns ONE reference on the whole ScrDyn FUNC node (closure
@@ -1875,7 +1922,7 @@ static JSValue isl_dynfn_invoke(JSContext *ctx, JSValueConst this_val, int argc,
1875
1922
  return isl_throw_pending(ctx);
1876
1923
  }
1877
1924
  if (!r) return JS_UNDEFINED;
1878
- /* The thunk's DOM result converts back per the from_dyn rules; a kind
1925
+ /* The thunk's dyn result converts back per the from_dyn rules; a kind
1879
1926
  * with no crossing (a handle, a promise) throws the same catchable
1880
1927
  * TypeError from_dyn would. */
1881
1928
  const char *bad = isl_dyn_unmarshalable(r);
@@ -1888,7 +1935,7 @@ static JSValue isl_dynfn_invoke(JSContext *ctx, JSValueConst this_val, int argc,
1888
1935
  return out; /* JS_EXCEPTION passes through as the engine throw */
1889
1936
  }
1890
1937
 
1891
- /* A fresh engine function over a boxed DOM function (borrows d — the
1938
+ /* A fresh engine function over a boxed dyn function (borrows d — the
1892
1939
  * opaque box retains it). */
1893
1940
  static JSValue isl_dynfn_new(const ScrDyn *d) {
1894
1941
  JSValue boxv = JS_NewObjectClass(isl_ctx, isl_dynfn_class_id);
@@ -1959,11 +2006,13 @@ static void isl_prom_wrap_entry(ScrFiber *self, void *arg) {
1959
2006
  bool b = false;
1960
2007
  ScrStr *s = NULL;
1961
2008
  ScrJsval *j = NULL;
2009
+ ScrArr *ja = NULL;
1962
2010
  switch (w->payload) {
1963
2011
  case SCR_ISLP_F64: f = scr_await_f64(w->p); break;
1964
2012
  case SCR_ISLP_BOOL: b = scr_await_bool(w->p); break;
1965
2013
  case SCR_ISLP_STR: s = scr_await_str(w->p); break;
1966
2014
  case SCR_ISLP_JSVAL: j = (ScrJsval *)scr_await_ref(w->p); break;
2015
+ case SCR_ISLP_JSVAL_ARR: ja = (ScrArr *)scr_await_ref(w->p); break;
1967
2016
  default: scr_await_void(w->p); break;
1968
2017
  }
1969
2018
  bool rejected = scr_exc_pending();
@@ -1977,11 +2026,26 @@ static void isl_prom_wrap_entry(ScrFiber *self, void *arg) {
1977
2026
  case SCR_ISLP_BOOL: v = JS_NewBool(isl_ctx, b); break;
1978
2027
  case SCR_ISLP_STR: v = s ? JS_NewStringLen(isl_ctx, s->data, s->len) : JS_UNDEFINED; break;
1979
2028
  case SCR_ISLP_JSVAL: v = j ? JS_DupValue(isl_ctx, j->v) : JS_UNDEFINED; break;
2029
+ case SCR_ISLP_JSVAL_ARR:
2030
+ /* A native array of engine cells fulfills: a fresh engine array
2031
+ * over the SAME engine values (identity crosses, spine a copy). */
2032
+ if (ja) {
2033
+ v = JS_NewArray(isl_ctx);
2034
+ for (size_t i = 0; i < ja->len; i++) {
2035
+ ScrJsval *cell = (ScrJsval *)scr_arr_get_ref(ja, (double)i); /* +1 */
2036
+ JS_SetPropertyUint32(isl_ctx, v, (uint32_t)i, JS_DupValue(isl_ctx, cell->v));
2037
+ scr_jsval_release(cell);
2038
+ }
2039
+ } else {
2040
+ v = JS_UNDEFINED;
2041
+ }
2042
+ break;
1980
2043
  default: v = JS_UNDEFINED; break;
1981
2044
  }
1982
2045
  }
1983
2046
  if (s) scr_str_release(s);
1984
2047
  if (j) scr_jsval_release(j);
2048
+ if (ja) scr_arr_release(ja);
1985
2049
  JSValue r = JS_Call(isl_ctx, rejected ? w->reject : w->resolve, JS_UNDEFINED, 1, &v);
1986
2050
  JS_FreeValue(isl_ctx, v);
1987
2051
  if (JS_IsException(r)) {
@@ -2095,6 +2159,20 @@ static JSValue isl_bridge_settle(JSContext *ctx, JSValueConst this_val, int argc
2095
2159
  /* fulfill_ref takes the +1 cell; awaiters retain their own out. */
2096
2160
  scr_promise_fulfill_ref(b->p, isl_cell_new(JS_DupValue(ctx, v)),
2097
2161
  scr_jsval_retain_v, scr_jsval_release_v, NULL);
2162
+ } else if (b->payload == SCR_ISLP_JSVAL_ARR) {
2163
+ /* An `any[]`-declared fulfillment (the inferred loadPlugins return):
2164
+ * the engine array exits Array.isArray-gated, elements BY REFERENCE
2165
+ * (the jsval-element-array exit). A lying fulfillment (non-array)
2166
+ * REJECTS the static promise with the boundary TypeError —
2167
+ * trust-but-verify at the settle, like every dyn→static edge. */
2168
+ ScrJsval *cell = isl_cell_new(JS_DupValue(ctx, v));
2169
+ ScrArr *arr = scr_jsval_exit_jsval_arr(cell);
2170
+ scr_jsval_release(cell);
2171
+ if (!arr) {
2172
+ scr_promise_reject_pending(b->p);
2173
+ } else {
2174
+ scr_promise_fulfill_ref(b->p, arr, scr_arr_retain_v, scr_arr_release_v, NULL);
2175
+ }
2098
2176
  } else {
2099
2177
  scr_promise_fulfill_void(b->p);
2100
2178
  }
@@ -9567,6 +9645,37 @@ ScrBytes *scr_jsval_exit_bytes(ScrJsval *v) {
9567
9645
  return b;
9568
9646
  }
9569
9647
 
9648
+ /* Validated exit of an engine value into an `any[]`-declared slot (the
9649
+ * jsval-element-array spelling — withPlugins' `loadPlugins(plugins)`
9650
+ * boundary): the engine's Array.isArray gates (a non-array refuses with
9651
+ * the catchable boundary TypeError), then elements copy BY REFERENCE
9652
+ * into a native array of engine cells — identity preserved, length a
9653
+ * snapshot (the exit's aliasing stance: element IDENTITY crosses, the
9654
+ * spine is a copy). +1, or NULL with the exception pending. */
9655
+ ScrArr *scr_jsval_exit_jsval_arr(ScrJsval *v) {
9656
+ isl_entry();
9657
+ if (JS_IsArray(v->v) <= 0) {
9658
+ isl_exit_fail("an array", v);
9659
+ return NULL;
9660
+ }
9661
+ JSValue lv = JS_GetPropertyStr(isl_ctx, v->v, "length");
9662
+ int64_t len = 0;
9663
+ JS_ToInt64(isl_ctx, &len, lv);
9664
+ JS_FreeValue(isl_ctx, lv);
9665
+ ScrArr *out = scr_arr_new_ref(&scr_jsval_retain_v, &scr_jsval_release_v, NULL,
9666
+ len > 0 ? (size_t)len : 0);
9667
+ for (int64_t i = 0; i < len; i++) {
9668
+ JSValue e = JS_GetPropertyUint32(isl_ctx, v->v, (uint32_t)i); /* getters run */
9669
+ if (JS_IsException(e)) {
9670
+ isl_bridge_exception();
9671
+ scr_arr_release(out);
9672
+ return NULL;
9673
+ }
9674
+ scr_arr_push_ref(out, isl_cell_new(e)); /* ownership moves in */
9675
+ }
9676
+ return out;
9677
+ }
9678
+
9570
9679
  /* Composite exit: engine JSON.stringify, feeding the existing
9571
9680
  * json.parse + dynCheck walker pipeline on the static side. A value
9572
9681
  * JSON cannot represent (function, undefined, symbol at the top) comes