@scriptc/runtime 0.0.0 → 0.0.2

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.
Files changed (416) hide show
  1. package/LICENSE +202 -0
  2. package/package.json +16 -6
  3. package/src/scr_array.c +488 -0
  4. package/src/scr_assert.c +1393 -0
  5. package/src/scr_async.c +2593 -0
  6. package/src/scr_async_dyn.c +607 -0
  7. package/src/scr_bytes.c +1425 -0
  8. package/src/scr_bytes_io.c +161 -0
  9. package/src/scr_child.c +3587 -0
  10. package/src/scr_closure.c +214 -0
  11. package/src/scr_console.c +143 -0
  12. package/src/scr_cycle.c +218 -0
  13. package/src/scr_dc.c +805 -0
  14. package/src/scr_dgram.c +1007 -0
  15. package/src/scr_dyn_handle.c +154 -0
  16. package/src/scr_dyn_invoke.c +644 -0
  17. package/src/scr_error.c +332 -0
  18. package/src/scr_events.c +714 -0
  19. package/src/scr_events_emitter.c +699 -0
  20. package/src/scr_exception.c +242 -0
  21. package/src/scr_fetch.c +1130 -0
  22. package/src/scr_fetch_curl.c +654 -0
  23. package/src/scr_http.c +3482 -0
  24. package/src/scr_http2.c +3030 -0
  25. package/src/scr_inspect.c +803 -0
  26. package/src/scr_inspect_island.c +51 -0
  27. package/src/scr_island.c +9363 -0
  28. package/src/scr_json.c +2151 -0
  29. package/src/scr_lib.c +3612 -0
  30. package/src/scr_loop_epoll.c +241 -0
  31. package/src/scr_loop_kqueue.c +121 -0
  32. package/src/scr_loop_wsapoll.c +208 -0
  33. package/src/scr_map.c +591 -0
  34. package/src/scr_net.c +3017 -0
  35. package/src/scr_net_island.c +458 -0
  36. package/src/scr_number.c +115 -0
  37. package/src/scr_object.c +25 -0
  38. package/src/scr_path.c +1266 -0
  39. package/src/scr_platform.h +119 -0
  40. package/src/scr_readline.c +287 -0
  41. package/src/scr_regex.c +1080 -0
  42. package/src/scr_runtime.h +5046 -0
  43. package/src/scr_stream.c +2877 -0
  44. package/src/scr_string.c +1264 -0
  45. package/src/scr_symbol.c +132 -0
  46. package/src/scr_test.c +662 -0
  47. package/src/scr_tls.c +1520 -0
  48. package/src/scr_union.c +105 -0
  49. package/src/scr_url.c +911 -0
  50. package/src/scr_url_internal.h +29 -0
  51. package/src/scr_url_params.c +561 -0
  52. package/src/scr_watch.c +568 -0
  53. package/src/scr_web.c +1778 -0
  54. package/src/scr_win.c +76 -0
  55. package/src/scr_zlib.c +197 -0
  56. package/src/scr_zlib_island.c +15 -0
  57. package/vendor/README.md +52 -0
  58. package/vendor/curl/COPYRIGHT +534 -0
  59. package/vendor/curl/include/curl/curl.h +3214 -0
  60. package/vendor/curl/include/curl/curlver.h +79 -0
  61. package/vendor/curl/include/curl/easy.h +125 -0
  62. package/vendor/curl/include/curl/header.h +74 -0
  63. package/vendor/curl/include/curl/mprintf.h +52 -0
  64. package/vendor/curl/include/curl/multi.h +460 -0
  65. package/vendor/curl/include/curl/options.h +70 -0
  66. package/vendor/curl/include/curl/stdcheaders.h +35 -0
  67. package/vendor/curl/include/curl/system.h +508 -0
  68. package/vendor/curl/include/curl/typecheck-gcc.h +716 -0
  69. package/vendor/curl/include/curl/urlapi.h +149 -0
  70. package/vendor/curl/include/curl/websockets.h +84 -0
  71. package/vendor/mbedtls/LICENSE +553 -0
  72. package/vendor/mbedtls/include/CMakeLists.txt +22 -0
  73. package/vendor/mbedtls/include/mbedtls/aes.h +631 -0
  74. package/vendor/mbedtls/include/mbedtls/aria.h +343 -0
  75. package/vendor/mbedtls/include/mbedtls/asn1.h +642 -0
  76. package/vendor/mbedtls/include/mbedtls/asn1write.h +390 -0
  77. package/vendor/mbedtls/include/mbedtls/base64.h +82 -0
  78. package/vendor/mbedtls/include/mbedtls/bignum.h +1088 -0
  79. package/vendor/mbedtls/include/mbedtls/block_cipher.h +76 -0
  80. package/vendor/mbedtls/include/mbedtls/build_info.h +194 -0
  81. package/vendor/mbedtls/include/mbedtls/camellia.h +305 -0
  82. package/vendor/mbedtls/include/mbedtls/ccm.h +526 -0
  83. package/vendor/mbedtls/include/mbedtls/chacha20.h +209 -0
  84. package/vendor/mbedtls/include/mbedtls/chachapoly.h +351 -0
  85. package/vendor/mbedtls/include/mbedtls/check_config.h +1149 -0
  86. package/vendor/mbedtls/include/mbedtls/cipher.h +1250 -0
  87. package/vendor/mbedtls/include/mbedtls/cmac.h +246 -0
  88. package/vendor/mbedtls/include/mbedtls/compat-2.x.h +46 -0
  89. package/vendor/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h +578 -0
  90. package/vendor/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h +873 -0
  91. package/vendor/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h +359 -0
  92. package/vendor/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h +145 -0
  93. package/vendor/mbedtls/include/mbedtls/config_adjust_ssl.h +91 -0
  94. package/vendor/mbedtls/include/mbedtls/config_adjust_x509.h +35 -0
  95. package/vendor/mbedtls/include/mbedtls/config_psa.h +61 -0
  96. package/vendor/mbedtls/include/mbedtls/constant_time.h +36 -0
  97. package/vendor/mbedtls/include/mbedtls/ctr_drbg.h +596 -0
  98. package/vendor/mbedtls/include/mbedtls/debug.h +156 -0
  99. package/vendor/mbedtls/include/mbedtls/des.h +385 -0
  100. package/vendor/mbedtls/include/mbedtls/dhm.h +972 -0
  101. package/vendor/mbedtls/include/mbedtls/ecdh.h +455 -0
  102. package/vendor/mbedtls/include/mbedtls/ecdsa.h +674 -0
  103. package/vendor/mbedtls/include/mbedtls/ecjpake.h +298 -0
  104. package/vendor/mbedtls/include/mbedtls/ecp.h +1517 -0
  105. package/vendor/mbedtls/include/mbedtls/entropy.h +274 -0
  106. package/vendor/mbedtls/include/mbedtls/error.h +201 -0
  107. package/vendor/mbedtls/include/mbedtls/gcm.h +387 -0
  108. package/vendor/mbedtls/include/mbedtls/hkdf.h +124 -0
  109. package/vendor/mbedtls/include/mbedtls/hmac_drbg.h +434 -0
  110. package/vendor/mbedtls/include/mbedtls/lms.h +440 -0
  111. package/vendor/mbedtls/include/mbedtls/mbedtls_config.h +4446 -0
  112. package/vendor/mbedtls/include/mbedtls/md.h +526 -0
  113. package/vendor/mbedtls/include/mbedtls/md5.h +190 -0
  114. package/vendor/mbedtls/include/mbedtls/memory_buffer_alloc.h +142 -0
  115. package/vendor/mbedtls/include/mbedtls/net_sockets.h +299 -0
  116. package/vendor/mbedtls/include/mbedtls/nist_kw.h +166 -0
  117. package/vendor/mbedtls/include/mbedtls/oid.h +727 -0
  118. package/vendor/mbedtls/include/mbedtls/pem.h +160 -0
  119. package/vendor/mbedtls/include/mbedtls/pk.h +1303 -0
  120. package/vendor/mbedtls/include/mbedtls/pkcs12.h +186 -0
  121. package/vendor/mbedtls/include/mbedtls/pkcs5.h +198 -0
  122. package/vendor/mbedtls/include/mbedtls/pkcs7.h +252 -0
  123. package/vendor/mbedtls/include/mbedtls/platform.h +516 -0
  124. package/vendor/mbedtls/include/mbedtls/platform_time.h +79 -0
  125. package/vendor/mbedtls/include/mbedtls/platform_util.h +247 -0
  126. package/vendor/mbedtls/include/mbedtls/poly1305.h +168 -0
  127. package/vendor/mbedtls/include/mbedtls/private_access.h +20 -0
  128. package/vendor/mbedtls/include/mbedtls/psa_util.h +207 -0
  129. package/vendor/mbedtls/include/mbedtls/ripemd160.h +136 -0
  130. package/vendor/mbedtls/include/mbedtls/rsa.h +1170 -0
  131. package/vendor/mbedtls/include/mbedtls/sha1.h +219 -0
  132. package/vendor/mbedtls/include/mbedtls/sha256.h +200 -0
  133. package/vendor/mbedtls/include/mbedtls/sha3.h +172 -0
  134. package/vendor/mbedtls/include/mbedtls/sha512.h +208 -0
  135. package/vendor/mbedtls/include/mbedtls/ssl.h +5887 -0
  136. package/vendor/mbedtls/include/mbedtls/ssl_cache.h +187 -0
  137. package/vendor/mbedtls/include/mbedtls/ssl_ciphersuites.h +482 -0
  138. package/vendor/mbedtls/include/mbedtls/ssl_cookie.h +106 -0
  139. package/vendor/mbedtls/include/mbedtls/ssl_ticket.h +199 -0
  140. package/vendor/mbedtls/include/mbedtls/threading.h +167 -0
  141. package/vendor/mbedtls/include/mbedtls/timing.h +94 -0
  142. package/vendor/mbedtls/include/mbedtls/version.h +78 -0
  143. package/vendor/mbedtls/include/mbedtls/x509.h +500 -0
  144. package/vendor/mbedtls/include/mbedtls/x509_crl.h +184 -0
  145. package/vendor/mbedtls/include/mbedtls/x509_crt.h +1208 -0
  146. package/vendor/mbedtls/include/mbedtls/x509_csr.h +382 -0
  147. package/vendor/mbedtls/include/psa/build_info.h +20 -0
  148. package/vendor/mbedtls/include/psa/crypto.h +4998 -0
  149. package/vendor/mbedtls/include/psa/crypto_adjust_auto_enabled.h +31 -0
  150. package/vendor/mbedtls/include/psa/crypto_adjust_config_dependencies.h +51 -0
  151. package/vendor/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h +101 -0
  152. package/vendor/mbedtls/include/psa/crypto_adjust_config_synonyms.h +49 -0
  153. package/vendor/mbedtls/include/psa/crypto_builtin_composites.h +214 -0
  154. package/vendor/mbedtls/include/psa/crypto_builtin_key_derivation.h +118 -0
  155. package/vendor/mbedtls/include/psa/crypto_builtin_primitives.h +114 -0
  156. package/vendor/mbedtls/include/psa/crypto_compat.h +230 -0
  157. package/vendor/mbedtls/include/psa/crypto_config.h +145 -0
  158. package/vendor/mbedtls/include/psa/crypto_driver_common.h +44 -0
  159. package/vendor/mbedtls/include/psa/crypto_driver_contexts_composites.h +151 -0
  160. package/vendor/mbedtls/include/psa/crypto_driver_contexts_key_derivation.h +52 -0
  161. package/vendor/mbedtls/include/psa/crypto_driver_contexts_primitives.h +105 -0
  162. package/vendor/mbedtls/include/psa/crypto_extra.h +2145 -0
  163. package/vendor/mbedtls/include/psa/crypto_legacy.h +88 -0
  164. package/vendor/mbedtls/include/psa/crypto_platform.h +102 -0
  165. package/vendor/mbedtls/include/psa/crypto_se_driver.h +1383 -0
  166. package/vendor/mbedtls/include/psa/crypto_sizes.h +1319 -0
  167. package/vendor/mbedtls/include/psa/crypto_struct.h +527 -0
  168. package/vendor/mbedtls/include/psa/crypto_types.h +508 -0
  169. package/vendor/mbedtls/include/psa/crypto_values.h +2782 -0
  170. package/vendor/mbedtls/library/aes.c +2294 -0
  171. package/vendor/mbedtls/library/aesce.c +624 -0
  172. package/vendor/mbedtls/library/aesce.h +136 -0
  173. package/vendor/mbedtls/library/aesni.c +846 -0
  174. package/vendor/mbedtls/library/aesni.h +162 -0
  175. package/vendor/mbedtls/library/alignment.h +704 -0
  176. package/vendor/mbedtls/library/aria.c +969 -0
  177. package/vendor/mbedtls/library/asn1parse.c +468 -0
  178. package/vendor/mbedtls/library/asn1write.c +440 -0
  179. package/vendor/mbedtls/library/base64.c +322 -0
  180. package/vendor/mbedtls/library/base64_internal.h +45 -0
  181. package/vendor/mbedtls/library/bignum.c +2583 -0
  182. package/vendor/mbedtls/library/bignum_core.c +1240 -0
  183. package/vendor/mbedtls/library/bignum_core.h +872 -0
  184. package/vendor/mbedtls/library/bignum_core_invasive.h +38 -0
  185. package/vendor/mbedtls/library/bignum_internal.h +122 -0
  186. package/vendor/mbedtls/library/bignum_mod.c +394 -0
  187. package/vendor/mbedtls/library/bignum_mod.h +452 -0
  188. package/vendor/mbedtls/library/bignum_mod_raw.c +276 -0
  189. package/vendor/mbedtls/library/bignum_mod_raw.h +416 -0
  190. package/vendor/mbedtls/library/bignum_mod_raw_invasive.h +34 -0
  191. package/vendor/mbedtls/library/block_cipher.c +207 -0
  192. package/vendor/mbedtls/library/block_cipher_internal.h +99 -0
  193. package/vendor/mbedtls/library/bn_mul.h +1094 -0
  194. package/vendor/mbedtls/library/camellia.c +1058 -0
  195. package/vendor/mbedtls/library/ccm.c +777 -0
  196. package/vendor/mbedtls/library/chacha20.c +563 -0
  197. package/vendor/mbedtls/library/chacha20_internal.h +23 -0
  198. package/vendor/mbedtls/library/chachapoly.c +487 -0
  199. package/vendor/mbedtls/library/check_crypto_config.h +136 -0
  200. package/vendor/mbedtls/library/cipher.c +1712 -0
  201. package/vendor/mbedtls/library/cipher_invasive.h +28 -0
  202. package/vendor/mbedtls/library/cipher_wrap.c +2482 -0
  203. package/vendor/mbedtls/library/cipher_wrap.h +178 -0
  204. package/vendor/mbedtls/library/cmac.c +1067 -0
  205. package/vendor/mbedtls/library/common.h +487 -0
  206. package/vendor/mbedtls/library/constant_time.c +247 -0
  207. package/vendor/mbedtls/library/constant_time_impl.h +541 -0
  208. package/vendor/mbedtls/library/constant_time_internal.h +579 -0
  209. package/vendor/mbedtls/library/ctr.h +35 -0
  210. package/vendor/mbedtls/library/ctr_drbg.c +1016 -0
  211. package/vendor/mbedtls/library/debug.c +475 -0
  212. package/vendor/mbedtls/library/debug_internal.h +185 -0
  213. package/vendor/mbedtls/library/des.c +1042 -0
  214. package/vendor/mbedtls/library/dhm.c +700 -0
  215. package/vendor/mbedtls/library/ecdh.c +696 -0
  216. package/vendor/mbedtls/library/ecdsa.c +858 -0
  217. package/vendor/mbedtls/library/ecjpake.c +1216 -0
  218. package/vendor/mbedtls/library/ecp.c +3674 -0
  219. package/vendor/mbedtls/library/ecp_curves.c +6125 -0
  220. package/vendor/mbedtls/library/ecp_curves_new.c +9 -0
  221. package/vendor/mbedtls/library/ecp_internal_alt.h +287 -0
  222. package/vendor/mbedtls/library/ecp_invasive.h +305 -0
  223. package/vendor/mbedtls/library/entropy.c +680 -0
  224. package/vendor/mbedtls/library/entropy_poll.c +233 -0
  225. package/vendor/mbedtls/library/entropy_poll.h +64 -0
  226. package/vendor/mbedtls/library/error.c +878 -0
  227. package/vendor/mbedtls/library/gcm.c +1330 -0
  228. package/vendor/mbedtls/library/hkdf.c +161 -0
  229. package/vendor/mbedtls/library/hmac_drbg.c +633 -0
  230. package/vendor/mbedtls/library/lmots.c +789 -0
  231. package/vendor/mbedtls/library/lmots.h +288 -0
  232. package/vendor/mbedtls/library/lms.c +778 -0
  233. package/vendor/mbedtls/library/md.c +1108 -0
  234. package/vendor/mbedtls/library/md5.c +426 -0
  235. package/vendor/mbedtls/library/md_psa.h +26 -0
  236. package/vendor/mbedtls/library/md_wrap.h +46 -0
  237. package/vendor/mbedtls/library/memory_buffer_alloc.c +751 -0
  238. package/vendor/mbedtls/library/mps_common.h +181 -0
  239. package/vendor/mbedtls/library/mps_error.h +89 -0
  240. package/vendor/mbedtls/library/mps_reader.c +538 -0
  241. package/vendor/mbedtls/library/mps_reader.h +366 -0
  242. package/vendor/mbedtls/library/mps_trace.c +112 -0
  243. package/vendor/mbedtls/library/mps_trace.h +154 -0
  244. package/vendor/mbedtls/library/net_sockets.c +694 -0
  245. package/vendor/mbedtls/library/nist_kw.c +729 -0
  246. package/vendor/mbedtls/library/oid.c +1166 -0
  247. package/vendor/mbedtls/library/padlock.c +157 -0
  248. package/vendor/mbedtls/library/padlock.h +111 -0
  249. package/vendor/mbedtls/library/pem.c +554 -0
  250. package/vendor/mbedtls/library/pk.c +1602 -0
  251. package/vendor/mbedtls/library/pk_ecc.c +261 -0
  252. package/vendor/mbedtls/library/pk_internal.h +241 -0
  253. package/vendor/mbedtls/library/pk_wrap.c +1618 -0
  254. package/vendor/mbedtls/library/pk_wrap.h +138 -0
  255. package/vendor/mbedtls/library/pkcs12.c +437 -0
  256. package/vendor/mbedtls/library/pkcs5.c +500 -0
  257. package/vendor/mbedtls/library/pkcs7.c +787 -0
  258. package/vendor/mbedtls/library/pkparse.c +1392 -0
  259. package/vendor/mbedtls/library/pkwrite.c +631 -0
  260. package/vendor/mbedtls/library/pkwrite.h +121 -0
  261. package/vendor/mbedtls/library/platform.c +402 -0
  262. package/vendor/mbedtls/library/platform_util.c +258 -0
  263. package/vendor/mbedtls/library/poly1305.c +492 -0
  264. package/vendor/mbedtls/library/psa_crypto.c +9532 -0
  265. package/vendor/mbedtls/library/psa_crypto_aead.c +646 -0
  266. package/vendor/mbedtls/library/psa_crypto_aead.h +499 -0
  267. package/vendor/mbedtls/library/psa_crypto_cipher.c +747 -0
  268. package/vendor/mbedtls/library/psa_crypto_cipher.h +316 -0
  269. package/vendor/mbedtls/library/psa_crypto_client.c +22 -0
  270. package/vendor/mbedtls/library/psa_crypto_core.h +983 -0
  271. package/vendor/mbedtls/library/psa_crypto_core_common.h +52 -0
  272. package/vendor/mbedtls/library/psa_crypto_driver_wrappers.h +2896 -0
  273. package/vendor/mbedtls/library/psa_crypto_driver_wrappers_no_static.c +256 -0
  274. package/vendor/mbedtls/library/psa_crypto_driver_wrappers_no_static.h +31 -0
  275. package/vendor/mbedtls/library/psa_crypto_ecp.c +594 -0
  276. package/vendor/mbedtls/library/psa_crypto_ecp.h +267 -0
  277. package/vendor/mbedtls/library/psa_crypto_ffdh.c +361 -0
  278. package/vendor/mbedtls/library/psa_crypto_ffdh.h +131 -0
  279. package/vendor/mbedtls/library/psa_crypto_hash.c +470 -0
  280. package/vendor/mbedtls/library/psa_crypto_hash.h +211 -0
  281. package/vendor/mbedtls/library/psa_crypto_invasive.h +92 -0
  282. package/vendor/mbedtls/library/psa_crypto_its.h +131 -0
  283. package/vendor/mbedtls/library/psa_crypto_mac.c +505 -0
  284. package/vendor/mbedtls/library/psa_crypto_mac.h +264 -0
  285. package/vendor/mbedtls/library/psa_crypto_pake.c +571 -0
  286. package/vendor/mbedtls/library/psa_crypto_pake.h +159 -0
  287. package/vendor/mbedtls/library/psa_crypto_random.c +181 -0
  288. package/vendor/mbedtls/library/psa_crypto_random.h +72 -0
  289. package/vendor/mbedtls/library/psa_crypto_random_impl.h +200 -0
  290. package/vendor/mbedtls/library/psa_crypto_rsa.c +714 -0
  291. package/vendor/mbedtls/library/psa_crypto_rsa.h +321 -0
  292. package/vendor/mbedtls/library/psa_crypto_se.c +373 -0
  293. package/vendor/mbedtls/library/psa_crypto_se.h +192 -0
  294. package/vendor/mbedtls/library/psa_crypto_slot_management.c +1137 -0
  295. package/vendor/mbedtls/library/psa_crypto_slot_management.h +344 -0
  296. package/vendor/mbedtls/library/psa_crypto_storage.c +481 -0
  297. package/vendor/mbedtls/library/psa_crypto_storage.h +392 -0
  298. package/vendor/mbedtls/library/psa_its_file.c +254 -0
  299. package/vendor/mbedtls/library/psa_util.c +614 -0
  300. package/vendor/mbedtls/library/psa_util_internal.h +100 -0
  301. package/vendor/mbedtls/library/ripemd160.c +490 -0
  302. package/vendor/mbedtls/library/rsa.c +3121 -0
  303. package/vendor/mbedtls/library/rsa_alt_helpers.c +455 -0
  304. package/vendor/mbedtls/library/rsa_alt_helpers.h +209 -0
  305. package/vendor/mbedtls/library/rsa_internal.h +188 -0
  306. package/vendor/mbedtls/library/sha1.c +480 -0
  307. package/vendor/mbedtls/library/sha256.c +983 -0
  308. package/vendor/mbedtls/library/sha3.c +721 -0
  309. package/vendor/mbedtls/library/sha512.c +1115 -0
  310. package/vendor/mbedtls/library/ssl_cache.c +410 -0
  311. package/vendor/mbedtls/library/ssl_ciphersuites.c +2050 -0
  312. package/vendor/mbedtls/library/ssl_ciphersuites_internal.h +154 -0
  313. package/vendor/mbedtls/library/ssl_client.c +1023 -0
  314. package/vendor/mbedtls/library/ssl_client.h +22 -0
  315. package/vendor/mbedtls/library/ssl_cookie.c +384 -0
  316. package/vendor/mbedtls/library/ssl_debug_helpers.h +85 -0
  317. package/vendor/mbedtls/library/ssl_debug_helpers_generated.c +251 -0
  318. package/vendor/mbedtls/library/ssl_misc.h +3198 -0
  319. package/vendor/mbedtls/library/ssl_msg.c +6619 -0
  320. package/vendor/mbedtls/library/ssl_ticket.c +556 -0
  321. package/vendor/mbedtls/library/ssl_tls.c +10234 -0
  322. package/vendor/mbedtls/library/ssl_tls12_client.c +3688 -0
  323. package/vendor/mbedtls/library/ssl_tls12_server.c +4311 -0
  324. package/vendor/mbedtls/library/ssl_tls13_client.c +3208 -0
  325. package/vendor/mbedtls/library/ssl_tls13_generic.c +1793 -0
  326. package/vendor/mbedtls/library/ssl_tls13_invasive.h +23 -0
  327. package/vendor/mbedtls/library/ssl_tls13_keys.c +1918 -0
  328. package/vendor/mbedtls/library/ssl_tls13_keys.h +668 -0
  329. package/vendor/mbedtls/library/ssl_tls13_server.c +3624 -0
  330. package/vendor/mbedtls/library/threading.c +193 -0
  331. package/vendor/mbedtls/library/threading_internal.h +28 -0
  332. package/vendor/mbedtls/library/timing.c +152 -0
  333. package/vendor/mbedtls/library/version.c +32 -0
  334. package/vendor/mbedtls/library/version_features.c +856 -0
  335. package/vendor/mbedtls/library/x509.c +1776 -0
  336. package/vendor/mbedtls/library/x509_create.c +570 -0
  337. package/vendor/mbedtls/library/x509_crl.c +708 -0
  338. package/vendor/mbedtls/library/x509_crt.c +3311 -0
  339. package/vendor/mbedtls/library/x509_csr.c +648 -0
  340. package/vendor/mbedtls/library/x509_internal.h +101 -0
  341. package/vendor/mbedtls/library/x509write.c +174 -0
  342. package/vendor/mbedtls/library/x509write_crt.c +688 -0
  343. package/vendor/mbedtls/library/x509write_csr.c +336 -0
  344. package/vendor/quickjs-ng/CMakeLists.txt +566 -0
  345. package/vendor/quickjs-ng/LICENSE +24 -0
  346. package/vendor/quickjs-ng/README.md +24 -0
  347. package/vendor/quickjs-ng/api-test.c +1195 -0
  348. package/vendor/quickjs-ng/builtin-array-fromasync.h +119 -0
  349. package/vendor/quickjs-ng/builtin-iterator-zip-keyed.h +332 -0
  350. package/vendor/quickjs-ng/builtin-iterator-zip.h +337 -0
  351. package/vendor/quickjs-ng/cutils.h +1998 -0
  352. package/vendor/quickjs-ng/dtoa.c +1619 -0
  353. package/vendor/quickjs-ng/dtoa.h +87 -0
  354. package/vendor/quickjs-ng/gen/function_source.c +81 -0
  355. package/vendor/quickjs-ng/gen/hello.c +53 -0
  356. package/vendor/quickjs-ng/gen/hello_module.c +106 -0
  357. package/vendor/quickjs-ng/gen/repl.c +3036 -0
  358. package/vendor/quickjs-ng/gen/standalone.c +323 -0
  359. package/vendor/quickjs-ng/gen/test_fib.c +81 -0
  360. package/vendor/quickjs-ng/libregexp-opcode.h +73 -0
  361. package/vendor/quickjs-ng/libregexp.c +3474 -0
  362. package/vendor/quickjs-ng/libregexp.h +101 -0
  363. package/vendor/quickjs-ng/libunicode-table.h +5173 -0
  364. package/vendor/quickjs-ng/libunicode.c +2069 -0
  365. package/vendor/quickjs-ng/libunicode.h +172 -0
  366. package/vendor/quickjs-ng/list.h +107 -0
  367. package/vendor/quickjs-ng/lre-test.c +52 -0
  368. package/vendor/quickjs-ng/qjs.c +762 -0
  369. package/vendor/quickjs-ng/qjsc.c +673 -0
  370. package/vendor/quickjs-ng/quickjs-atom.h +280 -0
  371. package/vendor/quickjs-ng/quickjs-c-atomics.h +54 -0
  372. package/vendor/quickjs-ng/quickjs-libc.c +5037 -0
  373. package/vendor/quickjs-ng/quickjs-libc.h +87 -0
  374. package/vendor/quickjs-ng/quickjs-opcode.h +377 -0
  375. package/vendor/quickjs-ng/quickjs.c +64041 -0
  376. package/vendor/quickjs-ng/quickjs.h +1447 -0
  377. package/vendor/quickjs-ng/run-test262.c +2374 -0
  378. package/vendor/quickjs-ng/unicode_gen.c +3121 -0
  379. package/vendor/quickjs-ng/unicode_gen_def.h +310 -0
  380. package/vendor/ryu/LICENSE-Boost +23 -0
  381. package/vendor/ryu/common.h +114 -0
  382. package/vendor/ryu/d2s.c +509 -0
  383. package/vendor/ryu/d2s_full_table.h +367 -0
  384. package/vendor/ryu/d2s_intrinsics.h +357 -0
  385. package/vendor/ryu/d2s_small_table.h +186 -0
  386. package/vendor/ryu/digit_table.h +35 -0
  387. package/vendor/ryu/ryu.h +46 -0
  388. package/vendor/zlib/LICENSE +22 -0
  389. package/vendor/zlib/adler32.c +164 -0
  390. package/vendor/zlib/compress.c +75 -0
  391. package/vendor/zlib/crc32.c +1049 -0
  392. package/vendor/zlib/crc32.h +9446 -0
  393. package/vendor/zlib/deflate.c +2139 -0
  394. package/vendor/zlib/deflate.h +377 -0
  395. package/vendor/zlib/gzclose.c +23 -0
  396. package/vendor/zlib/gzguts.h +214 -0
  397. package/vendor/zlib/gzlib.c +582 -0
  398. package/vendor/zlib/gzread.c +602 -0
  399. package/vendor/zlib/gzwrite.c +631 -0
  400. package/vendor/zlib/infback.c +628 -0
  401. package/vendor/zlib/inffast.c +320 -0
  402. package/vendor/zlib/inffast.h +11 -0
  403. package/vendor/zlib/inffixed.h +94 -0
  404. package/vendor/zlib/inflate.c +1526 -0
  405. package/vendor/zlib/inflate.h +126 -0
  406. package/vendor/zlib/inftrees.c +299 -0
  407. package/vendor/zlib/inftrees.h +62 -0
  408. package/vendor/zlib/trees.c +1117 -0
  409. package/vendor/zlib/trees.h +128 -0
  410. package/vendor/zlib/uncompr.c +85 -0
  411. package/vendor/zlib/zconf.h +543 -0
  412. package/vendor/zlib/zlib.h +1938 -0
  413. package/vendor/zlib/zutil.c +299 -0
  414. package/vendor/zlib/zutil.h +254 -0
  415. package/README.md +0 -3
  416. package/index.js +0 -2
@@ -0,0 +1,2877 @@
1
+ /* node:stream — the runtime-provided stream classes behind the
2
+ * options-object constructor forms (`new Readable({ read() {} })` et al).
3
+ * This translation unit links ONLY into binaries whose IR touches the
4
+ * stream surface (moduleUsesStream — the scr_events_emitter.c gating
5
+ * precedent); stream-free binaries keep their exact link line.
6
+ *
7
+ * ONE layout for all five classes (ScrStream: the ScrEmitter prefix plus
8
+ * a state block), so upcasts among them and to ScrEmitter are pointer
9
+ * reinterprets. Events dispatch through the embedded emitter registry —
10
+ * scr_emitter_emit's variadic core — with the frontend's PER-BASE forced
11
+ * tuples guaranteeing every 'data' listener reads one ScrBytes*, every
12
+ * 'pipe'/'unpipe' listener one ScrStream*, and the lifecycle events
13
+ * nothing.
14
+ *
15
+ * EVENT TIMING. Node schedules most stream emissions on process.nextTick;
16
+ * here those deferrals ride a FIFO tick queue drained at the top of every
17
+ * loop turn (scr_loop_set_stream — the station before events/net/timers,
18
+ * the closest to nextTick; ticks scheduled from user code on the main
19
+ * stack run when the loop starts, i.e. after all synchronous code — the
20
+ * nextTick shape). The implemented orderings follow lib/internal/streams:
21
+ * - on('data') starts flowing on a TICK (resume_): synchronous code
22
+ * after the registration runs before the first 'data'.
23
+ * - push() while flowing with an empty buffer emits 'data'
24
+ * SYNCHRONOUSLY (addChunk's direct-emit fast path).
25
+ * - 'readable' is emitted on a tick (emitReadable_), collapsed while
26
+ * one is already scheduled (emittedReadable).
27
+ * - push(null) → 'end' fires on a tick once the buffer drains, then
28
+ * autoDestroy (default) destroys and 'close' follows on the NEXT
29
+ * tick.
30
+ * - write() calls the user write synchronously; a SYNCHRONOUS
31
+ * completion defers afterWrite (the user cb + 'drain') to a tick
32
+ * (Node's onwrite sync guard), an async completion runs it directly.
33
+ * - end() → (buffer drains) → _final → 'prefinish' (sync) → 'finish'
34
+ * on a tick → autoDestroy → 'close' on the next tick.
35
+ * - destroy(err) calls _destroy synchronously; 'error' then 'close'
36
+ * fire on ticks (error first, close after — Node's order).
37
+ * An unhandled 'error' event throws its payload through the exception
38
+ * cell (scr_emitter_emit_error) — the loop's dispatch surfaces it as the
39
+ * uncaught crash, Node's contract.
40
+ *
41
+ * DOCUMENTED DIVERGENCES (SEMANTICS.md, the stream block): tick-vs-
42
+ * microtask interleaving (Node runs nextTick callbacks before promise
43
+ * jobs; this queue drains after the fiber ready-queue), pipe() without
44
+ * registry-visible listeners (listenerCount('data') does not count the
45
+ * C-side pipe hookup), and Transform completing writes without read-side
46
+ * backpressure. */
47
+ #include "scr_runtime.h"
48
+
49
+ #include <stdio.h>
50
+ #include <stdlib.h>
51
+ #include <string.h>
52
+
53
+ /* Node v24's stream default (lib/internal/streams/state.js — 64 KiB
54
+ * since nodejs/node#52037, EXCEPT win32, where the very same line keeps
55
+ * the old 16 KiB: `process.platform === 'win32' ? 16 * 1024 : 64 * 1024`.
56
+ * The triple decides at compile time, like the path/EOL bindings). */
57
+ #ifdef _WIN32
58
+ #define SCR_STREAM_DEFAULT_HWM 16384
59
+ #else
60
+ #define SCR_STREAM_DEFAULT_HWM 65536
61
+ #endif
62
+
63
+ static void scr_stream_oom(void) {
64
+ fputs("scriptc: out of memory\n", stderr);
65
+ abort();
66
+ }
67
+
68
+ /* ── state ────────────────────────────────────────────────────────────── */
69
+
70
+ typedef struct ScrWEntry {
71
+ ScrBytes *chunk; /* owned */
72
+ ScrClosure *cb; /* owned or NULL (the user's write(chunk, cb) callback) */
73
+ } ScrWEntry;
74
+
75
+ struct ScrStreamState {
76
+ bool has_r, has_w;
77
+ bool passthrough; /* identity transform (PassThrough) */
78
+ bool is_transform; /* the writable side routes through transform/flush */
79
+ bool allow_half_open;
80
+ bool auto_destroy, emit_close;
81
+
82
+ /* shared lifecycle */
83
+ bool destroyed, destroy_calling, error_emitted, close_emitted, error_scheduled, close_scheduled;
84
+ /* a parked for-await consumed the error as its rejection: the 'error'
85
+ * EVENT is suppressed unless a user listener exists (Node's iterator
86
+ * registers its own handler, so no unhandled-'error' crash occurs) */
87
+ bool next_err_consumed;
88
+ ScrError *errored; /* owned */
89
+
90
+ /* readable side. The buffer holds owned ScrBytes* chunks — or, once an
91
+ * encoding is set (setEncoding / the encoding option), owned ScrStr*
92
+ * entries: pushes decode through the StringDecoder machinery, lengths
93
+ * count JS string units (Node's encoded accounting), and head_off
94
+ * counts CHARS consumed of buf[0] instead of bytes. */
95
+ struct {
96
+ size_t hwm, length;
97
+ void **buf; /* queue of owned chunks; buf[0] read from head_off */
98
+ size_t n, cap, head_off;
99
+ int flowing; /* -1 null, 0 paused, 1 flowing */
100
+ bool reading, in_read_sync, ended, end_emitted, end_scheduled;
101
+ bool need_readable, emitted_readable, readable_listening;
102
+ bool resume_scheduled, maybe_more_scheduled;
103
+ bool encoded; /* string-chunk mode (decoder active) */
104
+ ScrStr *enc; /* owned canonical encoding name, or NULL */
105
+ double dec_pending; /* scr_strdec packed pending state */
106
+ /* Readable.from mode: entries are whole OBJECTS (each counts 1
107
+ * toward length/hwm and delivers undivided — Node's objectMode
108
+ * accounting for the from() surface; hwm is 1). */
109
+ bool object_entries;
110
+ /* The parked for-await next() promise (at most one — the loop awaits
111
+ * each chunk before asking again). next_dyn picks the resolution
112
+ * shape: a dyn boxed by tag (the JS lane), or bytes. */
113
+ ScrPromise *next_waiter;
114
+ bool next_dyn;
115
+ ScrClosure *read_cb;
116
+ ScrStreamReadInv read_inv;
117
+ } r;
118
+
119
+ /* writable side */
120
+ struct {
121
+ size_t hwm, length, inflight_len;
122
+ ScrWEntry *q;
123
+ size_t n, cap, head;
124
+ int corked;
125
+ bool writing, wsync, need_drain, after_scheduled;
126
+ bool ending, finished, prefinished, final_called, finish_scheduled;
127
+ ScrClosure *inflight_cb; /* owned or NULL */
128
+ ScrClosure *write_cb;
129
+ ScrStreamChunkInv write_inv;
130
+ ScrClosure *final_cb;
131
+ ScrStreamPlainInv final_inv;
132
+ ScrClosure **end_cbs; /* owned end(cb) callbacks, fired at 'finish' */
133
+ size_t end_cbs_n, end_cbs_cap;
134
+ } w;
135
+
136
+ /* transform */
137
+ ScrClosure *transform_cb;
138
+ ScrStreamChunkInv transform_inv;
139
+ ScrClosure *flush_cb;
140
+ ScrStreamPlainInv flush_inv;
141
+ bool flush_called;
142
+
143
+ /* destroy */
144
+ ScrClosure *destroy_cb;
145
+ ScrStreamErrInv destroy_inv;
146
+
147
+ /* finished()/pipeline() watchers: fired once at the terminal point
148
+ * (the 'close' tick — or a registration-time tick when already closed)
149
+ * with the stream's finish status (NULL / the error / premature close).
150
+ * A watcher's presence marks lifecycle errors HANDLED (Node's eos and
151
+ * pipeline both register 'error' listeners, so no unhandled crash). */
152
+ struct {
153
+ ScrClosure **cb;
154
+ ScrStreamErrInv *inv;
155
+ size_t n, cap;
156
+ } fin;
157
+
158
+ /* pipes (src side): owned destination refs */
159
+ struct {
160
+ ScrStream **dst;
161
+ bool *end;
162
+ size_t n, cap;
163
+ } pipes;
164
+ size_t await_drain; /* pipe destinations we are waiting on */
165
+
166
+ /* pipe sources awaiting THIS stream's drain (dst side): owned refs */
167
+ struct {
168
+ ScrStream **src;
169
+ size_t n, cap;
170
+ } drain_srcs;
171
+ };
172
+
173
+ /* ── vtables and RC ───────────────────────────────────────────────────── */
174
+
175
+ /* The five runtime vtables start with the EMPTY preorder interval
176
+ * (pre 1 > post 0): main() stamps the real interval exactly when the
177
+ * class def rides the module, and an unstamped vt must never claim a
178
+ * user class whose preorder number happens to collide (scr_stream_is
179
+ * below tests interval membership for compiler-emitted subclasses). */
180
+ static void scr_stream_release_direct(void *obj);
181
+ ScrVt scr_readable_vt = {1, 0, &scr_stream_release_direct};
182
+ ScrVt scr_writable_vt = {1, 0, &scr_stream_release_direct};
183
+ ScrVt scr_duplex_vt = {1, 0, &scr_stream_release_direct};
184
+ ScrVt scr_transform_vt = {1, 0, &scr_stream_release_direct};
185
+ ScrVt scr_passthrough_vt = {1, 0, &scr_stream_release_direct};
186
+
187
+ static bool scr_vt_within(const ScrVt *vt, const ScrVt *cls) {
188
+ return cls->pre <= vt->pre && vt->pre <= cls->post;
189
+ }
190
+
191
+ /* True for the five runtime classes AND compiler-emitted user subclasses
192
+ * (their structs embed the full ScrStream prefix, and their vtable's
193
+ * preorder number sits inside a stream class's stamped interval — the
194
+ * instanceof machinery). Duplex/Transform/PassThrough intervals nest
195
+ * inside Readable's, so two tests cover the forest. */
196
+ static bool scr_stream_is(const ScrEmitter *em) {
197
+ const ScrVt *vt = em->vt;
198
+ if (vt == &scr_readable_vt || vt == &scr_writable_vt || vt == &scr_duplex_vt ||
199
+ vt == &scr_transform_vt || vt == &scr_passthrough_vt) {
200
+ return true;
201
+ }
202
+ return scr_vt_within(vt, &scr_readable_vt) || scr_vt_within(vt, &scr_writable_vt) ||
203
+ scr_vt_within(vt, &scr_duplex_vt) || scr_vt_within(vt, &scr_transform_vt) ||
204
+ scr_vt_within(vt, &scr_passthrough_vt);
205
+ }
206
+
207
+ /* Readable-buffer entry accessors: bytes chunks, or strings once an
208
+ * encoding is set (lengths then count JS string units, Node's encoded
209
+ * accounting). */
210
+ static size_t scr_stream_entry_len(const ScrStreamState *st, void *e) {
211
+ if (st->r.object_entries) return 1; /* objectMode-style accounting */
212
+ return st->r.encoded ? (size_t)scr_str_utf16_len((ScrStr *)e) : ((ScrBytes *)e)->len;
213
+ }
214
+
215
+ static void scr_stream_entry_release(const ScrStreamState *st, void *e) {
216
+ if (st->r.encoded) scr_str_release((ScrStr *)e);
217
+ else scr_bytes_release((ScrBytes *)e);
218
+ }
219
+
220
+ static void scr_stream_state_drop(ScrStreamState *st, bool gc) {
221
+ if (!st) return;
222
+ if (st->errored) scr_error_release(st->errored);
223
+ for (size_t i = 0; i < st->r.n; i++) scr_stream_entry_release(st, st->r.buf[i]);
224
+ free(st->r.buf);
225
+ if (st->r.enc) scr_str_release(st->r.enc);
226
+ if (!gc && st->r.next_waiter) scr_promise_release(st->r.next_waiter);
227
+ if (!gc) {
228
+ if (st->r.read_cb) scr_closure_release(st->r.read_cb);
229
+ if (st->w.write_cb) scr_closure_release(st->w.write_cb);
230
+ if (st->w.final_cb) scr_closure_release(st->w.final_cb);
231
+ if (st->w.inflight_cb) scr_closure_release(st->w.inflight_cb);
232
+ if (st->transform_cb) scr_closure_release(st->transform_cb);
233
+ if (st->flush_cb) scr_closure_release(st->flush_cb);
234
+ if (st->destroy_cb) scr_closure_release(st->destroy_cb);
235
+ for (size_t i = 0; i < st->w.end_cbs_n; i++) scr_closure_release(st->w.end_cbs[i]);
236
+ for (size_t i = 0; i < st->fin.n; i++) scr_closure_release(st->fin.cb[i]);
237
+ for (size_t i = 0; i < st->pipes.n; i++) scr_stream_release(st->pipes.dst[i]);
238
+ for (size_t i = 0; i < st->drain_srcs.n; i++) scr_stream_release(st->drain_srcs.src[i]);
239
+ }
240
+ for (size_t i = st->w.head; i < st->w.n; i++) {
241
+ scr_bytes_release(st->w.q[i].chunk);
242
+ if (!gc && st->w.q[i].cb) scr_closure_release(st->w.q[i].cb);
243
+ }
244
+ free(st->w.q);
245
+ free(st->w.end_cbs);
246
+ free(st->fin.cb);
247
+ free(st->fin.inv);
248
+ free(st->pipes.dst);
249
+ free(st->pipes.end);
250
+ free(st->drain_srcs.src);
251
+ free(st);
252
+ }
253
+
254
+ /* The compiler-emitted subclass RC/trace entry points: a user `extends
255
+ * Readable` struct embeds the ScrStream prefix (vt, registry, display
256
+ * name, state pointer), so its emitted teardown/trace delegate the STATE
257
+ * here (the registry rides scr_emitter_reg_* like any emitter subclass).
258
+ * NULL-safe: an exception before super(options) leaves st unset. */
259
+ void scr_stream_st_release(ScrStreamState *st) { scr_stream_state_drop(st, false); }
260
+ void scr_stream_st_gcfree(ScrStreamState *st) { scr_stream_state_drop(st, true); }
261
+
262
+ void scr_stream_st_trace(ScrStreamState *st, ScrTraceVisit visit, void *ctx) {
263
+ if (!st) return;
264
+ if (st->r.next_waiter) visit(st->r.next_waiter, ctx);
265
+ if (st->r.read_cb) visit(st->r.read_cb, ctx);
266
+ if (st->w.write_cb) visit(st->w.write_cb, ctx);
267
+ if (st->w.final_cb) visit(st->w.final_cb, ctx);
268
+ if (st->w.inflight_cb) visit(st->w.inflight_cb, ctx);
269
+ if (st->transform_cb) visit(st->transform_cb, ctx);
270
+ if (st->flush_cb) visit(st->flush_cb, ctx);
271
+ if (st->destroy_cb) visit(st->destroy_cb, ctx);
272
+ for (size_t i = 0; i < st->w.end_cbs_n; i++) visit(st->w.end_cbs[i], ctx);
273
+ for (size_t i = 0; i < st->fin.n; i++) visit(st->fin.cb[i], ctx);
274
+ for (size_t i = st->w.head; i < st->w.n; i++) {
275
+ if (st->w.q[i].cb) visit(st->w.q[i].cb, ctx);
276
+ }
277
+ for (size_t i = 0; i < st->pipes.n; i++) visit(st->pipes.dst[i], ctx);
278
+ for (size_t i = 0; i < st->drain_srcs.n; i++) visit(st->drain_srcs.src[i], ctx);
279
+ }
280
+
281
+ void scr_stream_trace(void *obj, ScrTraceVisit visit, void *ctx) {
282
+ ScrStream *s = obj;
283
+ scr_emitter_reg_trace(s->reg, visit, ctx);
284
+ scr_stream_st_trace(s->st, visit, ctx);
285
+ }
286
+
287
+ static void scr_stream_gcfree(void *obj) {
288
+ ScrStream *s = obj;
289
+ scr_emitter_reg_gcfree(s->reg);
290
+ scr_stream_state_drop(s->st, true);
291
+ scr_obj_free_note();
292
+ scr_cyc_free(obj);
293
+ }
294
+
295
+ static void scr_stream_release_direct(void *obj) {
296
+ ScrStream *s = obj;
297
+ if (--s->rc == 0) {
298
+ scr_cyc_on_dead(s);
299
+ scr_emitter_reg_drop(s->reg);
300
+ scr_stream_state_drop(s->st, false);
301
+ scr_obj_free_note();
302
+ scr_cyc_free(s);
303
+ } else {
304
+ scr_cyc_on_release(s);
305
+ }
306
+ }
307
+
308
+ ScrStream *scr_stream_retain(ScrStream *s) {
309
+ if (s && s->rc != SIZE_MAX) {
310
+ s->rc++;
311
+ scr_cyc_mark_live(s);
312
+ }
313
+ return s;
314
+ }
315
+
316
+ void scr_stream_release(ScrStream *s) {
317
+ if (!s || s->rc == SIZE_MAX) return;
318
+ s->vt->release(s);
319
+ }
320
+
321
+ void *scr_stream_retain_v(void *s) { return scr_stream_retain(s); }
322
+ void scr_stream_release_v(void *s) { scr_stream_release(s); }
323
+
324
+ /* ── the tick queue (the nextTick stand-in) ───────────────────────────── */
325
+
326
+ typedef enum {
327
+ SCR_ST_RESUME, /* resume_: read(0) kick, 'resume', flow */
328
+ SCR_ST_READABLE, /* emitReadable_: 'readable', then flow */
329
+ SCR_ST_END, /* endReadableNT: 'end', autoDestroy/half-open */
330
+ SCR_ST_MAYBE_MORE, /* maybeReadMore_: refill toward hwm */
331
+ SCR_ST_AFTER_WRITE, /* deferred afterWrite (sync completion) */
332
+ SCR_ST_FINISH, /* 'finish', end cbs, autoDestroy */
333
+ SCR_ST_ERROR, /* 'error' with the owned payload */
334
+ SCR_ST_CLOSE, /* 'close' (when emitClose) */
335
+ SCR_ST_END_W, /* half-open: end the writable side */
336
+ SCR_ST_WCB_ERR, /* a failed write's user cb (called plain) */
337
+ SCR_ST_NEXT_EOF, /* a parked for-await's EOF sentinel, AFTER 'end' */
338
+ SCR_ST_FIN, /* notify finished()/pipeline watchers (already-
339
+ * terminal registration) */
340
+ } ScrStreamTickOp;
341
+
342
+ typedef struct ScrStreamTick {
343
+ ScrStream *s; /* owned */
344
+ ScrStreamTickOp op;
345
+ ScrError *err; /* owned or NULL (SCR_ST_ERROR) */
346
+ ScrClosure *cb; /* owned or NULL (SCR_ST_AFTER_WRITE / SCR_ST_WCB_ERR) */
347
+ struct ScrStreamTick *next;
348
+ } ScrStreamTick;
349
+
350
+ static ScrStreamTick *scr_st_head = NULL;
351
+ static ScrStreamTick *scr_st_tail = NULL;
352
+
353
+ static void scr_st_tick(ScrStream *s, ScrStreamTickOp op, ScrError *err /*moves*/,
354
+ ScrClosure *cb /*moves*/) {
355
+ ScrStreamTick *t = calloc(1, sizeof *t);
356
+ if (!t) scr_stream_oom();
357
+ t->s = scr_stream_retain(s);
358
+ t->op = op;
359
+ t->err = err;
360
+ t->cb = cb;
361
+ if (scr_st_tail) scr_st_tail->next = t;
362
+ else scr_st_head = t;
363
+ scr_st_tail = t;
364
+ }
365
+
366
+ static bool scr_stream_ticks_pending(void) { return scr_st_head != NULL; }
367
+
368
+ /* ── emit helpers ─────────────────────────────────────────────────────── */
369
+
370
+ static bool scr_stream_emit0(ScrStream *s, const char *name) {
371
+ ScrStr *n = scr_str_new(name, strlen(name));
372
+ bool had = scr_emitter_emit((ScrEmitter *)s, n);
373
+ scr_str_release(n);
374
+ return had;
375
+ }
376
+
377
+ static void scr_stream_emit_stream(ScrStream *s, const char *name, ScrStream *payload) {
378
+ ScrStr *n = scr_str_new(name, strlen(name));
379
+ scr_emitter_emit((ScrEmitter *)s, n, payload);
380
+ scr_str_release(n);
381
+ }
382
+
383
+ /* 'error': unhandled throws the payload (Node's crash), via the emitter's
384
+ * error contract. Borrows err. */
385
+ static void scr_stream_emit_error(ScrStream *s, ScrError *err) {
386
+ ScrStr *n = scr_str_new("error", 5);
387
+ scr_emitter_emit_error((ScrEmitter *)s, n, err);
388
+ scr_str_release(n);
389
+ }
390
+
391
+ static ScrError *scr_stream_mkerr(const char *code, const char *msg) {
392
+ ScrStr *m = scr_str_new(msg, strlen(msg));
393
+ ScrError *e = scr_error_new(SCR_ERR_ERROR, m);
394
+ scr_str_release(m);
395
+ scr_error_set_code(e, code);
396
+ return e;
397
+ }
398
+
399
+ /* ── forward decls ────────────────────────────────────────────────────── */
400
+
401
+ static void scr_stream_flow(ScrStream *s);
402
+ static void scr_stream_emit_readable_now(ScrStream *s);
403
+ static void scr_stream_maybe_read_more(ScrStream *s);
404
+ static void scr_stream_finish_maybe(ScrStream *s);
405
+ static void scr_stream_clear_buffer(ScrStream *s);
406
+ static void scr_stream_do_destroy(ScrStream *s, ScrError *err /*borrowed*/);
407
+ static void scr_stream_notify_finished(ScrStream *s);
408
+ static void *scr_stream_read_n(ScrStream *s, double size);
409
+ static void scr_stream_settle_next(ScrStream *s);
410
+ static ScrStream *scr_stream_alloc(const ScrVt *vt, const char *cls, bool has_r, bool has_w,
411
+ double rhwm, double whwm, bool auto_destroy,
412
+ bool emit_close, bool allow_half_open);
413
+
414
+ /* errorOrDestroy: autoDestroy (the default) destroys with the error;
415
+ * otherwise the error emits on a tick. Borrows err. */
416
+ static void scr_stream_error_or_destroy(ScrStream *s, ScrError *err) {
417
+ ScrStreamState *st = s->st;
418
+ if (st->destroyed) return; /* err stays the caller's borrow */
419
+ if (st->auto_destroy) {
420
+ scr_stream_do_destroy(s, err);
421
+ } else {
422
+ if (!st->errored) st->errored = scr_error_retain(err);
423
+ if (!st->error_scheduled) {
424
+ st->error_scheduled = true;
425
+ scr_st_tick(s, SCR_ST_ERROR, scr_error_retain(err), NULL);
426
+ }
427
+ scr_stream_settle_next(s); /* a parked for-await rejects with the error */
428
+ }
429
+ }
430
+
431
+ /* ── readable internals ───────────────────────────────────────────────── */
432
+
433
+ static void scr_stream_rbuf_push(ScrStreamState *st, void *chunk /*moves*/, bool front) {
434
+ if (st->r.n == st->r.cap) {
435
+ st->r.cap = st->r.cap ? st->r.cap * 2 : 4;
436
+ st->r.buf = realloc(st->r.buf, st->r.cap * sizeof *st->r.buf);
437
+ if (!st->r.buf) scr_stream_oom();
438
+ }
439
+ if (front) {
440
+ /* unshift: head_off must be zero (only buf[0] is partially consumed,
441
+ * and unshift with a part-read head keeps the remainder intact by
442
+ * materializing it first — the caller slices). */
443
+ memmove(st->r.buf + 1, st->r.buf, st->r.n * sizeof *st->r.buf);
444
+ st->r.buf[0] = chunk;
445
+ } else {
446
+ st->r.buf[st->r.n] = chunk;
447
+ }
448
+ st->r.n++;
449
+ st->r.length += scr_stream_entry_len(st, chunk);
450
+ }
451
+
452
+ /* Takes n units (n <= length; bytes, or chars once encoded) off the
453
+ * buffer head as one +1 Buffer / string. */
454
+ static void *scr_stream_rbuf_take(ScrStreamState *st, size_t n) {
455
+ void *first = st->r.n > 0 ? st->r.buf[0] : NULL;
456
+ if (first && st->r.head_off == 0 && scr_stream_entry_len(st, first) == n) {
457
+ /* whole-chunk fast path: hand the buffered chunk out as-is */
458
+ memmove(st->r.buf, st->r.buf + 1, (st->r.n - 1) * sizeof *st->r.buf);
459
+ st->r.n--;
460
+ st->r.length -= n;
461
+ return first;
462
+ }
463
+ if (st->r.encoded) {
464
+ /* string mode: slice by JS string units (Node's fromList over
465
+ * strings), concatenating across entries. */
466
+ ScrStr *out = NULL;
467
+ size_t at = 0;
468
+ while (at < n) {
469
+ ScrStr *head = st->r.buf[0];
470
+ size_t head_len = (size_t)scr_str_utf16_len(head);
471
+ size_t avail = head_len - st->r.head_off;
472
+ size_t take = avail < n - at ? avail : n - at;
473
+ ScrStr *piece = scr_str_slice(head, (double)st->r.head_off, (double)(st->r.head_off + take));
474
+ if (out == NULL) {
475
+ out = piece;
476
+ } else {
477
+ ScrStr *joined = scr_str_concat(out, piece);
478
+ scr_str_release(out);
479
+ scr_str_release(piece);
480
+ out = joined;
481
+ }
482
+ at += take;
483
+ st->r.head_off += take;
484
+ if (st->r.head_off == head_len) {
485
+ scr_str_release(head);
486
+ memmove(st->r.buf, st->r.buf + 1, (st->r.n - 1) * sizeof *st->r.buf);
487
+ st->r.n--;
488
+ st->r.head_off = 0;
489
+ }
490
+ }
491
+ st->r.length -= n;
492
+ return out;
493
+ }
494
+ ScrBytes *out = scr_bytes_new(SCR_BYTES_U8, (double)n);
495
+ size_t at = 0;
496
+ while (at < n) {
497
+ ScrBytes *head = st->r.buf[0];
498
+ size_t avail = head->len - st->r.head_off;
499
+ size_t take = avail < n - at ? avail : n - at;
500
+ memcpy(out->data + at, head->data + st->r.head_off, take);
501
+ at += take;
502
+ st->r.head_off += take;
503
+ if (st->r.head_off == head->len) {
504
+ scr_bytes_release(head);
505
+ memmove(st->r.buf, st->r.buf + 1, (st->r.n - 1) * sizeof *st->r.buf);
506
+ st->r.n--;
507
+ st->r.head_off = 0;
508
+ }
509
+ }
510
+ st->r.length -= n;
511
+ return out;
512
+ }
513
+
514
+ /* emit('data', chunk) + pipe delivery. Borrows chunk (a Buffer, or a
515
+ * string once encoded). The emit ABI carries BOTH payload slots — the
516
+ * bytes chunk and the string chunk, exactly one non-NULL — and the
517
+ * compiler's stream-data listener thunks dispatch on which (typed
518
+ * listeners unwrap their declared side; dyn listeners box by tag). */
519
+ static void scr_stream_emit_data(ScrStream *s, void *chunk) {
520
+ ScrStreamState *st = s->st;
521
+ ScrBytes *b = st->r.encoded ? NULL : (ScrBytes *)chunk;
522
+ ScrStr *str = st->r.encoded ? (ScrStr *)chunk : NULL;
523
+ ScrStr *n = scr_str_new("data", 4);
524
+ scr_emitter_emit((ScrEmitter *)s, n, b, str);
525
+ scr_str_release(n);
526
+ if (scr_exc_pending()) return;
527
+ /* pipe delivery, with backpressure: a full destination pauses us until
528
+ * its 'drain'. Encoded sources write the string (utf8 bytes at the
529
+ * destination — Node writes the decoded string through). */
530
+ for (size_t i = 0; i < st->pipes.n;) {
531
+ ScrStream *dst = st->pipes.dst[i];
532
+ if (dst->st->destroyed) {
533
+ /* Node's pipe cleanup: a destroyed destination unhooks (the
534
+ * onerror/onclose → unpipe path) and pauses the source — writing
535
+ * after destroy would drop the error silently and spin an infinite
536
+ * synchronous flow over an endless source. */
537
+ scr_stream_release(scr_stream_unpipe(s, dst));
538
+ if (scr_exc_pending()) return;
539
+ continue; /* the pipes array shifted down over i */
540
+ }
541
+ bool ok = b ? scr_stream_write(dst, b, NULL) : scr_stream_write_str(dst, str, NULL);
542
+ if (scr_exc_pending()) return;
543
+ i++;
544
+ if (!ok && !dst->st->destroyed) {
545
+ ScrStreamState *dstt = dst->st;
546
+ /* register once */
547
+ bool already = false;
548
+ for (size_t j = 0; j < dstt->drain_srcs.n; j++) {
549
+ if (dstt->drain_srcs.src[j] == s) already = true;
550
+ }
551
+ if (!already) {
552
+ if (dstt->drain_srcs.n == dstt->drain_srcs.cap) {
553
+ dstt->drain_srcs.cap = dstt->drain_srcs.cap ? dstt->drain_srcs.cap * 2 : 2;
554
+ dstt->drain_srcs.src = realloc(dstt->drain_srcs.src, dstt->drain_srcs.cap * sizeof *dstt->drain_srcs.src);
555
+ if (!dstt->drain_srcs.src) scr_stream_oom();
556
+ }
557
+ dstt->drain_srcs.src[dstt->drain_srcs.n++] = scr_stream_retain(s);
558
+ st->await_drain++;
559
+ st->r.flowing = 0;
560
+ }
561
+ }
562
+ }
563
+ }
564
+
565
+ static void scr_stream_emit_readable_nt(ScrStream *s) {
566
+ ScrStreamState *st = s->st;
567
+ st->r.need_readable = false; /* Node's emitReadable clears the ask */
568
+ if (!st->r.emitted_readable) {
569
+ st->r.emitted_readable = true;
570
+ scr_st_tick(s, SCR_ST_READABLE, NULL, NULL);
571
+ }
572
+ }
573
+
574
+ static void scr_stream_end_readable(ScrStream *s) {
575
+ ScrStreamState *st = s->st;
576
+ if (st->r.end_emitted || st->r.end_scheduled) return;
577
+ st->r.end_scheduled = true;
578
+ scr_st_tick(s, SCR_ST_END, NULL, NULL);
579
+ }
580
+
581
+ /* The user _read call (reading guard + sync flag). Absent read cb means
582
+ * ERR_METHOD_NOT_IMPLEMENTED, Node's contract. */
583
+ static void scr_stream_call_read(ScrStream *s) {
584
+ ScrStreamState *st = s->st;
585
+ if (st->r.read_cb == NULL && st->r.object_entries) {
586
+ /* Readable.from: fully seeded at construction — _read is a no-op */
587
+ st->r.in_read_sync = false;
588
+ return;
589
+ }
590
+ if (st->r.read_cb == NULL && !st->is_transform && !st->passthrough) {
591
+ ScrError *e = scr_stream_mkerr("ERR_METHOD_NOT_IMPLEMENTED", "The _read() method is not implemented");
592
+ scr_stream_error_or_destroy(s, e);
593
+ scr_error_release(e);
594
+ return;
595
+ }
596
+ if (st->r.read_cb == NULL) {
597
+ /* transform reads are push-driven; the attempt still clears the
598
+ * initial sync flag (Node's Transform._read ran) */
599
+ st->r.in_read_sync = false;
600
+ return;
601
+ }
602
+ st->r.reading = true;
603
+ st->r.in_read_sync = true;
604
+ st->r.read_inv(st->r.read_cb, s, (double)st->r.hwm);
605
+ st->r.in_read_sync = false;
606
+ }
607
+
608
+ /* readableAddChunk. Borrows chunk (retains its own ref). Returns the
609
+ * below-hwm answer. Encoded streams decode the bytes through the
610
+ * StringDecoder first (Node decodes at push); an empty decode (a partial
611
+ * multi-byte tail held pending) adds nothing but still clears the
612
+ * reading flag. */
613
+ static bool scr_stream_add_chunk(ScrStream *s, ScrBytes *chunk, bool front) {
614
+ ScrStreamState *st = s->st;
615
+ if (!st->has_r) {
616
+ ScrError *e = scr_stream_mkerr("ERR_STREAM_PUSH_AFTER_EOF", "push on a Writable (no readable side)");
617
+ scr_stream_error_or_destroy(s, e);
618
+ scr_error_release(e);
619
+ return false;
620
+ }
621
+ if (st->r.ended && !front) {
622
+ ScrError *e = scr_stream_mkerr("ERR_STREAM_PUSH_AFTER_EOF", "stream.push() after EOF");
623
+ scr_stream_error_or_destroy(s, e);
624
+ scr_error_release(e);
625
+ return false;
626
+ }
627
+ if (st->destroyed) return false;
628
+ if (!front) st->r.reading = false;
629
+ void *entry;
630
+ size_t entry_len;
631
+ if (st->r.encoded) {
632
+ ScrStr *dec = scr_strdec_write(st->r.enc, st->r.dec_pending, chunk);
633
+ st->r.dec_pending = scr_strdec_next(st->r.enc, st->r.dec_pending, chunk);
634
+ entry_len = (size_t)scr_str_utf16_len(dec);
635
+ if (entry_len == 0) {
636
+ scr_str_release(dec);
637
+ if (!front) scr_stream_maybe_read_more(s);
638
+ return !st->r.ended && (st->r.length < st->r.hwm || st->r.length == 0);
639
+ }
640
+ entry = dec; /* owned */
641
+ } else {
642
+ entry = scr_bytes_retain(chunk);
643
+ entry_len = chunk->len;
644
+ }
645
+ if (entry_len > 0 &&
646
+ st->r.flowing == 1 && st->r.length == 0 && !st->r.in_read_sync && !front &&
647
+ (scr_emitter_has((ScrEmitter *)s, "data") || st->pipes.n > 0)) {
648
+ /* the direct-emit fast path: flowing, empty buffer, async push */
649
+ if (st->r.emitted_readable) st->r.emitted_readable = false;
650
+ scr_stream_emit_data(s, entry);
651
+ scr_stream_entry_release(st, entry);
652
+ if (scr_exc_pending()) return false;
653
+ } else {
654
+ scr_stream_rbuf_push(st, entry, front);
655
+ if (st->r.need_readable) scr_stream_emit_readable_nt(s);
656
+ }
657
+ if (!front) scr_stream_maybe_read_more(s);
658
+ scr_stream_settle_next(s); /* a parked for-await consumes buffered content */
659
+ return !st->r.ended && (st->r.length < st->r.hwm || st->r.length == 0);
660
+ }
661
+
662
+ static void scr_stream_maybe_read_more(ScrStream *s) {
663
+ ScrStreamState *st = s->st;
664
+ if (st->r.maybe_more_scheduled || st->r.reading || st->r.ended || st->destroyed) return;
665
+ if (!(st->r.length < st->r.hwm && (st->r.flowing == 1 || st->r.need_readable))) return;
666
+ st->r.maybe_more_scheduled = true;
667
+ scr_st_tick(s, SCR_ST_MAYBE_MORE, NULL, NULL);
668
+ }
669
+
670
+ /* howMuchToRead + fromList + the doRead discipline: readable.read().
671
+ * Answers a +1 Buffer — or a +1 string once encoded (lengths and the
672
+ * size argument then count JS string units, Node's encoded accounting). */
673
+ static void *scr_stream_read_n(ScrStream *s, double size) {
674
+ ScrStreamState *st = s->st;
675
+ bool absent = size < 0;
676
+ size_t n = 0;
677
+ if (!absent) {
678
+ n = (size_t)size;
679
+ if (n > st->r.hwm) {
680
+ /* Node grows hwm to the next power of two above n */
681
+ size_t h = st->r.hwm;
682
+ while (h < n) h *= 2;
683
+ st->r.hwm = h;
684
+ }
685
+ }
686
+ if (!absent && n != 0) st->r.emitted_readable = false;
687
+ if (!absent && n == 0 && st->r.need_readable &&
688
+ (st->r.length >= st->r.hwm || st->r.ended)) {
689
+ if (st->r.length == 0 && st->r.ended) scr_stream_end_readable(s);
690
+ else scr_stream_emit_readable_nt(s);
691
+ return NULL;
692
+ }
693
+ /* howMuchToRead */
694
+ size_t want;
695
+ if (st->r.object_entries) {
696
+ /* objectMode-style: one whole entry per read, whatever n says */
697
+ want = st->r.length > 0 ? 1 : 0;
698
+ } else if (absent) {
699
+ want = st->r.flowing == 1 && st->r.n > 0
700
+ ? scr_stream_entry_len(st, st->r.buf[0]) - st->r.head_off
701
+ : st->r.length;
702
+ } else {
703
+ want = n <= st->r.length ? n : (st->r.ended ? st->r.length : 0);
704
+ }
705
+ if (want == 0 && st->r.ended) {
706
+ if (st->r.length == 0) scr_stream_end_readable(s);
707
+ return NULL;
708
+ }
709
+ bool do_read = st->r.need_readable || st->r.length == 0 ||
710
+ st->r.length - want < st->r.hwm;
711
+ if (st->r.ended || st->r.reading || st->destroyed || st->errored) do_read = false;
712
+ if (do_read) {
713
+ if (st->r.length == 0) st->r.need_readable = true;
714
+ scr_stream_call_read(s);
715
+ if (scr_exc_pending()) return NULL;
716
+ /* a synchronous push may have refilled */
717
+ if (!st->r.reading) {
718
+ if (st->r.object_entries) {
719
+ want = st->r.length > 0 ? 1 : 0;
720
+ } else if (absent) {
721
+ want = st->r.flowing == 1 && st->r.n > 0
722
+ ? scr_stream_entry_len(st, st->r.buf[0]) - st->r.head_off
723
+ : st->r.length;
724
+ } else {
725
+ want = n <= st->r.length ? n : (st->r.ended ? st->r.length : 0);
726
+ }
727
+ }
728
+ }
729
+ void *ret = want > 0 && want <= st->r.length ? scr_stream_rbuf_take(st, want) : NULL;
730
+ if (ret == NULL) {
731
+ st->r.need_readable = st->r.length <= st->r.hwm;
732
+ } else if (st->r.flowing == 1 && st->r.length == 0) {
733
+ st->r.need_readable = true;
734
+ }
735
+ if (st->r.length == 0) {
736
+ if (!st->r.ended) st->r.need_readable = true;
737
+ if (st->r.ended && !absent && n != want) scr_stream_end_readable(s);
738
+ if (st->r.ended && absent) scr_stream_end_readable(s);
739
+ }
740
+ if (ret != NULL && !st->error_emitted && !st->close_emitted) {
741
+ scr_stream_emit_data(s, ret);
742
+ if (scr_exc_pending()) {
743
+ scr_stream_entry_release(st, ret);
744
+ return NULL;
745
+ }
746
+ }
747
+ return ret;
748
+ }
749
+
750
+ static void scr_stream_flow(ScrStream *s) {
751
+ ScrStreamState *st = s->st;
752
+ while (st->r.flowing == 1 && !scr_exc_pending()) {
753
+ void *b = scr_stream_read_n(s, -1);
754
+ if (b == NULL) break;
755
+ scr_stream_entry_release(st, b);
756
+ }
757
+ }
758
+
759
+ static void scr_stream_resume_kick(ScrStream *s) {
760
+ ScrStreamState *st = s->st;
761
+ if (st->r.resume_scheduled) return;
762
+ st->r.resume_scheduled = true;
763
+ scr_st_tick(s, SCR_ST_RESUME, NULL, NULL);
764
+ }
765
+
766
+ /* ── the dyn-flavored completion callbacks (the JS lane) ──────────────── */
767
+
768
+ /* The JS lane's option callbacks and underscore methods declare their
769
+ * parameters implicitly-any: the compiler-emitted invoke thunks box the
770
+ * chunk/encoding/error into dyn, and the COMPLETION callback arrives as
771
+ * a callable dyn function whose glue lands here — arguments arrive as
772
+ * borrowed DOM values: the error is null/undefined (none), an
773
+ * error-shaped object ({message}), or a string; transform/flush data is
774
+ * bytes, a string, or absent. clo->caps[0] boxes the retained stream. */
775
+
776
+ static ScrError *scr_stream_dyn_err(ScrDyn *const *args, size_t argc) {
777
+ if (argc < 1) return NULL;
778
+ const ScrDyn *a = args[0];
779
+ if (a->kind == SCR_DYN_UNDEF || a->kind == SCR_DYN_NULL) return NULL;
780
+ if (a->kind == SCR_DYN_STR) {
781
+ ScrError *e = scr_error_new(SCR_ERR_ERROR, a->v.str);
782
+ return e;
783
+ }
784
+ if (a->kind == SCR_DYN_OBJ) {
785
+ ScrDyn *m = scr_dyn_obj_get((ScrDyn *)a, "message", 7);
786
+ ScrStr *msg = m && m->kind == SCR_DYN_STR ? scr_str_retain(m->v.str) : scr_str_new("", 0);
787
+ ScrError *e = scr_error_new(SCR_ERR_ERROR, msg);
788
+ scr_str_release(msg);
789
+ return e;
790
+ }
791
+ /* any other truthy value: Node would wrap it — approximate with "" */
792
+ ScrStr *empty = scr_str_new("", 0);
793
+ ScrError *e = scr_error_new(SCR_ERR_ERROR, empty);
794
+ scr_str_release(empty);
795
+ return e;
796
+ }
797
+
798
+ static ScrStream *scr_stream_dyn_recv(ScrClosure *clo) {
799
+ return scr_box_get_ref(clo->caps[0]);
800
+ }
801
+
802
+ static ScrDyn *scr_stream_done_dyn_ret(void) { return scr_dyn_retain(scr_dyn_undefined()); }
803
+
804
+ ScrDyn *scr_stream_done_dyn_w(ScrClosure *clo, ScrDyn *const *args, size_t argc) {
805
+ ScrStream *s = scr_stream_dyn_recv(clo);
806
+ scr_stream_write_done(s, scr_stream_dyn_err(args, argc));
807
+ scr_stream_release(s);
808
+ return scr_stream_done_dyn_ret();
809
+ }
810
+
811
+ ScrDyn *scr_stream_done_dyn_f(ScrClosure *clo, ScrDyn *const *args, size_t argc) {
812
+ ScrStream *s = scr_stream_dyn_recv(clo);
813
+ scr_stream_final_done(s, scr_stream_dyn_err(args, argc));
814
+ scr_stream_release(s);
815
+ return scr_stream_done_dyn_ret();
816
+ }
817
+
818
+ ScrDyn *scr_stream_done_dyn_d(ScrClosure *clo, ScrDyn *const *args, size_t argc) {
819
+ ScrStream *s = scr_stream_dyn_recv(clo);
820
+ scr_stream_destroy_done(s, scr_stream_dyn_err(args, argc));
821
+ scr_stream_release(s);
822
+ return scr_stream_done_dyn_ret();
823
+ }
824
+
825
+ static void scr_stream_dyn_data(ScrDyn *const *args, size_t argc,
826
+ ScrBytes **data, ScrStr **data_str) {
827
+ *data = NULL;
828
+ *data_str = NULL;
829
+ if (argc < 2) return;
830
+ const ScrDyn *a = args[1];
831
+ if (a->kind == SCR_DYN_BYTES) *data = scr_dyn_bytes_copy_out(a);
832
+ else if (a->kind == SCR_DYN_STR) *data_str = scr_str_retain(a->v.str);
833
+ }
834
+
835
+ ScrDyn *scr_stream_done_dyn_t(ScrClosure *clo, ScrDyn *const *args, size_t argc) {
836
+ ScrStream *s = scr_stream_dyn_recv(clo);
837
+ ScrBytes *data;
838
+ ScrStr *data_str;
839
+ scr_stream_dyn_data(args, argc, &data, &data_str);
840
+ scr_stream_transform_done(s, scr_stream_dyn_err(args, argc), data, data_str);
841
+ scr_stream_release(s);
842
+ return scr_stream_done_dyn_ret();
843
+ }
844
+
845
+ ScrDyn *scr_stream_done_dyn_l(ScrClosure *clo, ScrDyn *const *args, size_t argc) {
846
+ ScrStream *s = scr_stream_dyn_recv(clo);
847
+ ScrBytes *data;
848
+ ScrStr *data_str;
849
+ scr_stream_dyn_data(args, argc, &data, &data_str);
850
+ scr_stream_flush_done(s, scr_stream_dyn_err(args, argc), data, data_str);
851
+ scr_stream_release(s);
852
+ return scr_stream_done_dyn_ret();
853
+ }
854
+
855
+ /* Checked-dynamic chunks (the JS lane's push/write of any-typed values):
856
+ * dispatch by DOM tag — bytes copy out, strings convert utf8, null takes
857
+ * the null path (EOF for push; ERR_STREAM_NULL_VALUES for write), and
858
+ * anything else throws the write TypeError (Node would throw
859
+ * ERR_INVALID_ARG_TYPE — approximated by the same shape). Borrow d. */
860
+ bool scr_stream_push_dyn(ScrStream *s, const ScrDyn *d) {
861
+ switch (d->kind) {
862
+ case SCR_DYN_BYTES: {
863
+ ScrBytes *b = scr_dyn_bytes_copy_out(d);
864
+ bool r = scr_stream_push(s, b);
865
+ scr_bytes_release(b);
866
+ return r;
867
+ }
868
+ case SCR_DYN_STR:
869
+ return scr_stream_push_str(s, d->v.str);
870
+ case SCR_DYN_NULL:
871
+ return scr_stream_push_null(s);
872
+ default: {
873
+ static const char msg[] = "May not write null values to stream";
874
+ scr_throw_error_msg_code(SCR_ERR_TYPE, msg, sizeof msg - 1, "ERR_STREAM_NULL_VALUES");
875
+ return false;
876
+ }
877
+ }
878
+ }
879
+
880
+ bool scr_stream_write_dyn(ScrStream *s, const ScrDyn *d, ScrClosure *cb) {
881
+ switch (d->kind) {
882
+ case SCR_DYN_BYTES: {
883
+ ScrBytes *b = scr_dyn_bytes_copy_out(d);
884
+ bool r = scr_stream_write(s, b, cb);
885
+ scr_bytes_release(b);
886
+ return r;
887
+ }
888
+ case SCR_DYN_STR:
889
+ return scr_stream_write_str(s, d->v.str, cb);
890
+ default:
891
+ if (cb) scr_closure_release(cb);
892
+ return scr_stream_write_null(s);
893
+ }
894
+ }
895
+
896
+ /* ── the for-await surface (readable.nextChunk) ───────────────────────── */
897
+
898
+ /* Resolve the parked waiter with a taken chunk (moves), the EOF
899
+ * sentinel, or dyn-boxed content per next_dyn. */
900
+ static void scr_stream_next_fulfill(ScrStreamState *st, ScrPromise *w /*moves*/, void *chunk /*moves or NULL=EOF*/) {
901
+ if (st->r.next_dyn) {
902
+ ScrDyn *d;
903
+ if (chunk == NULL) {
904
+ d = scr_dyn_retain(scr_dyn_undefined()); /* the EOF sentinel */
905
+ } else if (st->r.encoded) {
906
+ d = scr_dyn_new_str((ScrStr *)chunk);
907
+ scr_str_release((ScrStr *)chunk);
908
+ } else {
909
+ d = scr_dyn_new_buffer_copy((ScrBytes *)chunk);
910
+ scr_bytes_release((ScrBytes *)chunk);
911
+ }
912
+ scr_promise_fulfill_ref(w, d, scr_dyn_retain_v, scr_dyn_release_v, NULL);
913
+ } else {
914
+ ScrBytes *b = chunk ? (ScrBytes *)chunk : scr_bytes_new(SCR_BYTES_U8, 0);
915
+ scr_promise_fulfill_ref(w, b, scr_bytes_retain_v, scr_bytes_release_v, NULL);
916
+ }
917
+ scr_promise_release(w);
918
+ }
919
+
920
+ /* Settle the parked waiter if the stream's state can answer now: an
921
+ * error rejects, buffered content fulfills (one whole entry in from
922
+ * mode, the whole buffer otherwise — Node's iterator read()), EOF
923
+ * fulfills the sentinel. Called from push/eof/destroy transitions. */
924
+ static void scr_stream_settle_next(ScrStream *s) {
925
+ ScrStreamState *st = s->st;
926
+ if (!st->r.next_waiter) return;
927
+ if (st->errored) {
928
+ ScrPromise *w = st->r.next_waiter;
929
+ st->r.next_waiter = NULL;
930
+ st->next_err_consumed = true;
931
+ scr_throw_obj(scr_error_retain(st->errored), &scr_error_retain_v, &scr_error_release_v,
932
+ scr_error_trace_arg());
933
+ scr_promise_reject_pending(w);
934
+ scr_promise_release(w);
935
+ return;
936
+ }
937
+ if (st->r.length > 0) {
938
+ ScrPromise *w = st->r.next_waiter;
939
+ st->r.next_waiter = NULL;
940
+ void *chunk = scr_stream_rbuf_take(st, st->r.object_entries ? 1 : st->r.length);
941
+ scr_stream_next_fulfill(st, w, chunk);
942
+ if (st->r.ended && st->r.length == 0) scr_stream_end_readable(s);
943
+ return;
944
+ }
945
+ if (st->r.ended || st->destroyed) {
946
+ if (st->r.ended && !st->r.end_emitted) {
947
+ /* Node's iterator completes AFTER 'end' (and its autoDestroy):
948
+ * queue the sentinel BEHIND the pending 'end' tick, so code after
949
+ * the loop reads readableEnded/destroyed true. */
950
+ scr_stream_end_readable(s);
951
+ scr_st_tick(s, SCR_ST_NEXT_EOF, NULL, NULL);
952
+ return;
953
+ }
954
+ ScrPromise *w = st->r.next_waiter;
955
+ st->r.next_waiter = NULL;
956
+ scr_stream_next_fulfill(st, w, NULL);
957
+ }
958
+ }
959
+
960
+ static ScrPromise *scr_stream_next_chunk_impl(ScrStream *s, bool dyn) {
961
+ ScrStreamState *st = s->st;
962
+ ScrPromise *p = scr_promise_new();
963
+ if (!st->has_r) {
964
+ st->r.next_dyn = dyn;
965
+ scr_stream_next_fulfill(st, scr_promise_retain(p), NULL);
966
+ return p;
967
+ }
968
+ if (st->r.next_waiter) {
969
+ /* the desugared loop awaits each chunk before asking again */
970
+ static const char msg[] = "concurrent for-await iteration of one stream is not supported yet";
971
+ scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1);
972
+ scr_promise_reject_pending(p);
973
+ return p;
974
+ }
975
+ st->r.next_dyn = dyn;
976
+ st->r.next_waiter = scr_promise_retain(p);
977
+ /* answer from the current state, or kick a _read and park */
978
+ scr_stream_settle_next(s);
979
+ if (st->r.next_waiter && !st->r.reading && !st->r.ended && !st->destroyed) {
980
+ st->r.need_readable = true;
981
+ scr_stream_call_read(s);
982
+ if (scr_exc_pending()) {
983
+ ScrPromise *w = st->r.next_waiter;
984
+ st->r.next_waiter = NULL;
985
+ scr_promise_reject_pending(w);
986
+ scr_promise_release(w);
987
+ return p;
988
+ }
989
+ /* a synchronous push may have answered already */
990
+ scr_stream_settle_next(s);
991
+ }
992
+ return p;
993
+ }
994
+
995
+ ScrPromise *scr_stream_next_chunk(ScrStream *s) {
996
+ ScrStreamState *st = s->st;
997
+ if (st->has_r && st->r.encoded) {
998
+ /* the typed lane's chunk is a Buffer; string chunks would confuse */
999
+ ScrPromise *p = scr_promise_new();
1000
+ static const char msg[] =
1001
+ "for-await over a stream with an encoding set is not supported yet in "
1002
+ "typed code (chunks are Buffers here)";
1003
+ scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1);
1004
+ scr_promise_reject_pending(p);
1005
+ return p;
1006
+ }
1007
+ return scr_stream_next_chunk_impl(s, false);
1008
+ }
1009
+
1010
+ ScrPromise *scr_stream_next_chunk_dyn(ScrStream *s) {
1011
+ return scr_stream_next_chunk_impl(s, true);
1012
+ }
1013
+
1014
+ /* ── Readable.from (array-seeded object-entry streams) ────────────────── */
1015
+
1016
+ /* +1 stream fully seeded from the array's elements (strings or Buffers —
1017
+ * one WHOLE entry per element, Node's objectMode delivery; hwm 1) and
1018
+ * already EOF'd. Borrows arr. */
1019
+ ScrStream *scr_stream_from_arr(ScrArr *arr, bool strings) {
1020
+ ScrStream *s = scr_stream_alloc(&scr_readable_vt, "Readable", true, false,
1021
+ 1, -1, true, true, true);
1022
+ ScrStreamState *st = s->st;
1023
+ st->r.object_entries = true;
1024
+ st->r.encoded = strings;
1025
+ if (strings) st->r.enc = scr_str_new("utf8", 4);
1026
+ for (size_t i = 0; i < arr->len; i++) {
1027
+ scr_stream_rbuf_push(st, scr_arr_get_ref(arr, (double)i), false);
1028
+ }
1029
+ scr_stream_push_null(s);
1030
+ return s;
1031
+ }
1032
+
1033
+ /* ── the registration hook ('data'/'readable' listeners kick flow) ────── */
1034
+
1035
+ static void scr_stream_on_listener(ScrEmitter *em, ScrStr *name) {
1036
+ if (!scr_stream_is(em)) return;
1037
+ ScrStream *s = (ScrStream *)em;
1038
+ ScrStreamState *st = s->st;
1039
+ if (!st || !st->has_r) return;
1040
+ if (name->len == 4 && memcmp(name->data, "data", 4) == 0) {
1041
+ st->r.readable_listening = scr_emitter_has(em, "readable");
1042
+ if (st->r.flowing != 0) {
1043
+ st->r.flowing = 1;
1044
+ scr_stream_resume_kick(s);
1045
+ }
1046
+ } else if (name->len == 8 && memcmp(name->data, "readable", 8) == 0) {
1047
+ if (!st->r.end_emitted && !st->r.readable_listening) {
1048
+ st->r.readable_listening = true;
1049
+ st->r.need_readable = true;
1050
+ st->r.flowing = 0;
1051
+ st->r.emitted_readable = false;
1052
+ if (st->r.length > 0) {
1053
+ scr_stream_emit_readable_nt(s);
1054
+ } else if (!st->r.reading) {
1055
+ /* nReadingNextTick: a read(0) kick on a tick */
1056
+ scr_st_tick(s, SCR_ST_MAYBE_MORE, NULL, NULL);
1057
+ st->r.maybe_more_scheduled = true;
1058
+ }
1059
+ }
1060
+ }
1061
+ }
1062
+
1063
+ /* ── writable internals ───────────────────────────────────────────────── */
1064
+
1065
+ static void scr_stream_wq_push(ScrStreamState *st, ScrBytes *chunk /*moves*/, ScrClosure *cb /*moves*/) {
1066
+ if (st->w.n == st->w.cap) {
1067
+ st->w.cap = st->w.cap ? st->w.cap * 2 : 4;
1068
+ st->w.q = realloc(st->w.q, st->w.cap * sizeof *st->w.q);
1069
+ if (!st->w.q) scr_stream_oom();
1070
+ }
1071
+ st->w.q[st->w.n].chunk = chunk;
1072
+ st->w.q[st->w.n].cb = cb;
1073
+ st->w.n++;
1074
+ }
1075
+
1076
+ static void scr_stream_do_write(ScrStream *s, ScrBytes *chunk /*moves*/, ScrClosure *cb /*moves*/) {
1077
+ ScrStreamState *st = s->st;
1078
+ st->w.writing = true;
1079
+ st->w.wsync = true;
1080
+ st->w.inflight_len = chunk->len;
1081
+ st->w.inflight_cb = cb;
1082
+ if (st->passthrough && st->transform_cb == NULL) {
1083
+ /* identity: push through, complete synchronously */
1084
+ scr_stream_add_chunk(s, chunk, false);
1085
+ scr_bytes_release(chunk);
1086
+ if (!scr_exc_pending()) scr_stream_write_done(s, NULL);
1087
+ st->w.wsync = false;
1088
+ return;
1089
+ }
1090
+ if (st->is_transform || st->passthrough) {
1091
+ if (st->transform_cb == NULL) {
1092
+ scr_bytes_release(chunk);
1093
+ ScrError *e = scr_stream_mkerr("ERR_METHOD_NOT_IMPLEMENTED", "The _transform() method is not implemented");
1094
+ st->w.wsync = false;
1095
+ scr_stream_error_or_destroy(s, e);
1096
+ scr_error_release(e);
1097
+ return;
1098
+ }
1099
+ st->transform_inv(st->transform_cb, s, chunk);
1100
+ scr_bytes_release(chunk);
1101
+ st->w.wsync = false;
1102
+ return;
1103
+ }
1104
+ if (st->w.write_cb == NULL) {
1105
+ scr_bytes_release(chunk);
1106
+ ScrError *e = scr_stream_mkerr("ERR_METHOD_NOT_IMPLEMENTED", "The _write() method is not implemented");
1107
+ st->w.wsync = false;
1108
+ scr_stream_error_or_destroy(s, e);
1109
+ scr_error_release(e);
1110
+ return;
1111
+ }
1112
+ st->w.write_inv(st->w.write_cb, s, chunk);
1113
+ scr_bytes_release(chunk);
1114
+ st->w.wsync = false;
1115
+ }
1116
+
1117
+ /* afterWrite — Node's exact order: 'drain' first (when the queue is
1118
+ * empty), THEN the user's write(chunk, cb) callback, then finishMaybe.
1119
+ * The buffered queue was already restarted by write_done (Node's onwrite
1120
+ * calls clearBuffer before afterWrite). cb moves. */
1121
+ static void scr_stream_after_write(ScrStream *s, ScrClosure *cb) {
1122
+ ScrStreamState *st = s->st;
1123
+ if (st->w.length == 0 && st->w.need_drain && !st->w.ending && !st->destroyed) {
1124
+ st->w.need_drain = false;
1125
+ scr_stream_emit0(s, "drain");
1126
+ if (scr_exc_pending()) {
1127
+ if (cb) scr_closure_release(cb);
1128
+ return;
1129
+ }
1130
+ /* pipe sources waiting on this drain resume now */
1131
+ while (st->drain_srcs.n > 0) {
1132
+ ScrStream *src = st->drain_srcs.src[--st->drain_srcs.n];
1133
+ if (src->st && src->st->await_drain > 0 && --src->st->await_drain == 0 && !src->st->destroyed) {
1134
+ src->st->r.flowing = 1;
1135
+ scr_stream_resume_kick(src);
1136
+ }
1137
+ scr_stream_release(src);
1138
+ }
1139
+ }
1140
+ if (cb) {
1141
+ ((void (*)(ScrClosure *))cb->fn)(cb);
1142
+ scr_closure_release(cb);
1143
+ if (scr_exc_pending()) return;
1144
+ }
1145
+ scr_stream_finish_maybe(s);
1146
+ }
1147
+
1148
+ static void scr_stream_clear_buffer(ScrStream *s) {
1149
+ ScrStreamState *st = s->st;
1150
+ while (!st->w.writing && st->w.head < st->w.n && st->w.corked == 0 &&
1151
+ !st->destroyed && !scr_exc_pending()) {
1152
+ ScrWEntry e = st->w.q[st->w.head++];
1153
+ if (st->w.head == st->w.n) {
1154
+ st->w.head = 0;
1155
+ st->w.n = 0;
1156
+ }
1157
+ scr_stream_do_write(s, e.chunk, e.cb);
1158
+ }
1159
+ }
1160
+
1161
+ /* prefinish + 'finish' scheduling once the writable side has fully
1162
+ * drained after end(). */
1163
+ static void scr_stream_finish_maybe(ScrStream *s) {
1164
+ ScrStreamState *st = s->st;
1165
+ if (!st->w.ending || st->w.writing || st->w.head < st->w.n || st->w.finished ||
1166
+ st->destroyed || st->w.finish_scheduled) {
1167
+ return;
1168
+ }
1169
+ if (!st->w.prefinished) {
1170
+ if ((st->is_transform || st->passthrough) && !st->flush_called) {
1171
+ st->flush_called = true;
1172
+ if (st->flush_cb) {
1173
+ st->flush_inv(st->flush_cb, s);
1174
+ return; /* flush_done resumes the finish path */
1175
+ }
1176
+ /* no flush: EOF the readable side now */
1177
+ scr_stream_push_null(s);
1178
+ if (scr_exc_pending()) return;
1179
+ }
1180
+ if (st->w.final_cb && !st->w.final_called) {
1181
+ st->w.final_called = true;
1182
+ st->w.final_inv(st->w.final_cb, s);
1183
+ return; /* final_done resumes the finish path */
1184
+ }
1185
+ st->w.prefinished = true;
1186
+ scr_stream_emit0(s, "prefinish");
1187
+ if (scr_exc_pending()) return;
1188
+ }
1189
+ st->w.finish_scheduled = true;
1190
+ scr_st_tick(s, SCR_ST_FINISH, NULL, NULL);
1191
+ }
1192
+
1193
+ /* ── construction ─────────────────────────────────────────────────────── */
1194
+
1195
+ static ScrStreamState *scr_stream_state_new(bool has_r, bool has_w,
1196
+ double rhwm, double whwm, bool auto_destroy,
1197
+ bool emit_close, bool allow_half_open) {
1198
+ ScrStreamState *st = calloc(1, sizeof *st);
1199
+ if (!st) scr_stream_oom();
1200
+ st->has_r = has_r;
1201
+ st->has_w = has_w;
1202
+ st->auto_destroy = auto_destroy;
1203
+ st->emit_close = emit_close;
1204
+ st->allow_half_open = allow_half_open;
1205
+ st->r.hwm = rhwm >= 0 ? (size_t)rhwm : SCR_STREAM_DEFAULT_HWM;
1206
+ st->w.hwm = whwm >= 0 ? (size_t)whwm : SCR_STREAM_DEFAULT_HWM;
1207
+ st->r.flowing = -1;
1208
+ /* Node's ReadableState.sync starts TRUE: pushes before the first _read
1209
+ * attempt take the buffered path (never the direct 'data' emit). */
1210
+ st->r.in_read_sync = true;
1211
+ return st;
1212
+ }
1213
+
1214
+ static ScrStream *scr_stream_alloc(const ScrVt *vt, const char *cls, bool has_r, bool has_w,
1215
+ double rhwm, double whwm, bool auto_destroy,
1216
+ bool emit_close, bool allow_half_open) {
1217
+ ScrStream *s = scr_cyc_alloc(sizeof *s, &scr_stream_trace, &scr_stream_gcfree);
1218
+ s->rc = 1;
1219
+ s->vt = vt;
1220
+ s->reg = NULL;
1221
+ s->cls = cls;
1222
+ s->st = scr_stream_state_new(has_r, has_w, rhwm, whwm, auto_destroy, emit_close, allow_half_open);
1223
+ scr_obj_alloc_note();
1224
+ return s;
1225
+ }
1226
+
1227
+ ScrStream *scr_stream_new_readable(double hwm, bool auto_destroy, bool emit_close,
1228
+ ScrClosure *read, ScrStreamReadInv read_inv,
1229
+ ScrClosure *destroy, ScrStreamErrInv destroy_inv) {
1230
+ ScrStream *s = scr_stream_alloc(&scr_readable_vt, "Readable", true, false,
1231
+ hwm, -1, auto_destroy, emit_close, true);
1232
+ s->st->r.read_cb = read;
1233
+ s->st->r.read_inv = read_inv;
1234
+ s->st->destroy_cb = destroy;
1235
+ s->st->destroy_inv = destroy_inv;
1236
+ return s;
1237
+ }
1238
+
1239
+ ScrStream *scr_stream_new_writable(double hwm, bool auto_destroy, bool emit_close,
1240
+ ScrClosure *write, ScrStreamChunkInv write_inv,
1241
+ ScrClosure *final_cb, ScrStreamPlainInv final_inv,
1242
+ ScrClosure *destroy, ScrStreamErrInv destroy_inv) {
1243
+ ScrStream *s = scr_stream_alloc(&scr_writable_vt, "Writable", false, true,
1244
+ -1, hwm, auto_destroy, emit_close, true);
1245
+ s->st->w.write_cb = write;
1246
+ s->st->w.write_inv = write_inv;
1247
+ s->st->w.final_cb = final_cb;
1248
+ s->st->w.final_inv = final_inv;
1249
+ s->st->destroy_cb = destroy;
1250
+ s->st->destroy_inv = destroy_inv;
1251
+ return s;
1252
+ }
1253
+
1254
+ ScrStream *scr_stream_new_duplex(double rhwm, double whwm, bool auto_destroy,
1255
+ bool emit_close, bool allow_half_open, bool readable_side, bool writable_side,
1256
+ ScrClosure *read, ScrStreamReadInv read_inv,
1257
+ ScrClosure *write, ScrStreamChunkInv write_inv,
1258
+ ScrClosure *final_cb, ScrStreamPlainInv final_inv,
1259
+ ScrClosure *destroy, ScrStreamErrInv destroy_inv) {
1260
+ ScrStream *s = scr_stream_alloc(&scr_duplex_vt, "Duplex", readable_side, writable_side,
1261
+ rhwm, whwm, auto_destroy, emit_close, allow_half_open);
1262
+ if (!readable_side) {
1263
+ /* Node: a { readable: false } duplex reads as an already-ended half */
1264
+ s->st->r.ended = true;
1265
+ s->st->r.end_emitted = true;
1266
+ }
1267
+ if (!writable_side) {
1268
+ s->st->w.ending = true;
1269
+ s->st->w.finished = true;
1270
+ }
1271
+ s->st->r.read_cb = read;
1272
+ s->st->r.read_inv = read_inv;
1273
+ s->st->w.write_cb = write;
1274
+ s->st->w.write_inv = write_inv;
1275
+ s->st->w.final_cb = final_cb;
1276
+ s->st->w.final_inv = final_inv;
1277
+ s->st->destroy_cb = destroy;
1278
+ s->st->destroy_inv = destroy_inv;
1279
+ return s;
1280
+ }
1281
+
1282
+ static ScrStream *scr_stream_new_transformish(const ScrVt *vt, const char *cls, bool passthrough,
1283
+ double rhwm, double whwm, bool auto_destroy, bool emit_close, bool allow_half_open,
1284
+ ScrClosure *transform, ScrStreamChunkInv transform_inv,
1285
+ ScrClosure *flush, ScrStreamPlainInv flush_inv,
1286
+ ScrClosure *destroy, ScrStreamErrInv destroy_inv) {
1287
+ ScrStream *s = scr_stream_alloc(vt, cls, true, true, rhwm, whwm,
1288
+ auto_destroy, emit_close, allow_half_open);
1289
+ s->st->is_transform = !passthrough;
1290
+ s->st->passthrough = passthrough;
1291
+ /* Node's Transform constructor: the readable half starts with
1292
+ * sync=false and needReadable=true (transformed data direct-emits). */
1293
+ s->st->r.in_read_sync = false;
1294
+ s->st->r.need_readable = true;
1295
+ s->st->transform_cb = transform;
1296
+ s->st->transform_inv = transform_inv;
1297
+ s->st->flush_cb = flush;
1298
+ s->st->flush_inv = flush_inv;
1299
+ s->st->destroy_cb = destroy;
1300
+ s->st->destroy_inv = destroy_inv;
1301
+ return s;
1302
+ }
1303
+
1304
+ ScrStream *scr_stream_new_transform(double rhwm, double whwm, bool auto_destroy,
1305
+ bool emit_close, bool allow_half_open, bool readable_side, bool writable_side,
1306
+ ScrClosure *transform, ScrStreamChunkInv transform_inv,
1307
+ ScrClosure *flush, ScrStreamPlainInv flush_inv,
1308
+ ScrClosure *destroy, ScrStreamErrInv destroy_inv) {
1309
+ (void)readable_side;
1310
+ (void)writable_side;
1311
+ return scr_stream_new_transformish(&scr_transform_vt, "Transform", false,
1312
+ rhwm, whwm, auto_destroy, emit_close, allow_half_open,
1313
+ transform, transform_inv, flush, flush_inv, destroy, destroy_inv);
1314
+ }
1315
+
1316
+ ScrStream *scr_stream_new_passthrough(double rhwm, double whwm, bool auto_destroy,
1317
+ bool emit_close, bool allow_half_open, bool readable_side, bool writable_side,
1318
+ ScrClosure *transform, ScrStreamChunkInv transform_inv,
1319
+ ScrClosure *flush, ScrStreamPlainInv flush_inv,
1320
+ ScrClosure *destroy, ScrStreamErrInv destroy_inv) {
1321
+ (void)readable_side;
1322
+ (void)writable_side;
1323
+ ScrStream *s = scr_stream_new_transformish(&scr_passthrough_vt, "PassThrough",
1324
+ transform == NULL, rhwm, whwm, auto_destroy, emit_close, allow_half_open,
1325
+ transform, transform_inv, flush, flush_inv, destroy, destroy_inv);
1326
+ if (transform != NULL) s->st->is_transform = true;
1327
+ return s;
1328
+ }
1329
+
1330
+ /* ── subclass initialization (the emitted super(options) call) ────────── */
1331
+
1332
+ /* A compiler-emitted `extends Readable` subclass allocates its own struct
1333
+ * (vt stamped with the SUBCLASS vtable, display name stamped, registry
1334
+ * and state NULL — the emitter-subclass story plus one slot); super(
1335
+ * options) lands here to build the stream state. The callback closures
1336
+ * move; overridden underscore methods arrive as compiler-synthesized
1337
+ * wrapper closures dispatching through the vt, so a further-derived
1338
+ * override wins even when an inherited constructor runs the init. */
1339
+
1340
+ void scr_stream_init_readable(ScrStream *s, double hwm, bool auto_destroy, bool emit_close,
1341
+ ScrClosure *read, ScrStreamReadInv read_inv,
1342
+ ScrClosure *destroy, ScrStreamErrInv destroy_inv) {
1343
+ ScrStreamState *st = scr_stream_state_new(true, false, hwm, -1, auto_destroy, emit_close, true);
1344
+ s->st = st;
1345
+ st->r.read_cb = read;
1346
+ st->r.read_inv = read_inv;
1347
+ st->destroy_cb = destroy;
1348
+ st->destroy_inv = destroy_inv;
1349
+ }
1350
+
1351
+ void scr_stream_init_writable(ScrStream *s, double hwm, bool auto_destroy, bool emit_close,
1352
+ ScrClosure *write, ScrStreamChunkInv write_inv,
1353
+ ScrClosure *final_cb, ScrStreamPlainInv final_inv,
1354
+ ScrClosure *destroy, ScrStreamErrInv destroy_inv) {
1355
+ ScrStreamState *st = scr_stream_state_new(false, true, -1, hwm, auto_destroy, emit_close, true);
1356
+ s->st = st;
1357
+ st->w.write_cb = write;
1358
+ st->w.write_inv = write_inv;
1359
+ st->w.final_cb = final_cb;
1360
+ st->w.final_inv = final_inv;
1361
+ st->destroy_cb = destroy;
1362
+ st->destroy_inv = destroy_inv;
1363
+ }
1364
+
1365
+ void scr_stream_init_duplex(ScrStream *s, double rhwm, double whwm, bool auto_destroy,
1366
+ bool emit_close, bool allow_half_open, bool readable_side, bool writable_side,
1367
+ ScrClosure *read, ScrStreamReadInv read_inv,
1368
+ ScrClosure *write, ScrStreamChunkInv write_inv,
1369
+ ScrClosure *final_cb, ScrStreamPlainInv final_inv,
1370
+ ScrClosure *destroy, ScrStreamErrInv destroy_inv) {
1371
+ ScrStreamState *st = scr_stream_state_new(readable_side, writable_side, rhwm, whwm,
1372
+ auto_destroy, emit_close, allow_half_open);
1373
+ s->st = st;
1374
+ if (!readable_side) {
1375
+ st->r.ended = true;
1376
+ st->r.end_emitted = true;
1377
+ }
1378
+ if (!writable_side) {
1379
+ st->w.ending = true;
1380
+ st->w.finished = true;
1381
+ }
1382
+ st->r.read_cb = read;
1383
+ st->r.read_inv = read_inv;
1384
+ st->w.write_cb = write;
1385
+ st->w.write_inv = write_inv;
1386
+ st->w.final_cb = final_cb;
1387
+ st->w.final_inv = final_inv;
1388
+ st->destroy_cb = destroy;
1389
+ st->destroy_inv = destroy_inv;
1390
+ }
1391
+
1392
+ static void scr_stream_init_transformish(ScrStream *s, bool passthrough,
1393
+ double rhwm, double whwm, bool auto_destroy, bool emit_close, bool allow_half_open,
1394
+ ScrClosure *transform, ScrStreamChunkInv transform_inv,
1395
+ ScrClosure *flush, ScrStreamPlainInv flush_inv,
1396
+ ScrClosure *destroy, ScrStreamErrInv destroy_inv) {
1397
+ ScrStreamState *st = scr_stream_state_new(true, true, rhwm, whwm,
1398
+ auto_destroy, emit_close, allow_half_open);
1399
+ s->st = st;
1400
+ st->is_transform = !passthrough;
1401
+ st->passthrough = passthrough;
1402
+ /* the Transform constructor's readable-half stance (see new_transformish) */
1403
+ st->r.in_read_sync = false;
1404
+ st->r.need_readable = true;
1405
+ st->transform_cb = transform;
1406
+ st->transform_inv = transform_inv;
1407
+ st->flush_cb = flush;
1408
+ st->flush_inv = flush_inv;
1409
+ st->destroy_cb = destroy;
1410
+ st->destroy_inv = destroy_inv;
1411
+ }
1412
+
1413
+ void scr_stream_init_transform(ScrStream *s, double rhwm, double whwm, bool auto_destroy,
1414
+ bool emit_close, bool allow_half_open, bool readable_side, bool writable_side,
1415
+ ScrClosure *transform, ScrStreamChunkInv transform_inv,
1416
+ ScrClosure *flush, ScrStreamPlainInv flush_inv,
1417
+ ScrClosure *destroy, ScrStreamErrInv destroy_inv) {
1418
+ (void)readable_side;
1419
+ (void)writable_side;
1420
+ scr_stream_init_transformish(s, false, rhwm, whwm, auto_destroy, emit_close, allow_half_open,
1421
+ transform, transform_inv, flush, flush_inv, destroy, destroy_inv);
1422
+ }
1423
+
1424
+ void scr_stream_init_passthrough(ScrStream *s, double rhwm, double whwm, bool auto_destroy,
1425
+ bool emit_close, bool allow_half_open, bool readable_side, bool writable_side,
1426
+ ScrClosure *transform, ScrStreamChunkInv transform_inv,
1427
+ ScrClosure *flush, ScrStreamPlainInv flush_inv,
1428
+ ScrClosure *destroy, ScrStreamErrInv destroy_inv) {
1429
+ (void)readable_side;
1430
+ (void)writable_side;
1431
+ scr_stream_init_transformish(s, transform == NULL, rhwm, whwm, auto_destroy, emit_close,
1432
+ allow_half_open, transform, transform_inv, flush, flush_inv, destroy, destroy_inv);
1433
+ }
1434
+
1435
+ /* ── the underscore-method assignment surface ─────────────────────────
1436
+ * `r._read = fn` / `w._write = fn` (and _final/_destroy/_transform/
1437
+ * _flush) AFTER construction: Node assigns an own property that shadows
1438
+ * the prototype method, and the machinery calls through it from then on.
1439
+ * Here the same slot the option callbacks fill swaps its closure — the
1440
+ * old callback releases, the new one (+1, moves) takes the slot with its
1441
+ * compiler-emitted invoke thunk. In-flight operations already dispatched
1442
+ * through the OLD callback complete against it (they hold no further
1443
+ * reference); the next _read/_write/... dispatch uses the new one, which
1444
+ * is Node's own timing. A NULL state block (assignment before a
1445
+ * subclass's super()) cannot be reached from lowered code — super()
1446
+ * lowers before any statement that could see `this`. */
1447
+ void scr_stream_set_read(ScrStream *s, ScrClosure *cb /*moves*/, ScrStreamReadInv inv) {
1448
+ ScrStreamState *st = s->st;
1449
+ if (st->r.read_cb) scr_closure_release(st->r.read_cb);
1450
+ st->r.read_cb = cb;
1451
+ st->r.read_inv = inv;
1452
+ }
1453
+
1454
+ void scr_stream_set_write(ScrStream *s, ScrClosure *cb /*moves*/, ScrStreamChunkInv inv) {
1455
+ ScrStreamState *st = s->st;
1456
+ if (st->w.write_cb) scr_closure_release(st->w.write_cb);
1457
+ st->w.write_cb = cb;
1458
+ st->w.write_inv = inv;
1459
+ }
1460
+
1461
+ void scr_stream_set_final(ScrStream *s, ScrClosure *cb /*moves*/, ScrStreamPlainInv inv) {
1462
+ ScrStreamState *st = s->st;
1463
+ if (st->w.final_cb) scr_closure_release(st->w.final_cb);
1464
+ st->w.final_cb = cb;
1465
+ st->w.final_inv = inv;
1466
+ }
1467
+
1468
+ void scr_stream_set_destroy(ScrStream *s, ScrClosure *cb /*moves*/, ScrStreamErrInv inv) {
1469
+ ScrStreamState *st = s->st;
1470
+ if (st->destroy_cb) scr_closure_release(st->destroy_cb);
1471
+ st->destroy_cb = cb;
1472
+ st->destroy_inv = inv;
1473
+ }
1474
+
1475
+ void scr_stream_set_transform(ScrStream *s, ScrClosure *cb /*moves*/, ScrStreamChunkInv inv) {
1476
+ ScrStreamState *st = s->st;
1477
+ if (st->transform_cb) scr_closure_release(st->transform_cb);
1478
+ st->transform_cb = cb;
1479
+ st->transform_inv = inv;
1480
+ }
1481
+
1482
+ void scr_stream_set_flush(ScrStream *s, ScrClosure *cb /*moves*/, ScrStreamPlainInv inv) {
1483
+ ScrStreamState *st = s->st;
1484
+ if (st->flush_cb) scr_closure_release(st->flush_cb);
1485
+ st->flush_cb = cb;
1486
+ st->flush_inv = inv;
1487
+ }
1488
+
1489
+ /* ── dyn options (a checked-dynamic options record at construction) ─────
1490
+ *
1491
+ * The JS lane's `constructor(options) { super(options); }` forwarding and
1492
+ * `new Readable(dynVar)`: the record's SHAPE is runtime data, so the
1493
+ * option walk happens here — scalar options read with Node's own rules
1494
+ * (highWaterMark wins over the sided keys; autoDestroy/emitClose/
1495
+ * allowHalfOpen and the Duplex side toggles compare `!== false`; unknown
1496
+ * keys are ignored exactly like Node), and callback slots holding
1497
+ * callable values (Node's `typeof === 'function'` guard — anything else
1498
+ * leaves the slot to the prototype/fallback) bind as closures whose one
1499
+ * cap boxes the dyn callable; the invs below build the DOM arguments the
1500
+ * way the compiler-emitted dyn thunks do (chunk as Buffer-flavored bytes,
1501
+ * encoding 'buffer', the error via the boundary encoding, the completion
1502
+ * callback as a callable dyn over the *_done glue). Options that Node's
1503
+ * base WOULD consume but have no lowering here (objectMode truthy,
1504
+ * decodeStrings:false, non-utf8 defaultEncoding, signal, construct,
1505
+ * writev, and read/write/final on the transformish bases) throw the loud
1506
+ * unsupported Error — the compile-time fence's runtime twin. */
1507
+
1508
+ /* The boxed dyn callable (+1 — scr_box_get_ref retains; callers release
1509
+ * after the call). */
1510
+ static ScrDyn *scr_stream_dynopt_target(ScrClosure *cb) {
1511
+ return scr_box_get_ref(cb->caps[0]);
1512
+ }
1513
+
1514
+ static ScrDyn *scr_stream_dynopt_done(ScrStream *s, ScrDynThunk glue, uint32_t arity, const char *sig) {
1515
+ ScrClosure *c = scr_closure_new((void *)glue, 1);
1516
+ c->caps[0] = scr_box_new_obj(&scr_stream_retain_v, &scr_stream_release_v, &scr_stream_trace);
1517
+ scr_box_set_ref(c->caps[0], scr_stream_retain(s));
1518
+ return scr_dyn_new_func(c, glue, arity, sig, "callback");
1519
+ }
1520
+
1521
+ static void scr_stream_dynopt_read_inv(ScrClosure *cb, ScrStream *s, double size) {
1522
+ (void)s;
1523
+ ScrDyn *fn = scr_stream_dynopt_target(cb);
1524
+ ScrDyn *a = scr_dyn_new_num(size);
1525
+ ScrDyn *r = scr_dyn_call(fn, &a, 1, "read");
1526
+ scr_dyn_release(a);
1527
+ scr_dyn_release(fn);
1528
+ if (r != NULL) scr_dyn_release(r);
1529
+ }
1530
+
1531
+ static void scr_stream_dynopt_chunk3(ScrClosure *cb, ScrStream *s, ScrBytes *chunk,
1532
+ ScrDynThunk glue, const char *what) {
1533
+ ScrDyn *args[3];
1534
+ args[0] = scr_dyn_new_buffer_copy(chunk);
1535
+ ScrStr *enc = scr_str_new("buffer", 6);
1536
+ args[1] = scr_dyn_new_str(enc);
1537
+ scr_str_release(enc);
1538
+ args[2] = scr_stream_dynopt_done(s, glue, 1, "(error)");
1539
+ ScrDyn *fn = scr_stream_dynopt_target(cb);
1540
+ ScrDyn *r = scr_dyn_call(fn, args, 3, what);
1541
+ for (int i = 0; i < 3; i++) scr_dyn_release(args[i]);
1542
+ scr_dyn_release(fn);
1543
+ if (r != NULL) scr_dyn_release(r);
1544
+ }
1545
+
1546
+ static void scr_stream_dynopt_write_inv(ScrClosure *cb, ScrStream *s, ScrBytes *chunk) {
1547
+ scr_stream_dynopt_chunk3(cb, s, chunk, &scr_stream_done_dyn_w, "write");
1548
+ }
1549
+
1550
+ static void scr_stream_dynopt_transform_inv(ScrClosure *cb, ScrStream *s, ScrBytes *chunk) {
1551
+ ScrDyn *args[3];
1552
+ args[0] = scr_dyn_new_buffer_copy(chunk);
1553
+ ScrStr *enc = scr_str_new("buffer", 6);
1554
+ args[1] = scr_dyn_new_str(enc);
1555
+ scr_str_release(enc);
1556
+ args[2] = scr_stream_dynopt_done(s, &scr_stream_done_dyn_t, 2, "(error,data)");
1557
+ ScrDyn *fn = scr_stream_dynopt_target(cb);
1558
+ ScrDyn *r = scr_dyn_call(fn, args, 3, "transform");
1559
+ for (int i = 0; i < 3; i++) scr_dyn_release(args[i]);
1560
+ scr_dyn_release(fn);
1561
+ if (r != NULL) scr_dyn_release(r);
1562
+ }
1563
+
1564
+ static void scr_stream_dynopt_final_inv(ScrClosure *cb, ScrStream *s) {
1565
+ ScrDyn *fn = scr_stream_dynopt_target(cb);
1566
+ ScrDyn *a = scr_stream_dynopt_done(s, &scr_stream_done_dyn_f, 1, "(error)");
1567
+ ScrDyn *r = scr_dyn_call(fn, &a, 1, "final");
1568
+ scr_dyn_release(a);
1569
+ scr_dyn_release(fn);
1570
+ if (r != NULL) scr_dyn_release(r);
1571
+ }
1572
+
1573
+ static void scr_stream_dynopt_flush_inv(ScrClosure *cb, ScrStream *s) {
1574
+ ScrDyn *fn = scr_stream_dynopt_target(cb);
1575
+ ScrDyn *a = scr_stream_dynopt_done(s, &scr_stream_done_dyn_l, 2, "(error,data)");
1576
+ ScrDyn *r = scr_dyn_call(fn, &a, 1, "flush");
1577
+ scr_dyn_release(a);
1578
+ scr_dyn_release(fn);
1579
+ if (r != NULL) scr_dyn_release(r);
1580
+ }
1581
+
1582
+ static void scr_stream_dynopt_destroy_inv(ScrClosure *cb, ScrStream *s, ScrError *err) {
1583
+ ScrDyn *args[2];
1584
+ args[0] = err != NULL ? scr_dyn_from_error(err) : scr_dyn_new_null();
1585
+ args[1] = scr_stream_dynopt_done(s, &scr_stream_done_dyn_d, 1, "(error)");
1586
+ ScrDyn *fn = scr_stream_dynopt_target(cb);
1587
+ ScrDyn *r = scr_dyn_call(fn, args, 2, "destroy");
1588
+ scr_dyn_release(args[0]);
1589
+ scr_dyn_release(args[1]);
1590
+ scr_dyn_release(fn);
1591
+ if (r != NULL) scr_dyn_release(r);
1592
+ }
1593
+
1594
+ static ScrClosure *scr_stream_dynopt_clo(ScrDyn *fn, void *inv) {
1595
+ ScrClosure *c = scr_closure_new(inv, 1);
1596
+ c->caps[0] = scr_box_new_obj(&scr_dyn_retain_v, &scr_dyn_release_v, NULL);
1597
+ scr_box_set_ref(c->caps[0], scr_dyn_retain(fn));
1598
+ return c;
1599
+ }
1600
+
1601
+ typedef struct {
1602
+ double rhwm, whwm;
1603
+ bool auto_destroy, emit_close, allow_half_open;
1604
+ bool readable_side, writable_side;
1605
+ ScrStr *enc; /* owned canonical name, or NULL */
1606
+ /* BORROWED callable fields (owned by the options record). */
1607
+ ScrDyn *read, *write, *final_fn, *destroy, *transform, *flush;
1608
+ } ScrStreamDynOpts;
1609
+
1610
+ /* Node's `=== false` comparisons (autoDestroy/emitClose/allowHalfOpen and
1611
+ * the Duplex side toggles: `options.x !== false` — 0 does NOT disable). */
1612
+ static bool scr_stream_dynopt_false(const ScrDyn *v) {
1613
+ return v != NULL && v->kind == SCR_DYN_BOOL && !v->v.b;
1614
+ }
1615
+
1616
+ static double scr_stream_dynopt_hwm(const ScrDyn *o, const char *key, size_t klen) {
1617
+ ScrDyn *v = scr_dyn_obj_get((ScrDyn *)o, key, klen);
1618
+ if (v == NULL || v->kind != SCR_DYN_NUM || v->v.num < 0) return -1;
1619
+ return v->v.num;
1620
+ }
1621
+
1622
+ static ScrDyn *scr_stream_dynopt_fn(const ScrDyn *o, const char *key, size_t klen) {
1623
+ ScrDyn *v = scr_dyn_obj_get((ScrDyn *)o, key, klen);
1624
+ return v != NULL && v->kind == SCR_DYN_FUNC ? v : NULL; /* Node's typeof guard */
1625
+ }
1626
+
1627
+ static bool scr_stream_dynopt_truthy(const ScrDyn *o, const char *key, size_t klen) {
1628
+ ScrDyn *v = scr_dyn_obj_get((ScrDyn *)o, key, klen);
1629
+ return v != NULL && scr_dyn_truthy(v);
1630
+ }
1631
+
1632
+ static bool scr_stream_dynopt_unsupported(const char *msg, size_t len) {
1633
+ scr_throw_error_msg(SCR_ERR_ERROR, msg, len);
1634
+ return false;
1635
+ }
1636
+
1637
+ /* The runtime twin of the frontend's literal-encoding fold (BUF_ENCODINGS):
1638
+ * canonicalizes or throws Node's ERR_UNKNOWN_ENCODING TypeError. */
1639
+ static ScrStr *scr_stream_dynopt_encoding(const ScrStr *raw) {
1640
+ static const struct { const char *from; const char *to; } map[] = {
1641
+ {"utf8", "utf8"}, {"utf-8", "utf8"}, {"hex", "hex"}, {"base64", "base64"},
1642
+ {"base64url", "base64url"}, {"latin1", "latin1"}, {"binary", "latin1"},
1643
+ {"ascii", "ascii"}, {"utf16le", "utf16le"}, {"utf-16le", "utf16le"},
1644
+ {"ucs2", "utf16le"}, {"ucs-2", "utf16le"},
1645
+ };
1646
+ for (size_t i = 0; i < sizeof map / sizeof map[0]; i++) {
1647
+ size_t n = strlen(map[i].from);
1648
+ if (raw->len == n && memcmp(raw->data, map[i].from, n) == 0) {
1649
+ return scr_str_new(map[i].to, strlen(map[i].to));
1650
+ }
1651
+ }
1652
+ char msg[128];
1653
+ int len = snprintf(msg, sizeof msg, "Unknown encoding: %.*s",
1654
+ (int)(raw->len < 64 ? raw->len : 64), raw->data);
1655
+ scr_throw_error_msg_code(SCR_ERR_TYPE, msg, (size_t)(len < 0 ? 0 : len), "ERR_UNKNOWN_ENCODING");
1656
+ return NULL;
1657
+ }
1658
+
1659
+ /* Parses a dyn options record for one base ('r','w','d','t','p'); false
1660
+ * with the exception pending when a consumed-but-unlowered option rides. */
1661
+ static bool scr_stream_parse_dyn_opts(const ScrDyn *opts, char base, ScrStreamDynOpts *out) {
1662
+ out->rhwm = -1;
1663
+ out->whwm = -1;
1664
+ out->auto_destroy = true;
1665
+ out->emit_close = true;
1666
+ out->allow_half_open = true;
1667
+ out->readable_side = true;
1668
+ out->writable_side = true;
1669
+ out->enc = NULL;
1670
+ out->read = out->write = out->final_fn = out->destroy = out->transform = out->flush = NULL;
1671
+ if (opts == NULL || opts->kind != SCR_DYN_OBJ) return true; /* Node's `if (options)` */
1672
+ const bool duplexish = base == 'd' || base == 't' || base == 'p';
1673
+ const bool transformish = base == 't' || base == 'p';
1674
+ const bool r_sided = base != 'w';
1675
+
1676
+ if (scr_stream_dynopt_truthy(opts, "objectMode", 10) ||
1677
+ scr_stream_dynopt_truthy(opts, "readableObjectMode", 18) ||
1678
+ scr_stream_dynopt_truthy(opts, "writableObjectMode", 18)) {
1679
+ static const char m[] = "objectMode streams are not supported yet (chunks are Buffers or utf8 strings)";
1680
+ return scr_stream_dynopt_unsupported(m, sizeof m - 1);
1681
+ }
1682
+ if (scr_stream_dynopt_false(scr_dyn_obj_get((ScrDyn *)opts, "decodeStrings", 13))) {
1683
+ static const char m[] = "decodeStrings: false is not supported yet (chunks always decode to Buffers)";
1684
+ return scr_stream_dynopt_unsupported(m, sizeof m - 1);
1685
+ }
1686
+ {
1687
+ ScrDyn *v = scr_dyn_obj_get((ScrDyn *)opts, "signal", 6);
1688
+ if (v != NULL && v->kind != SCR_DYN_NULL && v->kind != SCR_DYN_UNDEF) {
1689
+ static const char m[] = "the stream option 'signal' is not supported yet (AbortSignal-driven destruction has no lowering)";
1690
+ return scr_stream_dynopt_unsupported(m, sizeof m - 1);
1691
+ }
1692
+ }
1693
+ if (scr_stream_dynopt_fn(opts, "construct", 9) != NULL) {
1694
+ static const char m[] = "the stream option 'construct' is not supported yet (deferred construction has no lowering)";
1695
+ return scr_stream_dynopt_unsupported(m, sizeof m - 1);
1696
+ }
1697
+ if (scr_stream_dynopt_fn(opts, "writev", 6) != NULL) {
1698
+ static const char m[] = "the stream option 'writev' is not supported yet (writes deliver one chunk at a time)";
1699
+ return scr_stream_dynopt_unsupported(m, sizeof m - 1);
1700
+ }
1701
+ {
1702
+ ScrDyn *v = scr_dyn_obj_get((ScrDyn *)opts, "defaultEncoding", 15);
1703
+ if (v != NULL && v->kind == SCR_DYN_STR &&
1704
+ !(v->v.str->len == 4 && memcmp(v->v.str->data, "utf8", 4) == 0) &&
1705
+ !(v->v.str->len == 5 && memcmp(v->v.str->data, "utf-8", 5) == 0)) {
1706
+ static const char m[] = "the stream option 'defaultEncoding' is not supported yet (utf8, the default, is the write-side string encoding)";
1707
+ return scr_stream_dynopt_unsupported(m, sizeof m - 1);
1708
+ }
1709
+ }
1710
+ if (transformish) {
1711
+ /* Node's Transform would install these UNDER the transform machinery
1712
+ * (Duplex consumes read/write/final before Transform overrides). */
1713
+ if (scr_stream_dynopt_fn(opts, "read", 4) != NULL ||
1714
+ scr_stream_dynopt_fn(opts, "write", 5) != NULL ||
1715
+ scr_stream_dynopt_fn(opts, "final", 5) != NULL) {
1716
+ static const char m[] = "a read/write/final option on a Transform is not supported yet (it would replace the transform composition)";
1717
+ return scr_stream_dynopt_unsupported(m, sizeof m - 1);
1718
+ }
1719
+ }
1720
+
1721
+ /* highWaterMark wins over the sided keys (Node's getHighWaterMark). */
1722
+ {
1723
+ double hwm = scr_stream_dynopt_hwm(opts, "highWaterMark", 13);
1724
+ double rh = duplexish ? scr_stream_dynopt_hwm(opts, "readableHighWaterMark", 21) : -1;
1725
+ double wh = duplexish ? scr_stream_dynopt_hwm(opts, "writableHighWaterMark", 21) : -1;
1726
+ out->rhwm = hwm >= 0 ? hwm : rh;
1727
+ out->whwm = hwm >= 0 ? hwm : wh;
1728
+ }
1729
+ out->auto_destroy = !scr_stream_dynopt_false(scr_dyn_obj_get((ScrDyn *)opts, "autoDestroy", 11));
1730
+ out->emit_close = !scr_stream_dynopt_false(scr_dyn_obj_get((ScrDyn *)opts, "emitClose", 9));
1731
+ if (duplexish) {
1732
+ out->allow_half_open = !scr_stream_dynopt_false(scr_dyn_obj_get((ScrDyn *)opts, "allowHalfOpen", 13));
1733
+ out->readable_side = !scr_stream_dynopt_false(scr_dyn_obj_get((ScrDyn *)opts, "readable", 8));
1734
+ out->writable_side = !scr_stream_dynopt_false(scr_dyn_obj_get((ScrDyn *)opts, "writable", 8));
1735
+ }
1736
+ if (r_sided) {
1737
+ ScrDyn *v = scr_dyn_obj_get((ScrDyn *)opts, "encoding", 8);
1738
+ if (v != NULL && v->kind == SCR_DYN_STR) {
1739
+ out->enc = scr_stream_dynopt_encoding(v->v.str);
1740
+ if (out->enc == NULL) return false;
1741
+ }
1742
+ }
1743
+ out->destroy = scr_stream_dynopt_fn(opts, "destroy", 7);
1744
+ if (transformish) {
1745
+ out->transform = scr_stream_dynopt_fn(opts, "transform", 9);
1746
+ out->flush = scr_stream_dynopt_fn(opts, "flush", 5);
1747
+ } else {
1748
+ if (base != 'w') out->read = scr_stream_dynopt_fn(opts, "read", 4);
1749
+ if (base != 'r') {
1750
+ out->write = scr_stream_dynopt_fn(opts, "write", 5);
1751
+ out->final_fn = scr_stream_dynopt_fn(opts, "final", 5);
1752
+ }
1753
+ }
1754
+ return true;
1755
+ }
1756
+
1757
+ /* One slot: an options callback SHADOWS the fallback (Node's instance-
1758
+ * property-over-prototype rule); the unused fallback releases. */
1759
+ static ScrClosure *scr_stream_dynopt_pick(ScrDyn *opt, void *dyn_inv, ScrClosure *fb, void *fb_inv, void **inv_out) {
1760
+ if (opt != NULL) {
1761
+ if (fb != NULL) scr_closure_release(fb);
1762
+ *inv_out = dyn_inv;
1763
+ return scr_stream_dynopt_clo(opt, dyn_inv);
1764
+ }
1765
+ *inv_out = fb != NULL ? fb_inv : NULL;
1766
+ return fb;
1767
+ }
1768
+
1769
+ static void scr_stream_dynopt_apply_enc(ScrStream *s, ScrStr *enc) {
1770
+ if (enc == NULL) return;
1771
+ ScrStream *chain = scr_stream_set_encoding(s, enc);
1772
+ scr_stream_release(chain);
1773
+ scr_str_release(enc);
1774
+ }
1775
+
1776
+ ScrStream *scr_stream_new_readable_dyn(ScrDyn *opts) {
1777
+ ScrStreamDynOpts o;
1778
+ if (!scr_stream_parse_dyn_opts(opts, 'r', &o)) return NULL;
1779
+ void *rinv = NULL, *dinv = NULL;
1780
+ ScrClosure *read = scr_stream_dynopt_pick(o.read, (void *)&scr_stream_dynopt_read_inv, NULL, NULL, &rinv);
1781
+ ScrClosure *destroy = scr_stream_dynopt_pick(o.destroy, (void *)&scr_stream_dynopt_destroy_inv, NULL, NULL, &dinv);
1782
+ ScrStream *s = scr_stream_new_readable(o.rhwm, o.auto_destroy, o.emit_close,
1783
+ read, (ScrStreamReadInv)rinv, destroy, (ScrStreamErrInv)dinv);
1784
+ scr_stream_dynopt_apply_enc(s, o.enc);
1785
+ return s;
1786
+ }
1787
+
1788
+ ScrStream *scr_stream_new_writable_dyn(ScrDyn *opts) {
1789
+ ScrStreamDynOpts o;
1790
+ if (!scr_stream_parse_dyn_opts(opts, 'w', &o)) return NULL;
1791
+ void *winv = NULL, *finv = NULL, *dinv = NULL;
1792
+ ScrClosure *write = scr_stream_dynopt_pick(o.write, (void *)&scr_stream_dynopt_write_inv, NULL, NULL, &winv);
1793
+ ScrClosure *final_cb = scr_stream_dynopt_pick(o.final_fn, (void *)&scr_stream_dynopt_final_inv, NULL, NULL, &finv);
1794
+ ScrClosure *destroy = scr_stream_dynopt_pick(o.destroy, (void *)&scr_stream_dynopt_destroy_inv, NULL, NULL, &dinv);
1795
+ return scr_stream_new_writable(o.whwm, o.auto_destroy, o.emit_close,
1796
+ write, (ScrStreamChunkInv)winv, final_cb, (ScrStreamPlainInv)finv, destroy, (ScrStreamErrInv)dinv);
1797
+ }
1798
+
1799
+ ScrStream *scr_stream_new_duplex_dyn(ScrDyn *opts) {
1800
+ ScrStreamDynOpts o;
1801
+ if (!scr_stream_parse_dyn_opts(opts, 'd', &o)) return NULL;
1802
+ void *rinv = NULL, *winv = NULL, *finv = NULL, *dinv = NULL;
1803
+ ScrClosure *read = scr_stream_dynopt_pick(o.read, (void *)&scr_stream_dynopt_read_inv, NULL, NULL, &rinv);
1804
+ ScrClosure *write = scr_stream_dynopt_pick(o.write, (void *)&scr_stream_dynopt_write_inv, NULL, NULL, &winv);
1805
+ ScrClosure *final_cb = scr_stream_dynopt_pick(o.final_fn, (void *)&scr_stream_dynopt_final_inv, NULL, NULL, &finv);
1806
+ ScrClosure *destroy = scr_stream_dynopt_pick(o.destroy, (void *)&scr_stream_dynopt_destroy_inv, NULL, NULL, &dinv);
1807
+ ScrStream *s = scr_stream_new_duplex(o.rhwm, o.whwm, o.auto_destroy, o.emit_close,
1808
+ o.allow_half_open, o.readable_side, o.writable_side,
1809
+ read, (ScrStreamReadInv)rinv, write, (ScrStreamChunkInv)winv,
1810
+ final_cb, (ScrStreamPlainInv)finv, destroy, (ScrStreamErrInv)dinv);
1811
+ scr_stream_dynopt_apply_enc(s, o.enc);
1812
+ return s;
1813
+ }
1814
+
1815
+ static ScrStream *scr_stream_new_transformish_dyn(ScrDyn *opts, char base) {
1816
+ ScrStreamDynOpts o;
1817
+ if (!scr_stream_parse_dyn_opts(opts, base, &o)) return NULL;
1818
+ void *tinv = NULL, *linv = NULL, *dinv = NULL;
1819
+ ScrClosure *transform = scr_stream_dynopt_pick(o.transform, (void *)&scr_stream_dynopt_transform_inv, NULL, NULL, &tinv);
1820
+ ScrClosure *flush = scr_stream_dynopt_pick(o.flush, (void *)&scr_stream_dynopt_flush_inv, NULL, NULL, &linv);
1821
+ ScrClosure *destroy = scr_stream_dynopt_pick(o.destroy, (void *)&scr_stream_dynopt_destroy_inv, NULL, NULL, &dinv);
1822
+ ScrStream *s = base == 't'
1823
+ ? scr_stream_new_transform(o.rhwm, o.whwm, o.auto_destroy, o.emit_close,
1824
+ o.allow_half_open, o.readable_side, o.writable_side,
1825
+ transform, (ScrStreamChunkInv)tinv, flush, (ScrStreamPlainInv)linv,
1826
+ destroy, (ScrStreamErrInv)dinv)
1827
+ : scr_stream_new_passthrough(o.rhwm, o.whwm, o.auto_destroy, o.emit_close,
1828
+ o.allow_half_open, o.readable_side, o.writable_side,
1829
+ transform, (ScrStreamChunkInv)tinv, flush, (ScrStreamPlainInv)linv,
1830
+ destroy, (ScrStreamErrInv)dinv);
1831
+ scr_stream_dynopt_apply_enc(s, o.enc);
1832
+ return s;
1833
+ }
1834
+
1835
+ ScrStream *scr_stream_new_transform_dyn(ScrDyn *opts) {
1836
+ return scr_stream_new_transformish_dyn(opts, 't');
1837
+ }
1838
+
1839
+ ScrStream *scr_stream_new_passthrough_dyn(ScrDyn *opts) {
1840
+ return scr_stream_new_transformish_dyn(opts, 'p');
1841
+ }
1842
+
1843
+ void scr_stream_init_readable_dyn(ScrStream *s, ScrDyn *opts,
1844
+ ScrClosure *read_fb, ScrStreamReadInv read_fb_inv,
1845
+ ScrClosure *destroy_fb, ScrStreamErrInv destroy_fb_inv) {
1846
+ ScrStreamDynOpts o;
1847
+ if (!scr_stream_parse_dyn_opts(opts, 'r', &o)) {
1848
+ if (read_fb != NULL) scr_closure_release(read_fb);
1849
+ if (destroy_fb != NULL) scr_closure_release(destroy_fb);
1850
+ return;
1851
+ }
1852
+ void *rinv = NULL, *dinv = NULL;
1853
+ ScrClosure *read = scr_stream_dynopt_pick(o.read, (void *)&scr_stream_dynopt_read_inv, read_fb, (void *)read_fb_inv, &rinv);
1854
+ ScrClosure *destroy = scr_stream_dynopt_pick(o.destroy, (void *)&scr_stream_dynopt_destroy_inv, destroy_fb, (void *)destroy_fb_inv, &dinv);
1855
+ scr_stream_init_readable(s, o.rhwm, o.auto_destroy, o.emit_close,
1856
+ read, (ScrStreamReadInv)rinv, destroy, (ScrStreamErrInv)dinv);
1857
+ scr_stream_dynopt_apply_enc(s, o.enc);
1858
+ }
1859
+
1860
+ void scr_stream_init_writable_dyn(ScrStream *s, ScrDyn *opts,
1861
+ ScrClosure *write_fb, ScrStreamChunkInv write_fb_inv,
1862
+ ScrClosure *final_fb, ScrStreamPlainInv final_fb_inv,
1863
+ ScrClosure *destroy_fb, ScrStreamErrInv destroy_fb_inv) {
1864
+ ScrStreamDynOpts o;
1865
+ if (!scr_stream_parse_dyn_opts(opts, 'w', &o)) {
1866
+ if (write_fb != NULL) scr_closure_release(write_fb);
1867
+ if (final_fb != NULL) scr_closure_release(final_fb);
1868
+ if (destroy_fb != NULL) scr_closure_release(destroy_fb);
1869
+ return;
1870
+ }
1871
+ void *winv = NULL, *finv = NULL, *dinv = NULL;
1872
+ ScrClosure *write = scr_stream_dynopt_pick(o.write, (void *)&scr_stream_dynopt_write_inv, write_fb, (void *)write_fb_inv, &winv);
1873
+ ScrClosure *final_cb = scr_stream_dynopt_pick(o.final_fn, (void *)&scr_stream_dynopt_final_inv, final_fb, (void *)final_fb_inv, &finv);
1874
+ ScrClosure *destroy = scr_stream_dynopt_pick(o.destroy, (void *)&scr_stream_dynopt_destroy_inv, destroy_fb, (void *)destroy_fb_inv, &dinv);
1875
+ scr_stream_init_writable(s, o.whwm, o.auto_destroy, o.emit_close,
1876
+ write, (ScrStreamChunkInv)winv, final_cb, (ScrStreamPlainInv)finv, destroy, (ScrStreamErrInv)dinv);
1877
+ }
1878
+
1879
+ void scr_stream_init_duplex_dyn(ScrStream *s, ScrDyn *opts,
1880
+ ScrClosure *read_fb, ScrStreamReadInv read_fb_inv,
1881
+ ScrClosure *write_fb, ScrStreamChunkInv write_fb_inv,
1882
+ ScrClosure *final_fb, ScrStreamPlainInv final_fb_inv,
1883
+ ScrClosure *destroy_fb, ScrStreamErrInv destroy_fb_inv) {
1884
+ ScrStreamDynOpts o;
1885
+ if (!scr_stream_parse_dyn_opts(opts, 'd', &o)) {
1886
+ if (read_fb != NULL) scr_closure_release(read_fb);
1887
+ if (write_fb != NULL) scr_closure_release(write_fb);
1888
+ if (final_fb != NULL) scr_closure_release(final_fb);
1889
+ if (destroy_fb != NULL) scr_closure_release(destroy_fb);
1890
+ return;
1891
+ }
1892
+ void *rinv = NULL, *winv = NULL, *finv = NULL, *dinv = NULL;
1893
+ ScrClosure *read = scr_stream_dynopt_pick(o.read, (void *)&scr_stream_dynopt_read_inv, read_fb, (void *)read_fb_inv, &rinv);
1894
+ ScrClosure *write = scr_stream_dynopt_pick(o.write, (void *)&scr_stream_dynopt_write_inv, write_fb, (void *)write_fb_inv, &winv);
1895
+ ScrClosure *final_cb = scr_stream_dynopt_pick(o.final_fn, (void *)&scr_stream_dynopt_final_inv, final_fb, (void *)final_fb_inv, &finv);
1896
+ ScrClosure *destroy = scr_stream_dynopt_pick(o.destroy, (void *)&scr_stream_dynopt_destroy_inv, destroy_fb, (void *)destroy_fb_inv, &dinv);
1897
+ scr_stream_init_duplex(s, o.rhwm, o.whwm, o.auto_destroy, o.emit_close,
1898
+ o.allow_half_open, o.readable_side, o.writable_side,
1899
+ read, (ScrStreamReadInv)rinv, write, (ScrStreamChunkInv)winv,
1900
+ final_cb, (ScrStreamPlainInv)finv, destroy, (ScrStreamErrInv)dinv);
1901
+ scr_stream_dynopt_apply_enc(s, o.enc);
1902
+ }
1903
+
1904
+ static void scr_stream_init_transformish_dyn(ScrStream *s, ScrDyn *opts, char base,
1905
+ ScrClosure *transform_fb, ScrStreamChunkInv transform_fb_inv,
1906
+ ScrClosure *flush_fb, ScrStreamPlainInv flush_fb_inv,
1907
+ ScrClosure *destroy_fb, ScrStreamErrInv destroy_fb_inv) {
1908
+ ScrStreamDynOpts o;
1909
+ if (!scr_stream_parse_dyn_opts(opts, base, &o)) {
1910
+ if (transform_fb != NULL) scr_closure_release(transform_fb);
1911
+ if (flush_fb != NULL) scr_closure_release(flush_fb);
1912
+ if (destroy_fb != NULL) scr_closure_release(destroy_fb);
1913
+ return;
1914
+ }
1915
+ void *tinv = NULL, *linv = NULL, *dinv = NULL;
1916
+ ScrClosure *transform = scr_stream_dynopt_pick(o.transform, (void *)&scr_stream_dynopt_transform_inv, transform_fb, (void *)transform_fb_inv, &tinv);
1917
+ ScrClosure *flush = scr_stream_dynopt_pick(o.flush, (void *)&scr_stream_dynopt_flush_inv, flush_fb, (void *)flush_fb_inv, &linv);
1918
+ ScrClosure *destroy = scr_stream_dynopt_pick(o.destroy, (void *)&scr_stream_dynopt_destroy_inv, destroy_fb, (void *)destroy_fb_inv, &dinv);
1919
+ if (base == 't') {
1920
+ scr_stream_init_transform(s, o.rhwm, o.whwm, o.auto_destroy, o.emit_close,
1921
+ o.allow_half_open, o.readable_side, o.writable_side,
1922
+ transform, (ScrStreamChunkInv)tinv, flush, (ScrStreamPlainInv)linv,
1923
+ destroy, (ScrStreamErrInv)dinv);
1924
+ } else {
1925
+ scr_stream_init_passthrough(s, o.rhwm, o.whwm, o.auto_destroy, o.emit_close,
1926
+ o.allow_half_open, o.readable_side, o.writable_side,
1927
+ transform, (ScrStreamChunkInv)tinv, flush, (ScrStreamPlainInv)linv,
1928
+ destroy, (ScrStreamErrInv)dinv);
1929
+ }
1930
+ scr_stream_dynopt_apply_enc(s, o.enc);
1931
+ }
1932
+
1933
+ void scr_stream_init_transform_dyn(ScrStream *s, ScrDyn *opts,
1934
+ ScrClosure *transform_fb, ScrStreamChunkInv transform_fb_inv,
1935
+ ScrClosure *flush_fb, ScrStreamPlainInv flush_fb_inv,
1936
+ ScrClosure *destroy_fb, ScrStreamErrInv destroy_fb_inv) {
1937
+ scr_stream_init_transformish_dyn(s, opts, 't', transform_fb, transform_fb_inv,
1938
+ flush_fb, flush_fb_inv, destroy_fb, destroy_fb_inv);
1939
+ }
1940
+
1941
+ void scr_stream_init_passthrough_dyn(ScrStream *s, ScrDyn *opts,
1942
+ ScrClosure *transform_fb, ScrStreamChunkInv transform_fb_inv,
1943
+ ScrClosure *flush_fb, ScrStreamPlainInv flush_fb_inv,
1944
+ ScrClosure *destroy_fb, ScrStreamErrInv destroy_fb_inv) {
1945
+ scr_stream_init_transformish_dyn(s, opts, 'p', transform_fb, transform_fb_inv,
1946
+ flush_fb, flush_fb_inv, destroy_fb, destroy_fb_inv);
1947
+ }
1948
+
1949
+ /* ── finished() and pipeline() (lib/internal/streams/end-of-stream and
1950
+ * pipeline — the callback forms) ─────────────────────────────────────
1951
+ *
1952
+ * PROBED ORDERINGS (Node v24.15.0):
1953
+ * - finished(s, cb): cb fires right AFTER s's 'close' (willEmitClose —
1954
+ * the default autoDestroy+emitClose lifecycle), with `this` bound to
1955
+ * the stream, no arguments on success, the error on error, and
1956
+ * ERR_STREAM_PREMATURE_CLOSE ("Premature close") when the stream
1957
+ * closed before end/finish. finished returns a CLEANUP function that
1958
+ * unhooks the callback.
1959
+ * - pipeline(a, b, c, cb) on a mid-stream error: the erroring stream
1960
+ * destroys itself and emits 'error'+'close'; the pipeline then
1961
+ * destroys every OTHER stream with the SAME error (pipeline order),
1962
+ * each emitting 'error' then 'close'; cb(err) runs after the last
1963
+ * 'close'. Success: cb(null) after the destination's 'close'.
1964
+ * - both register 'error' handlers, so an otherwise-unhandled stream
1965
+ * error no longer crashes (the SCR_ST_ERROR suppression above). */
1966
+
1967
+ static void scr_stream_fin_add(ScrStreamState *st, ScrClosure *cb /*moves*/, ScrStreamErrInv inv) {
1968
+ if (st->fin.n == st->fin.cap) {
1969
+ st->fin.cap = st->fin.cap ? st->fin.cap * 2 : 2;
1970
+ st->fin.cb = realloc(st->fin.cb, st->fin.cap * sizeof *st->fin.cb);
1971
+ st->fin.inv = realloc(st->fin.inv, st->fin.cap * sizeof *st->fin.inv);
1972
+ if (!st->fin.cb || !st->fin.inv) scr_stream_oom();
1973
+ }
1974
+ st->fin.cb[st->fin.n] = cb;
1975
+ st->fin.inv[st->fin.n] = inv;
1976
+ st->fin.n++;
1977
+ }
1978
+
1979
+ /* The stream's finish status: +1 error (the recorded one, or premature
1980
+ * close), or NULL for a clean end+finish. */
1981
+ static ScrError *scr_stream_finish_status(ScrStream *s) {
1982
+ ScrStreamState *st = s->st;
1983
+ if (st->errored) return scr_error_retain(st->errored);
1984
+ if ((st->has_r && !st->r.end_emitted) || (st->has_w && !st->w.finished)) {
1985
+ return scr_stream_mkerr("ERR_STREAM_PREMATURE_CLOSE", "Premature close");
1986
+ }
1987
+ return NULL;
1988
+ }
1989
+
1990
+ static void scr_stream_notify_finished(ScrStream *s) {
1991
+ ScrStreamState *st = s->st;
1992
+ if (st->fin.n == 0) return;
1993
+ /* Take the list out first: a watcher may register another finished()
1994
+ * or run cleanup reentrantly. */
1995
+ ScrClosure **cbs = st->fin.cb;
1996
+ ScrStreamErrInv *invs = st->fin.inv;
1997
+ size_t n = st->fin.n;
1998
+ st->fin.cb = NULL;
1999
+ st->fin.inv = NULL;
2000
+ st->fin.n = 0;
2001
+ st->fin.cap = 0;
2002
+ ScrError *err = scr_stream_finish_status(s);
2003
+ for (size_t i = 0; i < n; i++) {
2004
+ if (!scr_exc_pending()) invs[i](cbs[i], s, err);
2005
+ scr_closure_release(cbs[i]);
2006
+ }
2007
+ if (err) scr_error_release(err);
2008
+ free(cbs);
2009
+ free(invs);
2010
+ }
2011
+
2012
+ static void scr_stream_finished_cleanup_fn(ScrClosure *self) {
2013
+ ScrStream *s = scr_box_get_ref(self->caps[0]); /* +1 */
2014
+ ScrClosure *cb = scr_box_get_ref(self->caps[1]); /* +1 */
2015
+ ScrStreamState *st = s->st;
2016
+ for (size_t i = 0; i < st->fin.n; i++) {
2017
+ if (st->fin.cb[i] == cb) {
2018
+ scr_closure_release(st->fin.cb[i]);
2019
+ memmove(&st->fin.cb[i], &st->fin.cb[i + 1], (st->fin.n - i - 1) * sizeof *st->fin.cb);
2020
+ memmove(&st->fin.inv[i], &st->fin.inv[i + 1], (st->fin.n - i - 1) * sizeof *st->fin.inv);
2021
+ st->fin.n--;
2022
+ break;
2023
+ }
2024
+ }
2025
+ scr_closure_release(cb);
2026
+ scr_stream_release(s);
2027
+ }
2028
+
2029
+ ScrClosure *scr_stream_finished(ScrStream *s, ScrClosure *cb /*moves*/, ScrStreamErrInv inv) {
2030
+ ScrStreamState *st = s->st;
2031
+ scr_stream_fin_add(st, scr_closure_retain(cb), inv);
2032
+ if (st->close_emitted) {
2033
+ /* Already terminal: Node's eos still calls back asynchronously. */
2034
+ scr_st_tick(s, SCR_ST_FIN, NULL, NULL);
2035
+ }
2036
+ ScrClosure *cleanup = scr_closure_new((void *)&scr_stream_finished_cleanup_fn, 2);
2037
+ cleanup->caps[0] = scr_box_new_obj(&scr_stream_retain_v, &scr_stream_release_v, &scr_stream_trace);
2038
+ scr_box_set_ref(cleanup->caps[0], scr_stream_retain(s));
2039
+ cleanup->caps[1] = scr_box_new_obj(&scr_closure_retain_v, &scr_closure_release_v, &scr_closure_trace_v);
2040
+ scr_box_set_ref(cleanup->caps[1], cb); /* the move lands here */
2041
+ return cleanup;
2042
+ }
2043
+
2044
+ /* The dyn-valued finished/pipeline callback (a mustCall wrapper): success
2045
+ * calls with NO arguments (Node's callback.call(stream)), an error boxes
2046
+ * through the boundary encoding. */
2047
+ void scr_stream_finished_dyn_inv(ScrClosure *cb, ScrStream *s, ScrError *err) {
2048
+ (void)s;
2049
+ ScrDyn *fn = scr_stream_dynopt_target(cb);
2050
+ ScrDyn *r;
2051
+ if (err != NULL) {
2052
+ ScrDyn *a = scr_dyn_from_error(err);
2053
+ r = scr_dyn_call(fn, &a, 1, "callback");
2054
+ scr_dyn_release(a);
2055
+ } else {
2056
+ r = scr_dyn_call(fn, NULL, 0, "callback");
2057
+ }
2058
+ scr_dyn_release(fn);
2059
+ if (r != NULL) scr_dyn_release(r);
2060
+ }
2061
+
2062
+ typedef struct {
2063
+ size_t rc;
2064
+ size_t n, closed;
2065
+ ScrStream **streams; /* owned */
2066
+ ScrClosure *cb; /* owned; NULL once fired */
2067
+ ScrStreamErrInv inv;
2068
+ ScrError *err; /* first error, owned */
2069
+ } ScrStreamPipeCtx;
2070
+
2071
+ static void *scr_stream_pipectx_retain_v(void *p) {
2072
+ ((ScrStreamPipeCtx *)p)->rc++;
2073
+ return p;
2074
+ }
2075
+
2076
+ static void scr_stream_pipectx_release_v(void *p) {
2077
+ ScrStreamPipeCtx *ctx = p;
2078
+ if (--ctx->rc > 0) return;
2079
+ for (size_t i = 0; i < ctx->n; i++) scr_stream_release(ctx->streams[i]);
2080
+ free(ctx->streams);
2081
+ if (ctx->cb) scr_closure_release(ctx->cb);
2082
+ if (ctx->err) scr_error_release(ctx->err);
2083
+ free(ctx);
2084
+ }
2085
+
2086
+ static void scr_stream_pipeline_watch_inv(ScrClosure *cb, ScrStream *s, ScrError *err) {
2087
+ ScrStreamPipeCtx *ctx = scr_box_get_ref(cb->caps[0]); /* +1 */
2088
+ ctx->closed++;
2089
+ if (err != NULL && ctx->err == NULL) {
2090
+ ctx->err = scr_error_retain(err);
2091
+ /* Node's pipeline destroyer: every other stream goes down with the
2092
+ * SAME error, pipeline order (each emits 'error' then 'close'). */
2093
+ for (size_t i = 0; i < ctx->n; i++) {
2094
+ if (ctx->streams[i] != s) {
2095
+ ScrStream *chain = scr_stream_destroy(ctx->streams[i], ctx->err);
2096
+ scr_stream_release(chain);
2097
+ }
2098
+ }
2099
+ }
2100
+ if (ctx->closed == ctx->n && ctx->cb != NULL) {
2101
+ ScrClosure *ucb = ctx->cb;
2102
+ ctx->cb = NULL;
2103
+ ctx->inv(ucb, ctx->streams[ctx->n - 1], ctx->err);
2104
+ scr_closure_release(ucb);
2105
+ }
2106
+ scr_stream_pipectx_release_v(ctx);
2107
+ }
2108
+
2109
+ /* The dyn-valued twins: the callback rides as a checked-dynamic value
2110
+ * (borrowed; the closure's cap box retains it). */
2111
+ ScrClosure *scr_stream_finished_dyn(ScrStream *s, ScrDyn *cb) {
2112
+ ScrClosure *c = scr_stream_dynopt_clo(cb, (void *)&scr_stream_finished_dyn_inv);
2113
+ ScrClosure *cleanup = scr_stream_finished(s, c, &scr_stream_finished_dyn_inv);
2114
+ return cleanup;
2115
+ }
2116
+
2117
+ ScrStream *scr_stream_pipeline_dyn(double n, ScrStream **streams, ScrDyn *cb) {
2118
+ ScrClosure *c = scr_stream_dynopt_clo(cb, (void *)&scr_stream_finished_dyn_inv);
2119
+ return scr_stream_pipeline(n, streams, c, &scr_stream_finished_dyn_inv);
2120
+ }
2121
+
2122
+ ScrStream *scr_stream_pipeline(double n_d, ScrStream **streams /*borrowed*/,
2123
+ ScrClosure *cb /*moves*/, ScrStreamErrInv inv) {
2124
+ size_t n = (size_t)n_d;
2125
+ ScrStreamPipeCtx *ctx = calloc(1, sizeof *ctx);
2126
+ if (!ctx) scr_stream_oom();
2127
+ ctx->rc = 1;
2128
+ ctx->n = n;
2129
+ ctx->streams = calloc(n, sizeof *ctx->streams);
2130
+ if (!ctx->streams) scr_stream_oom();
2131
+ for (size_t i = 0; i < n; i++) ctx->streams[i] = scr_stream_retain(streams[i]);
2132
+ ctx->cb = cb;
2133
+ ctx->inv = inv;
2134
+ /* Chain the pipes first (end: true — Node's pipeline forwards EOF). */
2135
+ for (size_t i = 0; i + 1 < n; i++) {
2136
+ ScrStream *d = scr_stream_pipe(streams[i], streams[i + 1], true);
2137
+ scr_stream_release(d);
2138
+ }
2139
+ /* One watcher per stream: the terminal statuses drive error propagation
2140
+ * and the final callback (after the LAST 'close'). */
2141
+ for (size_t i = 0; i < n; i++) {
2142
+ ScrClosure *w = scr_closure_new((void *)&scr_stream_pipeline_watch_inv, 1);
2143
+ w->caps[0] = scr_box_new_obj(&scr_stream_pipectx_retain_v, &scr_stream_pipectx_release_v, NULL);
2144
+ scr_box_set_ref(w->caps[0], scr_stream_pipectx_retain_v(ctx));
2145
+ scr_stream_fin_add(streams[i]->st, w, &scr_stream_pipeline_watch_inv);
2146
+ }
2147
+ scr_stream_pipectx_release_v(ctx); /* the boxes own it now */
2148
+ return scr_stream_retain(streams[n - 1]);
2149
+ }
2150
+
2151
+ /* ── the readable surface ─────────────────────────────────────────────── */
2152
+
2153
+ bool scr_stream_push(ScrStream *s, ScrBytes *chunk) {
2154
+ return scr_stream_add_chunk(s, chunk, false);
2155
+ }
2156
+
2157
+ bool scr_stream_push_str(ScrStream *s, ScrStr *str) {
2158
+ ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)str->len);
2159
+ memcpy(b->data, str->data, str->len);
2160
+ bool ret = scr_stream_add_chunk(s, b, false);
2161
+ scr_bytes_release(b);
2162
+ return ret;
2163
+ }
2164
+
2165
+ bool scr_stream_push_null(ScrStream *s) {
2166
+ ScrStreamState *st = s->st;
2167
+ if (!st->has_r || st->destroyed) return false;
2168
+ st->r.reading = false;
2169
+ if (!st->r.ended) {
2170
+ /* onEofChunk flushes the decoder's held partial first (utf8's
2171
+ * replacement chars et al — Node's decoder.end() push). */
2172
+ if (st->r.encoded) {
2173
+ ScrStr *tail = scr_strdec_end(st->r.enc, st->r.dec_pending);
2174
+ st->r.dec_pending = 0;
2175
+ if (scr_str_utf16_len(tail) > 0) {
2176
+ scr_stream_rbuf_push(st, tail, false);
2177
+ if (st->r.need_readable) scr_stream_emit_readable_nt(s);
2178
+ } else {
2179
+ scr_str_release(tail);
2180
+ }
2181
+ }
2182
+ st->r.ended = true;
2183
+ /* onEofChunk: inside a _read call (or before the first attempt) the
2184
+ * 'readable' pass defers to a tick; outside one it runs NOW — a
2185
+ * flowing stream delivers its buffered chunks synchronously here. */
2186
+ if (st->r.in_read_sync) {
2187
+ scr_stream_emit_readable_nt(s);
2188
+ if (st->r.length == 0) scr_stream_end_readable(s);
2189
+ } else {
2190
+ st->r.need_readable = false;
2191
+ st->r.emitted_readable = true;
2192
+ scr_stream_emit_readable_now(s);
2193
+ }
2194
+ if (!scr_exc_pending()) scr_stream_settle_next(s);
2195
+ }
2196
+ return false;
2197
+ }
2198
+
2199
+ void scr_stream_unshift(ScrStream *s, ScrBytes *chunk) {
2200
+ ScrStreamState *st = s->st;
2201
+ if (st->r.head_off > 0) {
2202
+ /* materialize the part-read head so the front slot is whole */
2203
+ void *rest = scr_stream_rbuf_take(st, scr_stream_entry_len(st, st->r.buf[0]) - st->r.head_off);
2204
+ scr_stream_rbuf_push(st, rest, true);
2205
+ }
2206
+ scr_stream_add_chunk(s, chunk, true);
2207
+ }
2208
+
2209
+ void scr_stream_unshift_str(ScrStream *s, ScrStr *str) {
2210
+ ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)str->len);
2211
+ memcpy(b->data, str->data, str->len);
2212
+ scr_stream_unshift(s, b);
2213
+ scr_bytes_release(b);
2214
+ }
2215
+
2216
+ ScrBytes *scr_stream_read(ScrStream *s, double size) {
2217
+ /* a destroyed readable still DRAINS its buffer (Node: destroy leaves
2218
+ * buffered chunks readable; only _read refills are blocked) */
2219
+ if (!s->st->has_r) return NULL;
2220
+ if (s->st->r.encoded) {
2221
+ /* read() answers STRINGS once encoded — the static result type here
2222
+ * is `Buffer | null`, so the honest answer is a fence, not a
2223
+ * mistyped payload (SEMANTICS.md; 'data' listeners carry strings). */
2224
+ static const char msg[] =
2225
+ "read() on a stream with an encoding set is not supported yet "
2226
+ "(consume 'data' events, which deliver strings)";
2227
+ scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1);
2228
+ return NULL;
2229
+ }
2230
+ return scr_stream_read_n(s, size);
2231
+ }
2232
+
2233
+ /* setEncoding(enc): flips the readable buffer into string mode — Node
2234
+ * re-decodes the buffered content into ONE string entry and re-counts
2235
+ * length in string units. Borrows enc (canonical, frontend-folded);
2236
+ * answers the receiver +1. */
2237
+ ScrStream *scr_stream_set_encoding(ScrStream *s, ScrStr *enc) {
2238
+ ScrStreamState *st = s->st;
2239
+ if (!st->has_r) return scr_stream_retain(s);
2240
+ if (st->r.encoded) {
2241
+ /* switching encodings mid-stream: fold the old decoder's pending
2242
+ * tail into the buffer first (rare; Node re-creates the decoder) */
2243
+ ScrStr *tail = scr_strdec_end(st->r.enc, st->r.dec_pending);
2244
+ if (scr_str_utf16_len(tail) > 0) scr_stream_rbuf_push(st, tail, false);
2245
+ else scr_str_release(tail);
2246
+ scr_str_release(st->r.enc);
2247
+ st->r.enc = scr_str_retain(enc);
2248
+ st->r.dec_pending = 0;
2249
+ return scr_stream_retain(s);
2250
+ }
2251
+ st->r.enc = scr_str_retain(enc);
2252
+ st->r.dec_pending = 0;
2253
+ /* decode the buffered bytes into one concatenated string entry */
2254
+ ScrStr *content = NULL;
2255
+ size_t skip = st->r.head_off; /* buf[0] may be part-read (byte offset) */
2256
+ for (size_t i = 0; i < st->r.n; i++) {
2257
+ ScrBytes *chunk = st->r.buf[i];
2258
+ ScrBytes *view = chunk;
2259
+ if (i == 0 && skip > 0) {
2260
+ view = scr_bytes_new(SCR_BYTES_U8, (double)(chunk->len - skip));
2261
+ memcpy(view->data, chunk->data + skip, chunk->len - skip);
2262
+ }
2263
+ ScrStr *piece = scr_strdec_write(st->r.enc, st->r.dec_pending, view);
2264
+ st->r.dec_pending = scr_strdec_next(st->r.enc, st->r.dec_pending, view);
2265
+ if (view != chunk) scr_bytes_release(view);
2266
+ scr_bytes_release(chunk);
2267
+ if (content == NULL) {
2268
+ content = piece;
2269
+ } else {
2270
+ ScrStr *joined = scr_str_concat(content, piece);
2271
+ scr_str_release(content);
2272
+ scr_str_release(piece);
2273
+ content = joined;
2274
+ }
2275
+ }
2276
+ st->r.n = 0;
2277
+ st->r.head_off = 0;
2278
+ st->r.length = 0;
2279
+ st->r.encoded = true; /* AFTER the byte-entry walk above */
2280
+ if (content != NULL) {
2281
+ if (scr_str_utf16_len(content) > 0) scr_stream_rbuf_push(st, content, false);
2282
+ else scr_str_release(content);
2283
+ }
2284
+ return scr_stream_retain(s);
2285
+ }
2286
+
2287
+ ScrStream *scr_stream_pause(ScrStream *s) {
2288
+ ScrStreamState *st = s->st;
2289
+ if (st->has_r && st->r.flowing != 0) {
2290
+ st->r.flowing = 0;
2291
+ scr_stream_emit0(s, "pause"); /* Node emits 'pause' synchronously */
2292
+ }
2293
+ return scr_stream_retain(s);
2294
+ }
2295
+
2296
+ ScrStream *scr_stream_resume(ScrStream *s) {
2297
+ ScrStreamState *st = s->st;
2298
+ if (st->has_r && !st->destroyed && st->r.flowing != 1) {
2299
+ st->r.flowing = st->r.readable_listening ? 0 : 1;
2300
+ scr_stream_resume_kick(s);
2301
+ }
2302
+ return scr_stream_retain(s);
2303
+ }
2304
+
2305
+ bool scr_stream_is_paused(ScrStream *s) { return s->st->r.flowing == 0; }
2306
+
2307
+ double scr_stream_flowing(ScrStream *s) {
2308
+ int f = s->st->r.flowing;
2309
+ return f < 0 ? -1 : f;
2310
+ }
2311
+
2312
+ ScrStream *scr_stream_pipe(ScrStream *src, ScrStream *dst, bool end) {
2313
+ ScrStreamState *st = src->st;
2314
+ if (st->pipes.n == st->pipes.cap) {
2315
+ st->pipes.cap = st->pipes.cap ? st->pipes.cap * 2 : 2;
2316
+ st->pipes.dst = realloc(st->pipes.dst, st->pipes.cap * sizeof *st->pipes.dst);
2317
+ st->pipes.end = realloc(st->pipes.end, st->pipes.cap * sizeof *st->pipes.end);
2318
+ if (!st->pipes.dst || !st->pipes.end) scr_stream_oom();
2319
+ }
2320
+ st->pipes.dst[st->pipes.n] = scr_stream_retain(dst);
2321
+ st->pipes.end[st->pipes.n] = end;
2322
+ st->pipes.n++;
2323
+ scr_stream_emit_stream(dst, "pipe", src); /* Node emits 'pipe' synchronously */
2324
+ if (!scr_exc_pending() && st->r.flowing != 1 && st->await_drain == 0) {
2325
+ st->r.flowing = 1;
2326
+ scr_stream_resume_kick(src);
2327
+ }
2328
+ return scr_stream_retain(dst);
2329
+ }
2330
+
2331
+ ScrStream *scr_stream_unpipe(ScrStream *src, ScrStream *dst) {
2332
+ ScrStreamState *st = src->st;
2333
+ for (size_t i = 0; i < st->pipes.n;) {
2334
+ if (dst == NULL || st->pipes.dst[i] == dst) {
2335
+ ScrStream *d = st->pipes.dst[i];
2336
+ memmove(st->pipes.dst + i, st->pipes.dst + i + 1, (st->pipes.n - i - 1) * sizeof *st->pipes.dst);
2337
+ memmove(st->pipes.end + i, st->pipes.end + i + 1, (st->pipes.n - i - 1) * sizeof *st->pipes.end);
2338
+ st->pipes.n--;
2339
+ st->r.flowing = 0; /* Node pauses the source on unpipe */
2340
+ scr_stream_emit_stream(d, "unpipe", src);
2341
+ scr_stream_release(d);
2342
+ if (scr_exc_pending()) break;
2343
+ if (dst != NULL) break;
2344
+ } else {
2345
+ i++;
2346
+ }
2347
+ }
2348
+ return scr_stream_retain(src);
2349
+ }
2350
+
2351
+ /* ── the writable surface ─────────────────────────────────────────────── */
2352
+
2353
+ static bool scr_stream_write_chunk(ScrStream *s, ScrBytes *chunk /*borrowed*/, ScrClosure *cb /*moves*/) {
2354
+ ScrStreamState *st = s->st;
2355
+ if (!st->has_w || st->destroyed || st->w.ending) {
2356
+ ScrError *e = st->has_w && !st->destroyed
2357
+ ? scr_stream_mkerr("ERR_STREAM_WRITE_AFTER_END", "write after end")
2358
+ : scr_stream_mkerr("ERR_STREAM_DESTROYED", "Cannot call write after a stream was destroyed");
2359
+ if (cb) scr_st_tick(s, SCR_ST_WCB_ERR, NULL, cb);
2360
+ scr_stream_error_or_destroy(s, e);
2361
+ scr_error_release(e);
2362
+ return false;
2363
+ }
2364
+ size_t len = chunk->len;
2365
+ st->w.length += len;
2366
+ if (st->w.writing || st->w.corked > 0) {
2367
+ scr_stream_wq_push(st, scr_bytes_retain(chunk), cb);
2368
+ } else {
2369
+ scr_stream_do_write(s, scr_bytes_retain(chunk), cb);
2370
+ }
2371
+ /* Node computes the answer AFTER the (possibly synchronous) write:
2372
+ * a sync completion has already drained its bytes from length. */
2373
+ bool ret = st->w.length < st->w.hwm && !st->destroyed && !st->errored;
2374
+ if (!ret) st->w.need_drain = true;
2375
+ return ret;
2376
+ }
2377
+
2378
+ bool scr_stream_write(ScrStream *s, ScrBytes *chunk, ScrClosure *cb) {
2379
+ return scr_stream_write_chunk(s, chunk, cb);
2380
+ }
2381
+
2382
+ /* write(null) reached at runtime (a nullable-union chunk's null arm):
2383
+ * Node throws the ERR_STREAM_NULL_VALUES TypeError synchronously. */
2384
+ bool scr_stream_write_null(ScrStream *s) {
2385
+ (void)s;
2386
+ static const char msg[] = "May not write null values to stream";
2387
+ scr_throw_error_msg_code(SCR_ERR_TYPE, msg, sizeof msg - 1, "ERR_STREAM_NULL_VALUES");
2388
+ return false;
2389
+ }
2390
+
2391
+ bool scr_stream_write_str(ScrStream *s, ScrStr *str, ScrClosure *cb) {
2392
+ ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)str->len);
2393
+ memcpy(b->data, str->data, str->len);
2394
+ bool ret = scr_stream_write_chunk(s, b, cb);
2395
+ scr_bytes_release(b);
2396
+ return ret;
2397
+ }
2398
+
2399
+ ScrStream *scr_stream_end(ScrStream *s, ScrBytes *chunk_b, ScrStr *chunk_s, ScrClosure *cb) {
2400
+ ScrStreamState *st = s->st;
2401
+ if (chunk_b) scr_stream_write(s, chunk_b, NULL);
2402
+ else if (chunk_s) scr_stream_write_str(s, chunk_s, NULL);
2403
+ if (!st->has_w || st->destroyed) {
2404
+ if (cb) scr_closure_release(cb);
2405
+ return scr_stream_retain(s);
2406
+ }
2407
+ if (cb) {
2408
+ if (st->w.finished) {
2409
+ scr_st_tick(s, SCR_ST_WCB_ERR, NULL, cb); /* already finished: plain call */
2410
+ } else {
2411
+ if (st->w.end_cbs_n == st->w.end_cbs_cap) {
2412
+ st->w.end_cbs_cap = st->w.end_cbs_cap ? st->w.end_cbs_cap * 2 : 2;
2413
+ st->w.end_cbs = realloc(st->w.end_cbs, st->w.end_cbs_cap * sizeof *st->w.end_cbs);
2414
+ if (!st->w.end_cbs) scr_stream_oom();
2415
+ }
2416
+ st->w.end_cbs[st->w.end_cbs_n++] = cb;
2417
+ }
2418
+ }
2419
+ if (!st->w.ending) {
2420
+ st->w.ending = true;
2421
+ if (st->w.corked > 0) {
2422
+ st->w.corked = 1;
2423
+ scr_stream_uncork(s);
2424
+ }
2425
+ scr_stream_finish_maybe(s);
2426
+ }
2427
+ return scr_stream_retain(s);
2428
+ }
2429
+
2430
+ void scr_stream_cork(ScrStream *s) { s->st->w.corked++; }
2431
+
2432
+ void scr_stream_uncork(ScrStream *s) {
2433
+ ScrStreamState *st = s->st;
2434
+ if (st->w.corked > 0 && --st->w.corked == 0) {
2435
+ scr_stream_clear_buffer(s);
2436
+ scr_stream_finish_maybe(s);
2437
+ }
2438
+ }
2439
+
2440
+ /* ── completion entries (called by the emitted done closures) ─────────── */
2441
+
2442
+ void scr_stream_write_done(ScrStream *s, ScrError *err /*moves*/) {
2443
+ ScrStreamState *st = s->st;
2444
+ if (!st->w.writing) {
2445
+ /* cb called twice (or spuriously): Node's ERR_MULTIPLE_CALLBACK */
2446
+ if (err) scr_error_release(err);
2447
+ ScrError *e = scr_stream_mkerr("ERR_MULTIPLE_CALLBACK", "Callback called multiple times");
2448
+ scr_stream_error_or_destroy(s, e);
2449
+ scr_error_release(e);
2450
+ return;
2451
+ }
2452
+ st->w.writing = false;
2453
+ st->w.length -= st->w.inflight_len;
2454
+ st->w.inflight_len = 0;
2455
+ ScrClosure *ucb = st->w.inflight_cb;
2456
+ st->w.inflight_cb = NULL;
2457
+ if (err) {
2458
+ if (ucb) scr_st_tick(s, SCR_ST_WCB_ERR, NULL, ucb);
2459
+ scr_stream_error_or_destroy(s, err);
2460
+ scr_error_release(err);
2461
+ return;
2462
+ }
2463
+ /* Node's onwrite: the buffered queue restarts FIRST (clearBuffer),
2464
+ * then afterWrite — deferred to a tick for a synchronous completion. */
2465
+ bool was_sync = st->w.wsync;
2466
+ scr_stream_clear_buffer(s);
2467
+ if (scr_exc_pending()) {
2468
+ if (ucb) scr_closure_release(ucb);
2469
+ return;
2470
+ }
2471
+ if (was_sync) {
2472
+ scr_st_tick(s, SCR_ST_AFTER_WRITE, NULL, ucb);
2473
+ } else {
2474
+ scr_stream_after_write(s, ucb);
2475
+ }
2476
+ }
2477
+
2478
+ void scr_stream_final_done(ScrStream *s, ScrError *err /*moves*/) {
2479
+ ScrStreamState *st = s->st;
2480
+ if (err) {
2481
+ scr_stream_error_or_destroy(s, err);
2482
+ scr_error_release(err);
2483
+ return;
2484
+ }
2485
+ st->w.prefinished = true;
2486
+ scr_stream_emit0(s, "prefinish");
2487
+ if (scr_exc_pending()) return;
2488
+ scr_stream_finish_maybe(s);
2489
+ }
2490
+
2491
+ void scr_stream_destroy_done(ScrStream *s, ScrError *err /*moves*/) {
2492
+ ScrStreamState *st = s->st;
2493
+ /* Node's onDestroy: 'error' emits only with the error the USER'S
2494
+ * callback passed along — a `cb()` after destroy(err) SWALLOWS the
2495
+ * error (errored stays set, no emission; the default _destroy forwards,
2496
+ * which is the no-destroy-callback path in do_destroy). */
2497
+ if (err) {
2498
+ if (!st->errored) st->errored = scr_error_retain(err);
2499
+ if (!st->error_scheduled) {
2500
+ st->error_scheduled = true;
2501
+ scr_st_tick(s, SCR_ST_ERROR, err, NULL);
2502
+ } else {
2503
+ scr_error_release(err);
2504
+ }
2505
+ }
2506
+ if (!st->close_scheduled) {
2507
+ st->close_scheduled = true;
2508
+ scr_st_tick(s, SCR_ST_CLOSE, NULL, NULL);
2509
+ }
2510
+ }
2511
+
2512
+ void scr_stream_transform_done(ScrStream *s, ScrError *err /*moves*/, ScrBytes *data /*moves*/,
2513
+ ScrStr *data_str /*moves*/) {
2514
+ if (!err) {
2515
+ if (data) {
2516
+ scr_stream_add_chunk(s, data, false);
2517
+ } else if (data_str) {
2518
+ ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)data_str->len);
2519
+ memcpy(b->data, data_str->data, data_str->len);
2520
+ scr_stream_add_chunk(s, b, false);
2521
+ scr_bytes_release(b);
2522
+ }
2523
+ }
2524
+ if (data) scr_bytes_release(data);
2525
+ if (data_str) scr_str_release(data_str);
2526
+ if (scr_exc_pending()) {
2527
+ if (err) scr_error_release(err);
2528
+ return;
2529
+ }
2530
+ scr_stream_write_done(s, err);
2531
+ }
2532
+
2533
+ void scr_stream_flush_done(ScrStream *s, ScrError *err /*moves*/, ScrBytes *data /*moves*/,
2534
+ ScrStr *data_str /*moves*/) {
2535
+ ScrStreamState *st = s->st;
2536
+ if (err) {
2537
+ if (data) scr_bytes_release(data);
2538
+ if (data_str) scr_str_release(data_str);
2539
+ scr_stream_error_or_destroy(s, err);
2540
+ scr_error_release(err);
2541
+ return;
2542
+ }
2543
+ if (data) {
2544
+ scr_stream_add_chunk(s, data, false);
2545
+ scr_bytes_release(data);
2546
+ } else if (data_str) {
2547
+ ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)data_str->len);
2548
+ memcpy(b->data, data_str->data, data_str->len);
2549
+ scr_stream_add_chunk(s, b, false);
2550
+ scr_bytes_release(b);
2551
+ scr_str_release(data_str);
2552
+ }
2553
+ if (scr_exc_pending()) return;
2554
+ scr_stream_push_null(s); /* EOF the readable side after flush */
2555
+ if (scr_exc_pending()) return;
2556
+ scr_stream_finish_maybe(s);
2557
+ }
2558
+
2559
+ /* ── destroy ──────────────────────────────────────────────────────────── */
2560
+
2561
+ static void scr_stream_do_destroy(ScrStream *s, ScrError *err /*borrowed*/) {
2562
+ ScrStreamState *st = s->st;
2563
+ if (st->destroyed || st->destroy_calling) return;
2564
+ st->destroyed = true;
2565
+ if (err && !st->errored) st->errored = scr_error_retain(err);
2566
+ scr_stream_settle_next(s); /* a parked for-await rejects/finishes */
2567
+ if (st->destroy_cb) {
2568
+ st->destroy_calling = true;
2569
+ st->destroy_inv(st->destroy_cb, s, err);
2570
+ st->destroy_calling = false;
2571
+ if (scr_exc_pending()) return;
2572
+ /* the user's destroy must call its callback (destroy_done schedules
2573
+ * 'error'/'close'); a user destroy that never calls back leaves the
2574
+ * stream half-closed, exactly Node */
2575
+ return;
2576
+ }
2577
+ if (err) {
2578
+ if (!st->error_scheduled) {
2579
+ st->error_scheduled = true;
2580
+ scr_st_tick(s, SCR_ST_ERROR, scr_error_retain(err), NULL);
2581
+ }
2582
+ }
2583
+ if (!st->close_scheduled) {
2584
+ st->close_scheduled = true;
2585
+ scr_st_tick(s, SCR_ST_CLOSE, NULL, NULL);
2586
+ }
2587
+ }
2588
+
2589
+ ScrStream *scr_stream_destroy(ScrStream *s, ScrError *err) {
2590
+ scr_stream_do_destroy(s, err);
2591
+ return scr_stream_retain(s);
2592
+ }
2593
+
2594
+ ScrError *scr_stream_errored(ScrStream *s) {
2595
+ return s->st->errored ? scr_error_retain(s->st->errored) : NULL;
2596
+ }
2597
+
2598
+ /* ── properties ───────────────────────────────────────────────────────── */
2599
+
2600
+ double scr_stream_prop(ScrStream *s, const char *name) {
2601
+ ScrStreamState *st = s->st;
2602
+ if (strcmp(name, "readable") == 0) {
2603
+ return st->has_r && !st->destroyed && !st->r.end_emitted && !st->errored;
2604
+ }
2605
+ if (strcmp(name, "readableEnded") == 0) return st->r.end_emitted;
2606
+ if (strcmp(name, "readableLength") == 0) return (double)st->r.length;
2607
+ if (strcmp(name, "readableHighWaterMark") == 0) return (double)st->r.hwm;
2608
+ if (strcmp(name, "readableObjectMode") == 0) return st->r.object_entries;
2609
+ if (strcmp(name, "writable") == 0) {
2610
+ return st->has_w && !st->destroyed && !st->w.ending && !st->errored;
2611
+ }
2612
+ if (strcmp(name, "writableEnded") == 0) return st->w.ending;
2613
+ if (strcmp(name, "writableFinished") == 0) return st->w.finished;
2614
+ if (strcmp(name, "writableNeedDrain") == 0) return st->w.need_drain;
2615
+ if (strcmp(name, "writableLength") == 0) return (double)st->w.length;
2616
+ if (strcmp(name, "writableHighWaterMark") == 0) return (double)st->w.hwm;
2617
+ if (strcmp(name, "writableCorked") == 0) return (double)st->w.corked;
2618
+ if (strcmp(name, "destroyed") == 0) return st->destroyed;
2619
+ if (strcmp(name, "closed") == 0) return st->close_emitted;
2620
+ if (strcmp(name, "allowHalfOpen") == 0) return st->allow_half_open;
2621
+ /* The _readableState/_writableState compat VIEW (read-only): each name
2622
+ * answers from the field the runtime actually keeps. Node's ending/
2623
+ * ended distinction inside end() collapses onto w.ending here. */
2624
+ if (strncmp(name, "rs:", 3) == 0) {
2625
+ const char *p = name + 3;
2626
+ if (strcmp(p, "ended") == 0) return st->r.ended;
2627
+ if (strcmp(p, "endEmitted") == 0) return st->r.end_emitted;
2628
+ if (strcmp(p, "destroyed") == 0) return st->destroyed;
2629
+ if (strcmp(p, "errorEmitted") == 0) return st->error_emitted;
2630
+ if (strcmp(p, "emittedReadable") == 0) return st->r.emitted_readable;
2631
+ if (strcmp(p, "needReadable") == 0) return st->r.need_readable;
2632
+ if (strcmp(p, "reading") == 0) return st->r.reading;
2633
+ if (strcmp(p, "readableListening") == 0) return st->r.readable_listening;
2634
+ if (strcmp(p, "resumeScheduled") == 0) return st->r.resume_scheduled;
2635
+ if (strcmp(p, "objectMode") == 0) return st->r.object_entries;
2636
+ if (strcmp(p, "constructed") == 0) return 1;
2637
+ if (strcmp(p, "closed") == 0) return st->close_emitted;
2638
+ if (strcmp(p, "length") == 0) return (double)st->r.length;
2639
+ if (strcmp(p, "highWaterMark") == 0) return (double)st->r.hwm;
2640
+ return 0;
2641
+ }
2642
+ if (strncmp(name, "ws:", 3) == 0) {
2643
+ const char *p = name + 3;
2644
+ if (strcmp(p, "ended") == 0 || strcmp(p, "ending") == 0) return st->w.ending;
2645
+ if (strcmp(p, "finished") == 0) return st->w.finished;
2646
+ if (strcmp(p, "prefinished") == 0) return st->w.prefinished;
2647
+ if (strcmp(p, "destroyed") == 0) return st->destroyed;
2648
+ if (strcmp(p, "errorEmitted") == 0) return st->error_emitted;
2649
+ if (strcmp(p, "needDrain") == 0) return st->w.need_drain;
2650
+ if (strcmp(p, "objectMode") == 0) return 0;
2651
+ if (strcmp(p, "constructed") == 0) return 1;
2652
+ if (strcmp(p, "closed") == 0) return st->close_emitted;
2653
+ if (strcmp(p, "length") == 0) return (double)st->w.length;
2654
+ if (strcmp(p, "highWaterMark") == 0) return (double)st->w.hwm;
2655
+ if (strcmp(p, "corked") == 0) return (double)st->w.corked;
2656
+ if (strcmp(p, "bufferedRequestCount") == 0) return (double)(st->w.n - st->w.head);
2657
+ return 0;
2658
+ }
2659
+ return 0;
2660
+ }
2661
+
2662
+ /* emitReadable_ — the shared body of the 'readable' tick AND the
2663
+ * synchronous onEofChunk call (Node runs it directly when the eof push
2664
+ * arrives outside a _read call). */
2665
+ static void scr_stream_emit_readable_now(ScrStream *s) {
2666
+ ScrStreamState *st = s->st;
2667
+ st->r.emitted_readable = false;
2668
+ if (!st->destroyed && !st->errored && (st->r.length > 0 || st->r.ended)) {
2669
+ scr_stream_emit0(s, "readable");
2670
+ if (scr_exc_pending()) return;
2671
+ }
2672
+ st->r.need_readable = st->r.flowing != 1 && !st->r.ended &&
2673
+ st->r.length <= st->r.hwm;
2674
+ scr_stream_flow(s);
2675
+ if (!scr_exc_pending() && st->r.ended && st->r.length == 0) {
2676
+ scr_stream_end_readable(s);
2677
+ }
2678
+ }
2679
+
2680
+ /* ── tick dispatch ────────────────────────────────────────────────────── */
2681
+
2682
+ static void scr_stream_run_tick(ScrStreamTick *t) {
2683
+ ScrStream *s = t->s;
2684
+ ScrStreamState *st = s->st;
2685
+ switch (t->op) {
2686
+ case SCR_ST_RESUME: {
2687
+ st->r.resume_scheduled = false;
2688
+ if (!st->r.reading && st->r.flowing == 1) {
2689
+ /* read(0) kick: pull the user _read without consuming */
2690
+ st->r.need_readable = st->r.length == 0;
2691
+ if (!st->r.ended && !st->destroyed) scr_stream_call_read(s);
2692
+ if (scr_exc_pending()) break;
2693
+ }
2694
+ scr_stream_emit0(s, "resume");
2695
+ if (scr_exc_pending()) break;
2696
+ scr_stream_flow(s);
2697
+ if (scr_exc_pending()) break;
2698
+ if (st->r.flowing == 1 && !st->r.reading && !st->r.ended && !st->destroyed) {
2699
+ scr_stream_call_read(s);
2700
+ }
2701
+ break;
2702
+ }
2703
+ case SCR_ST_READABLE:
2704
+ scr_stream_emit_readable_now(s);
2705
+ break;
2706
+ case SCR_ST_END: {
2707
+ st->r.end_scheduled = false;
2708
+ if (st->destroyed || st->errored || st->r.end_emitted || st->r.length != 0 || !st->r.ended) break;
2709
+ st->r.end_emitted = true;
2710
+ scr_stream_emit0(s, "end");
2711
+ if (scr_exc_pending()) break;
2712
+ /* pipe end propagation */
2713
+ for (size_t i = 0; i < st->pipes.n; i++) {
2714
+ if (st->pipes.end[i]) {
2715
+ scr_stream_release(scr_stream_end(st->pipes.dst[i], NULL, NULL, NULL));
2716
+ if (scr_exc_pending()) break;
2717
+ }
2718
+ }
2719
+ if (scr_exc_pending()) break;
2720
+ if (st->has_w && !st->allow_half_open && !st->w.ending) {
2721
+ scr_st_tick(s, SCR_ST_END_W, NULL, NULL);
2722
+ break;
2723
+ }
2724
+ if (st->auto_destroy && (!st->has_w || st->w.finished)) {
2725
+ scr_stream_do_destroy(s, NULL);
2726
+ }
2727
+ break;
2728
+ }
2729
+ case SCR_ST_MAYBE_MORE: {
2730
+ st->r.maybe_more_scheduled = false;
2731
+ if (st->destroyed) break;
2732
+ /* loop while synchronous pushes make progress below the hwm */
2733
+ while (!st->r.reading && !st->r.ended && st->r.length < st->r.hwm &&
2734
+ (st->r.flowing == 1 || st->r.need_readable) && !scr_exc_pending()) {
2735
+ size_t before = st->r.length;
2736
+ scr_stream_call_read(s);
2737
+ if (scr_exc_pending() || st->r.length == before) break;
2738
+ }
2739
+ break;
2740
+ }
2741
+ case SCR_ST_AFTER_WRITE: {
2742
+ st->w.after_scheduled = false;
2743
+ ScrClosure *cb = t->cb;
2744
+ t->cb = NULL;
2745
+ if (!st->destroyed) {
2746
+ scr_stream_after_write(s, cb);
2747
+ } else if (cb) {
2748
+ ((void (*)(ScrClosure *))cb->fn)(cb);
2749
+ scr_closure_release(cb);
2750
+ }
2751
+ break;
2752
+ }
2753
+ case SCR_ST_FINISH: {
2754
+ st->w.finish_scheduled = false;
2755
+ if (st->destroyed || st->w.finished) break;
2756
+ st->w.finished = true;
2757
+ /* Node runs the end(cb) callbacks (kOnFinished) BEFORE 'finish'. */
2758
+ while (st->w.end_cbs_n > 0) {
2759
+ ScrClosure *cb = st->w.end_cbs[0];
2760
+ memmove(st->w.end_cbs, st->w.end_cbs + 1, (st->w.end_cbs_n - 1) * sizeof *st->w.end_cbs);
2761
+ st->w.end_cbs_n--;
2762
+ ((void (*)(ScrClosure *))cb->fn)(cb);
2763
+ scr_closure_release(cb);
2764
+ if (scr_exc_pending()) break;
2765
+ }
2766
+ if (scr_exc_pending()) break;
2767
+ scr_stream_emit0(s, "finish");
2768
+ if (scr_exc_pending()) break;
2769
+ if (st->auto_destroy && (!st->has_r || st->r.end_emitted || !st->r.readable_listening)) {
2770
+ /* Node: autoDestroy after finish once the readable half is done
2771
+ * (a half-open duplex with a live readable side stays up) */
2772
+ if (!st->has_r || st->r.end_emitted) scr_stream_do_destroy(s, NULL);
2773
+ }
2774
+ break;
2775
+ }
2776
+ case SCR_ST_ERROR: {
2777
+ st->error_scheduled = false;
2778
+ if (!st->error_emitted && t->err) {
2779
+ st->error_emitted = true;
2780
+ if (st->next_err_consumed && !scr_emitter_has((ScrEmitter *)s, "error")) {
2781
+ /* the for-await's rejection consumed it (Node's iterator
2782
+ * registers its own 'error' handler — no unhandled crash) */
2783
+ break;
2784
+ }
2785
+ if (st->fin.n > 0 && !scr_emitter_has((ScrEmitter *)s, "error")) {
2786
+ /* a finished()/pipeline watcher consumes it (Node's eos and
2787
+ * pipeline register their own 'error' handlers) — the watcher
2788
+ * reports it at the terminal point instead of crashing */
2789
+ break;
2790
+ }
2791
+ scr_stream_emit_error(s, t->err);
2792
+ }
2793
+ break;
2794
+ }
2795
+ case SCR_ST_CLOSE: {
2796
+ st->close_scheduled = false;
2797
+ if (!st->close_emitted) {
2798
+ st->close_emitted = true;
2799
+ if (st->emit_close) scr_stream_emit0(s, "close");
2800
+ /* eos ordering: the finished()/pipeline watchers fire right after
2801
+ * 'close' (Node's willEmitClose stance). */
2802
+ scr_stream_notify_finished(s);
2803
+ /* Fully closed: nothing calls the option callbacks again (Node's
2804
+ * contract), so drop them here — this breaks the reference cycle
2805
+ * a callback capturing its own stream forms, including the dyn-
2806
+ * options closures whose stream edge rides through a DOM function
2807
+ * value the cycle collector cannot traverse. */
2808
+ if (st->r.read_cb) { scr_closure_release(st->r.read_cb); st->r.read_cb = NULL; }
2809
+ if (st->w.write_cb) { scr_closure_release(st->w.write_cb); st->w.write_cb = NULL; }
2810
+ if (st->w.final_cb) { scr_closure_release(st->w.final_cb); st->w.final_cb = NULL; }
2811
+ if (st->transform_cb) { scr_closure_release(st->transform_cb); st->transform_cb = NULL; }
2812
+ if (st->flush_cb) { scr_closure_release(st->flush_cb); st->flush_cb = NULL; }
2813
+ if (st->destroy_cb) { scr_closure_release(st->destroy_cb); st->destroy_cb = NULL; }
2814
+ }
2815
+ break;
2816
+ }
2817
+ case SCR_ST_END_W: {
2818
+ scr_stream_release(scr_stream_end(s, NULL, NULL, NULL));
2819
+ break;
2820
+ }
2821
+ case SCR_ST_WCB_ERR: {
2822
+ ScrClosure *cb = t->cb;
2823
+ t->cb = NULL;
2824
+ if (cb) {
2825
+ ((void (*)(ScrClosure *))cb->fn)(cb);
2826
+ scr_closure_release(cb);
2827
+ }
2828
+ break;
2829
+ }
2830
+ case SCR_ST_FIN: {
2831
+ scr_stream_notify_finished(s);
2832
+ break;
2833
+ }
2834
+ case SCR_ST_NEXT_EOF: {
2835
+ if (st->r.next_waiter && st->r.length == 0 && (st->r.ended || st->destroyed)) {
2836
+ ScrPromise *w = st->r.next_waiter;
2837
+ st->r.next_waiter = NULL;
2838
+ scr_stream_next_fulfill(st, w, NULL);
2839
+ } else if (st->r.next_waiter) {
2840
+ scr_stream_settle_next(s); /* content raced in: deliver it */
2841
+ }
2842
+ break;
2843
+ }
2844
+ }
2845
+ }
2846
+
2847
+ static void scr_stream_dispatch(void) {
2848
+ while (scr_st_head != NULL && !scr_exc_pending() && !scr_loop_has_ready()) {
2849
+ ScrStreamTick *t = scr_st_head;
2850
+ scr_st_head = t->next;
2851
+ if (scr_st_head == NULL) scr_st_tail = NULL;
2852
+ scr_stream_run_tick(t);
2853
+ scr_stream_release(t->s);
2854
+ if (t->err) scr_error_release(t->err);
2855
+ if (t->cb) scr_closure_release(t->cb);
2856
+ free(t);
2857
+ }
2858
+ /* An uncaught throw ends the process (the loop returns to main): the
2859
+ * undelivered ticks are DROPPED — release their references here so the
2860
+ * RC audit stays clean (the children-teardown precedent). */
2861
+ if (scr_exc_pending()) {
2862
+ while (scr_st_head != NULL) {
2863
+ ScrStreamTick *t = scr_st_head;
2864
+ scr_st_head = t->next;
2865
+ scr_stream_release(t->s);
2866
+ if (t->err) scr_error_release(t->err);
2867
+ if (t->cb) scr_closure_release(t->cb);
2868
+ free(t);
2869
+ }
2870
+ scr_st_tail = NULL;
2871
+ }
2872
+ }
2873
+
2874
+ void scr_stream_install(void) {
2875
+ scr_emitter_on_hook = &scr_stream_on_listener;
2876
+ scr_loop_set_stream(&scr_stream_ticks_pending, &scr_stream_dispatch);
2877
+ }