Add readline/promises, stream.addAbortSignal and the stream/promises options (#983)

This commit is contained in:
Chris Tate
2026-10-14 16:11:25 -05:00
committed by GitHub
48 changed files with 4147 additions and 700 deletions
+1
View File
@@ -52,6 +52,7 @@ A declaration in the standard library does not guarantee that an API compiles. U
- **Text encoding:** `TextEncoder` and `TextDecoder` can be stored in fields, arguments, return values, arrays, and closures. Encoding supports UTF-8, including `encodeInto` into a Uint8Array or Buffer view. Codec properties expose the canonical encoding and decoder flags. Decoding accepts recognized WHATWG labels, including runtime labels and omitted or undefined encodings. Literal constructor options support `fatal` and `ignoreBOM`; literal decode options support streaming UTF-8, UTF-16, and single-byte encodings. Other legacy streaming encodings, runtime options objects, inspection, JSON conversion, and module-scope declarations using `var` or lacking an initializer are unsupported. Use initialized `let` or `const` bindings for stored codecs.
- **EventEmitter:** Literal event names use fixed argument tuples. Computed string names require a constant prefix or suffix that excludes `error`, meta events, and stream-internal events; their arguments must match the exact arity and support checked conversion. Unrestricted names, symbols, names that may reach internal events, and typed `listeners()`/`rawListeners()` results for names with possible computed registrations are unsupported.
- **Streams and readline:** Stream chunks are Buffers or strings. Object-mode streams of other values, `Readable.from` over arrays of other values, the `Readable` iterator helpers (`map`, `filter`, `toArray`, and the others), `stream.compose`, and generator stages in `pipeline` are unsupported. `pipeline` from `node:stream/promises` accepts a trailing `{ signal, end }` object, and `finished` accepts `readable`, `writable`, `error`, `cleanup`, and `signal` options, with boolean literals for the flags. Stream constructors accept a `signal` option in an options object literal, and `stream.addAbortSignal` accepts any stream. `node:readline` and `node:readline/promises` interfaces read `process.stdin` or a readable stream without terminal mode: `question`, `prompt`, `write`, `pause`, `resume`, `'line'`/`'close'`/`'pause'`/`'resume'`/`'error'` listeners, and `for await` compile. Terminal mode, `completer`, history options, and chained listener registration are unsupported.
- **CommonJS metadata:** The module graph is fixed at build time. Cache deletion or reloading, metadata writes, `module.paths` mutation, and `require.extensions` are unsupported. Read-only access to `module.id`, `filename`, `path`, `paths`, `loaded`, `isPreloading`, `parent`, `children`, `require.main`, and `require.cache` lookup/enumeration compiles.
- **ESM metadata:** Native `import.meta.url`, `filename`, `dirname`, and `main` support dot access, statically known keys, and destructuring selected fields. Storing or mutating the metadata object, rest bindings, runtime keys, and stored `resolve` callbacks are unsupported. Native `import.meta.resolve()` requires a literal specifier. Module instances distinguished by URL query strings or fragments are unsupported.
- **Date:** Date unions, year/month constructors, setters, statically typed TypeScript identity comparisons, thrown Date values, and locale/string formatters are unsupported. Zero-argument and single number/string constructors, storage and passing, `getTime`, `valueOf`, `toISOString`, calendar getters, `getTimezoneOffset`, and single-string `Date.parse` compile. JavaScript Dates retain identity through checked storage and support Date-copy construction and JSON serialization. See [Dates and timezones](#dates-and-timezones) for parsing restrictions.
@@ -2,6 +2,6 @@
"schemaVersion": 3,
"nodeVersion": "24.15.0",
"nodeCommit": "848430679556aed0bd073f2bc263331ad84fa119",
"artifactVersion": "442e4b7745b74b802a16",
"artifactVersion": "5bc8e72822dd7065df84",
"rowCount": 3662
}
+61 -61
View File
@@ -599,9 +599,9 @@
"apiEntries": 43,
"static": {
"supported": 0,
"partial": 4,
"partial": 21,
"refused": 0,
"not-implemented": 39,
"not-implemented": 22,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -644,9 +644,9 @@
"apiEntries": 137,
"static": {
"supported": 0,
"partial": 36,
"partial": 38,
"refused": 0,
"not-implemented": 101,
"not-implemented": 99,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -55258,9 +55258,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -55281,9 +55281,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -55465,9 +55465,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -55511,9 +55511,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -55534,9 +55534,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -55764,9 +55764,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -55787,9 +55787,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -55810,9 +55810,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -55833,9 +55833,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -55856,9 +55856,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -55879,9 +55879,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -55902,9 +55902,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -55994,9 +55994,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -56017,9 +56017,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -56040,9 +56040,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -56086,9 +56086,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -56109,9 +56109,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -58110,9 +58110,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented static async-iteration subset.",
"verification": "test-backed"
}
},
{
@@ -58593,9 +58593,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented static async-iteration subset.",
"verification": "test-backed"
}
},
{
@@ -8,8 +8,8 @@
"tiers": {
"static": {
"replace-refusal": 40,
"verify-gap": 2296,
"audit-partial": 852,
"verify-gap": 2277,
"audit-partial": 871,
"classify": 2,
"implement": 80
}
@@ -54988,12 +54988,17 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:readline",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:readline.readlinePromises.Interface",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5298-readline-abort.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
}
}
},
@@ -55013,12 +55018,17 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:readline",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:readline.rl.question",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5298-readline-abort.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
}
}
},
@@ -55213,12 +55223,17 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:readline",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:readline.readlinePromises.createInterface",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5298-readline-abort.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
}
}
},
@@ -55263,12 +55278,17 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:readline",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:readline.readline.Interface",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
}
}
},
@@ -55288,12 +55308,17 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:readline",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:readline.rl.question",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5298-readline-abort.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
}
}
},
@@ -55523,7 +55548,10 @@
"confidence": "high",
"source": "compiler-feature:readline.rl",
"tests": [
"tests/corpus/2844-node-disposable-resources.ts"
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
}
}
@@ -55544,12 +55572,17 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:readline",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:readline.rl.pause",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
}
}
},
@@ -55569,12 +55602,17 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:readline",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:readline.rl.prompt",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
}
}
},
@@ -55594,12 +55632,17 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:readline",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:readline.rl.resume",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
}
}
},
@@ -55619,12 +55662,17 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:readline",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:readline.rl.setPrompt",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
}
}
},
@@ -55644,12 +55692,17 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:readline",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:readline.rl.getPrompt",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
}
}
},
@@ -55669,12 +55722,17 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:readline",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:readline.rl.write",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
}
}
},
@@ -55694,12 +55752,17 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:readline",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:readline.rl",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
}
}
},
@@ -55794,12 +55857,17 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:readline",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:readline.'close'",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
}
}
},
@@ -55819,12 +55887,17 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:readline",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:readline.'error'",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
}
}
},
@@ -55844,12 +55917,17 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:readline",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:readline.'line'",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
}
}
},
@@ -55894,12 +55972,17 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:readline",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:readline.'pause'",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
}
}
},
@@ -55919,12 +56002,17 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:readline",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:readline.'resume'",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
}
}
},
@@ -57552,7 +57640,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -57581,7 +57671,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -57610,7 +57702,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -57639,7 +57733,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -57668,7 +57764,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -57697,7 +57795,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -58043,12 +58143,15 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:stream",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:stream.stream.addAbortSignal",
"tests": [
"tests/corpus/5300-stream-add-abort-signal.ts",
"tests/corpus/5301-stream-add-abort-signal-errors.cjs"
]
}
}
},
@@ -58076,7 +58179,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -58130,7 +58235,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -58159,7 +58266,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -58188,7 +58297,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -58217,7 +58328,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -58246,7 +58359,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -58592,12 +58707,15 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:stream",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:stream.stream.addAbortSignal",
"tests": [
"tests/corpus/5300-stream-add-abort-signal.ts",
"tests/corpus/5301-stream-add-abort-signal-errors.cjs"
]
}
}
},
@@ -58625,7 +58743,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -58704,7 +58824,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -58760,7 +58882,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -58789,7 +58913,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -58818,7 +58944,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -58872,7 +59000,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -59381,7 +59511,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -59410,7 +59542,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -59439,7 +59573,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -59468,7 +59604,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -59497,7 +59635,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -59526,7 +59666,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -59555,7 +59697,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -59584,7 +59728,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -59613,7 +59759,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -60848,7 +60996,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -61002,7 +61152,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -61031,7 +61183,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
}
}
@@ -626,9 +626,9 @@
"apiEntries": 43,
"static": {
"supported": 0,
"partial": 4,
"partial": 21,
"refused": 0,
"not-implemented": 39,
"not-implemented": 22,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -701,9 +701,9 @@
"apiEntries": 137,
"static": {
"supported": 0,
"partial": 36,
"partial": 38,
"refused": 0,
"not-implemented": 101,
"not-implemented": 99,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -75896,7 +75896,11 @@
"evidence": "derived:descendants",
"tests": [
"tests/corpus/1475-readline-closed-stdin.ts",
"tests/corpus/2844-node-disposable-resources.ts"
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5298-readline-abort.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
},
"anchorSource": "chapter"
@@ -75942,8 +75946,14 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:readline"
"status": "partial",
"evidence": "compiler-feature:readline.readlinePromises.Interface",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5298-readline-abort.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
},
"anchorSource": "exact"
},
@@ -75965,8 +75975,14 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:readline"
"status": "partial",
"evidence": "compiler-feature:readline.rl.question",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5298-readline-abort.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
},
"anchorSource": "exact"
},
@@ -76149,8 +76165,14 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:readline"
"status": "partial",
"evidence": "compiler-feature:readline.readlinePromises.createInterface",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5298-readline-abort.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
},
"anchorSource": "exact"
},
@@ -76218,8 +76240,14 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:readline"
"status": "partial",
"evidence": "compiler-feature:readline.readline.Interface",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
},
"anchorSource": "exact"
},
@@ -76241,8 +76269,14 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:readline"
"status": "partial",
"evidence": "compiler-feature:readline.rl.question",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5298-readline-abort.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
},
"anchorSource": "exact"
},
@@ -76480,7 +76514,10 @@
"status": "partial",
"evidence": "compiler-feature:readline.rl",
"tests": [
"tests/corpus/2844-node-disposable-resources.ts"
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
},
"anchorSource": "exact"
@@ -76503,8 +76540,14 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:readline"
"status": "partial",
"evidence": "compiler-feature:readline.rl.pause",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
},
"anchorSource": "exact"
},
@@ -76526,8 +76569,14 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:readline"
"status": "partial",
"evidence": "compiler-feature:readline.rl.prompt",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
},
"anchorSource": "exact"
},
@@ -76549,8 +76598,14 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:readline"
"status": "partial",
"evidence": "compiler-feature:readline.rl.resume",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
},
"anchorSource": "exact"
},
@@ -76572,8 +76627,14 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:readline"
"status": "partial",
"evidence": "compiler-feature:readline.rl.setPrompt",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
},
"anchorSource": "exact"
},
@@ -76595,8 +76656,14 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:readline"
"status": "partial",
"evidence": "compiler-feature:readline.rl.getPrompt",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
},
"anchorSource": "exact"
},
@@ -76618,8 +76685,14 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:readline"
"status": "partial",
"evidence": "compiler-feature:readline.rl.write",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
},
"anchorSource": "exact"
},
@@ -76641,8 +76714,14 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:readline"
"status": "partial",
"evidence": "compiler-feature:readline.rl",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
},
"anchorSource": "exact"
},
@@ -76733,8 +76812,14 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:readline"
"status": "partial",
"evidence": "compiler-feature:readline.'close'",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
},
"anchorSource": "exact"
},
@@ -76756,8 +76841,14 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:readline"
"status": "partial",
"evidence": "compiler-feature:readline.'error'",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
},
"anchorSource": "exact"
},
@@ -76779,8 +76870,14 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:readline"
"status": "partial",
"evidence": "compiler-feature:readline.'line'",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
},
"anchorSource": "exact"
},
@@ -76825,8 +76922,14 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:readline"
"status": "partial",
"evidence": "compiler-feature:readline.'pause'",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
},
"anchorSource": "exact"
},
@@ -76848,8 +76951,14 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:readline"
"status": "partial",
"evidence": "compiler-feature:readline.'resume'",
"tests": [
"tests/corpus/5295-readline-promises-question.ts",
"tests/corpus/5296-readline-lines.ts",
"tests/corpus/5297-readline-callback-events.cjs",
"tests/corpus/5299-readline-stdin.ts"
]
},
"anchorSource": "exact"
},
@@ -79642,7 +79751,11 @@
"tests/corpus/2893-child-stdin-duplex.ts",
"tests/corpus/2894-child-stdin-backpressure.ts",
"tests/corpus/2895-child-stdin-write-after-end.ts",
"tests/corpus/2897-child-stdin-destroy.ts"
"tests/corpus/2897-child-stdin-destroy.ts",
"tests/corpus/5300-stream-add-abort-signal.ts",
"tests/corpus/5301-stream-add-abort-signal-errors.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "chapter"
@@ -79762,7 +79875,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "exact"
@@ -79790,7 +79905,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "exact"
@@ -79818,7 +79935,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "exact"
@@ -79869,7 +79988,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "exact"
@@ -79897,7 +80018,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "exact"
@@ -79925,7 +80048,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "exact"
@@ -80247,8 +80372,12 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:stream"
"status": "partial",
"evidence": "compiler-feature:stream.stream.addAbortSignal",
"tests": [
"tests/corpus/5300-stream-add-abort-signal.ts",
"tests/corpus/5301-stream-add-abort-signal-errors.cjs"
]
},
"anchorSource": "exact"
},
@@ -80275,7 +80404,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "exact"
@@ -80326,7 +80457,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "exact"
@@ -80354,7 +80487,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "exact"
@@ -80405,7 +80540,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "ancestor"
@@ -80433,7 +80570,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "ancestor"
@@ -80461,7 +80600,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "ancestor"
@@ -80783,8 +80924,12 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:stream"
"status": "partial",
"evidence": "compiler-feature:stream.stream.addAbortSignal",
"tests": [
"tests/corpus/5300-stream-add-abort-signal.ts",
"tests/corpus/5301-stream-add-abort-signal-errors.cjs"
]
},
"anchorSource": "ancestor"
},
@@ -80811,7 +80956,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "ancestor"
@@ -80908,7 +81055,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "exact"
@@ -80962,7 +81111,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "exact"
@@ -80990,7 +81141,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "exact"
@@ -81018,7 +81171,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "exact"
@@ -81069,7 +81224,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "exact"
@@ -81633,7 +81790,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "exact"
@@ -81661,7 +81820,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "exact"
@@ -81689,7 +81850,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "exact"
@@ -81717,7 +81880,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "exact"
@@ -81745,7 +81910,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "exact"
@@ -81773,7 +81940,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "exact"
@@ -81801,7 +81970,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "exact"
@@ -81829,7 +82000,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "exact"
@@ -81857,7 +82030,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "exact"
@@ -83204,7 +83379,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "exact"
@@ -83462,7 +83639,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "ancestor"
@@ -83490,7 +83669,9 @@
"tests": [
"tests/corpus/1685-stream-readable-basics.ts",
"tests/corpus/1688-stream-writable-basics.ts",
"tests/corpus/1814-stream-pipeline.cjs"
"tests/corpus/1814-stream-pipeline.cjs",
"tests/corpus/5302-stream-promises-options.ts",
"tests/corpus/5303-stream-finished-callback-opts.cjs"
]
},
"anchorSource": "ancestor"
+4 -1
View File
@@ -170,6 +170,9 @@
{"chapter": "events", "symbols": ["CustomEvent", "detail"], "status": "partial", "evidence": ["tests/corpus/5130-event-target-dispatch.js", "tests/corpus/5132-event-target-typed.ts"]},
{"chapter": "events", "symbols": ["events.errorMonitor"], "status": "partial", "evidence": ["tests/corpus/5134-events-error-monitor.ts", "tests/corpus/5136-events-error-monitor-string-name.ts", "tests/corpus/5137-events-error-monitor-computed-name.js", "tests/corpus/5138-events-error-monitor-shared-listener.ts"]},
{"chapter": "events", "symbols": ["events.addAbortListener"], "status": "partial", "evidence": ["tests/corpus/5133-abort-signal-event-target.ts"]},
{"chapter": "readline", "symbols": ["readlinePromises.Interface", "readlinePromises.createInterface", "rl.question"], "status": "partial", "evidence": ["tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5298-readline-abort.cjs", "tests/corpus/5299-readline-stdin.ts"]},
{"chapter": "readline", "symbols": ["readline.Interface", "rl.pause", "rl.resume", "rl.prompt", "rl.setPrompt", "rl.getPrompt", "rl.write", "rl", "'close'", "'error'", "'line'", "'pause'", "'resume'"], "status": "partial", "evidence": ["tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", "tests/corpus/5299-readline-stdin.ts"]},
{"chapter": "stream", "symbols": ["stream.addAbortSignal"], "status": "partial", "evidence": ["tests/corpus/5300-stream-add-abort-signal.ts", "tests/corpus/5301-stream-add-abort-signal-errors.cjs"]},
{"chapter": "worker_threads", "symbols": ["Worker"], "status": "partial", "evidence": ["tests/corpus/native-worker-messages.ts", "tests/corpus/native-worker-commonjs.cjs", "tests/corpus/native-worker-environment.ts", "tests/corpus/native-worker-roots/main.ts"]},
{"chapter": "worker_threads", "symbols": ["MessagePort", "parentPort", "worker_threads.workerData", "isMainThread", "threadId"], "status": "partial", "evidence": ["tests/corpus/native-worker-commonjs.cjs", "tests/corpus/native-worker-port-lifetime.ts", "tests/corpus/native-worker-references.ts"]},
{"chapter": "worker_threads", "symbols": ["worker.postMessage", "port.postMessage"], "status": "partial", "evidence": ["tests/corpus/native-worker-message-graphs.ts", "tests/corpus/native-worker-shared-views.ts", "tests/corpus/native-worker-clone-errors.ts", "tests/corpus/native-worker-delayed-messages.ts"]},
@@ -314,7 +317,7 @@
{ "symbols": ["AsyncLocalStorage", "asyncLocalStorage.run", "asyncLocalStorage.exit", "asyncLocalStorage.getStore", "asyncLocalStorage.enterWith", "asyncLocalStorage.disable"], "status": "partial", "evidence": ["tests/corpus/2213-async-local-storage.cjs"] },
{ "symbols": ["StringDecoder", "stringDecoder.write", "stringDecoder.end"], "status": "partial", "evidence": ["tests/corpus/1662-string-decoder-encodings.ts", "tests/corpus/node24-string-decoder.ts"] },
{ "symbols": ["setImmediate", "setInterval", "setTimeout", "clearImmediate", "clearInterval", "clearTimeout", "timersPromises.setTimeout", "timersPromises.setImmediate", "timersPromises.setInterval", "immediate.hasRef", "immediate.ref", "immediate.unref", "timeout.hasRef", "timeout.ref", "timeout.unref", "timeout.refresh"], "status": "partial", "evidence": ["tests/corpus/1804-timers-module.ts", "tests/corpus/1805-timer-callback-args.ts", "tests/corpus/2093-timers-promises.ts", "tests/corpus/2852-timers-promises-interval.ts"] },
{ "symbols": ["stream.finished", "stream.pipeline", "stream.getDefaultHighWaterMark", "readable.push", "readable.unshift", "readable.read", "readable.pause", "readable.resume", "readable.isPaused", "readable.setEncoding", "readable.pipe", "readable.unpipe", "readable.destroy", "writable.write", "writable.end", "writable.cork", "writable.uncork", "writable.setDefaultEncoding"], "status": "partial", "evidence": ["tests/corpus/1685-stream-readable-basics.ts", "tests/corpus/1688-stream-writable-basics.ts", "tests/corpus/1814-stream-pipeline.cjs"] },
{ "symbols": ["stream.finished", "stream.pipeline", "stream.getDefaultHighWaterMark", "readable.push", "readable.unshift", "readable.read", "readable.pause", "readable.resume", "readable.isPaused", "readable.setEncoding", "readable.pipe", "readable.unpipe", "readable.destroy", "writable.write", "writable.end", "writable.cork", "writable.uncork", "writable.setDefaultEncoding"], "status": "partial", "evidence": ["tests/corpus/1685-stream-readable-basics.ts", "tests/corpus/1688-stream-writable-basics.ts", "tests/corpus/1814-stream-pipeline.cjs", "tests/corpus/5302-stream-promises-options.ts", "tests/corpus/5303-stream-finished-callback-opts.cjs"] },
{ "symbols": ["net.createServer", "net.connect", "net.createConnection", "net.getDefaultAutoSelectFamilyAttemptTimeout", "net.setDefaultAutoSelectFamilyAttemptTimeout", "server.address", "server.close", "server.listen", "socket.destroy", "socket.end", "socket.pause", "socket.resume", "socket.setEncoding", "socket.setTimeout", "socket.write"], "status": "partial", "evidence": ["tests/corpus/2500-net-autosel-timeout.ts", "tests/corpus/2596-net-arg-ladders.cjs"] },
{ "symbols": ["dgram.createSocket", "socket.address", "socket.bind", "socket.close", "socket.connect", "socket.ref", "socket.remoteAddress", "socket.send", "socket.unref"], "status": "partial", "evidence": ["tests/corpus/2597-dgram-send-ladders.cjs"] },
{ "symbols": ["readline.createInterface", "rl.close"], "status": "partial", "evidence": ["tests/corpus/1475-readline-closed-stdin.ts"] },
+106 -16
View File
@@ -3033,29 +3033,82 @@ declare module "node:querystring" {
* on("close", cb). An open interface keeps the process alive until
* close()/EOF, Node's semantics; lines split on \n and \r\n. */
declare module "readline" {
/* The non-terminal Interface over process.stdin or a stream input
* (scr_readline.c). Listener registrations are statements: chaining
* on()/once() results has no lowering. */
export interface Interface extends Disposable {
question(query: string, callback: (answer: string) => void): void;
close(): void;
on(event: "close", listener: () => void): void;
pause(): this;
resume(): this;
prompt(preserveCursor?: boolean): void;
setPrompt(prompt: string): void;
getPrompt(): string;
write(data: string): void;
on(event: "line", listener: (input: string) => void): this;
on(event: "close" | "pause" | "resume", listener: () => void): this;
on(event: "error", listener: (err: Error) => void): this;
once(event: "line", listener: (input: string) => void): this;
once(event: "close" | "pause" | "resume", listener: () => void): this;
once(event: "error", listener: (err: Error) => void): this;
addListener(event: "line", listener: (input: string) => void): this;
addListener(event: "close" | "pause" | "resume", listener: () => void): this;
addListener(event: "error", listener: (err: Error) => void): this;
[Symbol.asyncIterator](): AsyncIterableIterator<string>;
[Symbol.dispose](): void;
}
export function createInterface(options: {
export interface ReadLineOptions {
input: unknown;
output?: unknown;
terminal?: boolean;
/* crlfDelay: Infinity is accepted — the lowered splitter already
* holds a trailing \r for the next chunk, Node's Infinity behavior;
* completer and the history knobs fence by name (no interactive
* terminal). The options-record stance (see http.RequestOptions)
* admits every key; the walk decides. */
/* Node's crlfDelay: a \n arriving within this many ms (at least 100)
* after a line ended by \r is dropped. completer and the history
* knobs fence by name (no interactive terminal). The options-record
* stance (see http.RequestOptions) admits every key; the walk
* decides. */
crlfDelay?: number;
completer?: unknown;
signal?: AbortSignal;
[option: string]: unknown;
}): Interface;
}
export function createInterface(options: ReadLineOptions): Interface;
export function createInterface(input: unknown, output?: unknown): Interface;
}
declare module "node:readline" {
export * from "readline";
}
declare module "readline/promises" {
import { ReadLineOptions } from "readline";
/* The readline Interface with the promise question: it resolves with
* the next line, rejects with an AbortError when options.signal aborts,
* and with ERR_USE_AFTER_CLOSE after close(). */
export interface Interface extends Disposable {
question(query: string, options?: { signal?: AbortSignal }): Promise<string>;
close(): void;
pause(): this;
resume(): this;
prompt(preserveCursor?: boolean): void;
setPrompt(prompt: string): void;
getPrompt(): string;
write(data: string): void;
on(event: "line", listener: (input: string) => void): this;
on(event: "close" | "pause" | "resume", listener: () => void): this;
on(event: "error", listener: (err: Error) => void): this;
once(event: "line", listener: (input: string) => void): this;
once(event: "close" | "pause" | "resume", listener: () => void): this;
once(event: "error", listener: (err: Error) => void): this;
addListener(event: "line", listener: (input: string) => void): this;
addListener(event: "close" | "pause" | "resume", listener: () => void): this;
addListener(event: "error", listener: (err: Error) => void): this;
[Symbol.asyncIterator](): AsyncIterableIterator<string>;
[Symbol.dispose](): void;
}
export function createInterface(options: ReadLineOptions): Interface;
export function createInterface(input: unknown, output?: unknown): Interface;
}
declare module "node:readline/promises" {
export * from "readline/promises";
}
/* node:zlib — the one-shot zlib/raw/gzip codecs lower for string/Buffer
* inputs with Node's default options; libz links only into zlib-using
@@ -4731,7 +4784,7 @@ declare module "stream" {
emitClose?: boolean;
captureRejections?: boolean;
defaultEncoding?: string;
signal?: unknown;
signal?: AbortSignal;
}
interface WritableOptions {
highWaterMark?: number;
@@ -4751,7 +4804,7 @@ declare module "stream" {
autoDestroy?: boolean;
emitClose?: boolean;
captureRejections?: boolean;
signal?: unknown;
signal?: AbortSignal;
}
interface DuplexOptions {
highWaterMark?: number;
@@ -4780,7 +4833,7 @@ declare module "stream" {
writable?: boolean;
defaultEncoding?: string;
writev?(this: Duplex, chunks: unknown[], callback: (error?: Error | null) => void): void;
signal?: unknown;
signal?: AbortSignal;
}
interface TransformOptions {
highWaterMark?: number;
@@ -4816,7 +4869,7 @@ declare module "stream" {
readable?: boolean;
writable?: boolean;
defaultEncoding?: string;
signal?: unknown;
signal?: AbortSignal;
}
class Readable extends EventEmitter {
@@ -5060,15 +5113,34 @@ declare module "stream" {
constructor(opts?: TransformOptions);
}
/* addAbortSignal(signal, stream): the stream, destroyed with an
* AbortError (cause: signal.reason) when the signal aborts. */
function addAbortSignal<T extends Readable | Writable>(signal: AbortSignal, stream: T): T;
/* Declared so requires typecheck; each fences at its use site. */
function pipeline(...streams: unknown[]): unknown;
/* finished's options: readable/writable choose which ends count,
* error: false ignores 'error' events, signal aborts the wait. */
interface FinishedOptions {
readable?: boolean;
writable?: boolean;
error?: boolean;
signal?: AbortSignal;
}
function finished(stream: unknown, callback: (err?: Error | null) => void): () => void;
function finished(
stream: unknown,
options: FinishedOptions | null,
callback: (err?: Error | null) => void,
): () => void;
/* Folds to the TARGET platform's default at compile time (win32 16384,
* else 65536 — Node's own state.js split); literal argument only. */
function getDefaultHighWaterMark(objectMode: boolean): number;
namespace promises {
function pipeline(...streams: unknown[]): Promise<void>;
function finished(stream: unknown): Promise<void>;
function finished(
stream: unknown,
options?: FinishedOptions & { cleanup?: boolean },
): Promise<void>;
}
class Stream extends EventEmitter {}
@@ -5079,12 +5151,19 @@ declare module "stream" {
Transform,
PassThrough,
Stream,
addAbortSignal,
pipeline,
finished,
getDefaultHighWaterMark,
promises,
};
export type { ReadableOptions, WritableOptions, DuplexOptions, TransformOptions };
export type {
ReadableOptions,
WritableOptions,
DuplexOptions,
TransformOptions,
FinishedOptions,
};
}
declare module "node:stream" {
import stream = require("stream");
@@ -5094,9 +5173,20 @@ declare module "stream/promises" {
/* The promise forms of finished/pipeline: a void promise the stream's
* terminal point settles (rejected with the error, or
* ERR_STREAM_PREMATURE_CLOSE on an early close). Stream arguments are
* the lowered surface — iterables/generators/functions fence per site. */
* the lowered surface — iterables/generators/functions fence per site.
* pipeline's trailing { signal, end } object and finished's options
* (readable, writable, error, cleanup, signal) are supported. */
function pipeline(...streams: unknown[]): Promise<void>;
function finished(stream: unknown): Promise<void>;
function finished(
stream: unknown,
options?: {
readable?: boolean;
writable?: boolean;
error?: boolean;
cleanup?: boolean;
signal?: AbortSignal;
} | null,
): Promise<void>;
export { pipeline, finished };
}
declare module "node:stream/promises" {
@@ -36,13 +36,43 @@ export function emitAssertInspectLibCall(host: LlvmEmitterContext, e: LibCallExp
export function emitIoLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue {
const B = host.B;
if (e.fn === "rl.create") {
if (e.fn === "rl.create" || e.fn === "rl.createSignal") {
// readline interface handles are runtime IDs (doubles); an open
// interface holds the loop.
// interface on stdin holds the loop. The mode literal says which
// optional arguments follow (lowerReadlineCreateCall): a stream input
// or output goes through scr_stream.c's bridge.
host.usesTimers = true;
host.declare(`declare double @scr_rl_create()`);
const modeArg = e.args[0]!;
if (modeArg.kind !== "numLit") {
throw new InternalCompilerError("llvm emitter bug: rl.create mode is not a literal");
}
const mode = modeArg.value;
let i = 1;
const input = mode & 1 ? host.emitExpr(e.args[i++]!).name : "null";
const output = mode & 2 ? host.emitExpr(e.args[i++]!).name : "null";
const crlf = host.emitExpr(e.args[i++]!).name;
const prompt = mode & 16 ? host.emitExpr(e.args[i++]!).name : "null";
const signal = mode & 8 ? host.emitExpr(e.args[i++]!).name : "null";
let add = "null";
if (mode & 8) {
host.declare(`declare ptr @scr_events_add_abort_listener(ptr, ptr)`);
add = "@scr_events_add_abort_listener";
}
const stdout = mode & 4 ? "true" : "false";
const t = B.tmp();
B.line(`${t} = call double @scr_rl_create()`);
if (mode & 3) {
host.declare(`declare double @scr_stream_rl_open(ptr, ptr, i1, double, ptr, ptr, ptr)`);
B.line(
`${t} = call double @scr_stream_rl_open(ptr ${input}, ptr ${output}, i1 ${stdout}, double ${crlf}, ptr ${prompt}, ptr ${signal}, ptr ${add})`,
);
} else {
host.declare(`declare double @scr_rl_open(ptr, ptr, ptr, i1, double, ptr, ptr, ptr)`);
B.line(
`${t} = call double @scr_rl_open(ptr null, ptr null, ptr null, i1 ${stdout}, double ${crlf}, ptr ${prompt}, ptr ${signal}, ptr ${add})`,
);
}
// A signal that is not an AbortSignal throws ERR_INVALID_ARG_TYPE.
if (mode & 8) host.emitPendingCheck();
return { name: t, type: e.type };
}
if (e.fn === "rl.question") {
@@ -54,7 +84,12 @@ export function emitIoLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue
throw new InternalCompilerError("llvm emitter bug: rl.question callback not a func");
const args = e.args.map((a) => host.emitExpr(a));
host.moveTemp(args[2]!);
const adapter = cbT.params.length === 0 ? "scr_rl_answer_thunk0" : "scr_rl_answer_thunk_str";
const adapter =
cbT.params.length === 0
? "scr_rl_answer_thunk0"
: cbT.params[0]!.kind === "dyn"
? "scr_rl_answer_thunk_dyn"
: "scr_rl_answer_thunk_str";
host.declare(`declare void @${adapter}(ptr, ptr)`);
host.declare(`declare void @scr_rl_question(double, ptr, ptr, ptr)`);
B.line(
@@ -63,20 +98,53 @@ export function emitIoLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue
host.emitPendingCheck();
return { name: "", type: e.type };
}
if (e.fn === "rl.close") {
const id = host.emitExpr(e.args[0]!);
host.declare(`declare void @scr_rl_close(double)`);
B.line(`call void @scr_rl_close(double ${id.name})`);
return { name: "", type: e.type };
}
if (e.fn === "rl.onClose") {
// The close listener MOVES into the interface's registry.
if (e.fn === "rl.questionAsync" || e.fn === "rl.questionAsyncSignal") {
// The promise question never throws: closed interfaces and aborted
// signals reject the answered promise (+1).
host.usesTimers = true;
const args = e.args.map((a) => host.emitExpr(a));
let signal = "null";
let add = "null";
if (e.fn === "rl.questionAsyncSignal") {
signal = args[2]!.name;
host.declare(`declare ptr @scr_events_add_abort_listener(ptr, ptr)`);
add = "@scr_events_add_abort_listener";
}
host.declare(`declare ptr @scr_rl_question_promise(double, ptr, ptr, ptr)`);
const t = B.tmp();
B.line(
`${t} = call ptr @scr_rl_question_promise(double ${args[0]!.name}, ptr ${args[1]!.name}, ptr ${signal}, ptr ${add})`,
);
return host.own({ name: t, type: e.type });
}
if (e.fn === "rl.on") {
// The listener MOVES into the interface's registry; the adapter is
// picked by event and parameter count ('line': the text, 'error': the
// error, the rest none).
host.usesTimers = true;
const cbT = e.args[2]!.type;
const evArg = e.args[1]!;
if (cbT.kind !== "func" || evArg.kind !== "numLit") {
throw new InternalCompilerError("llvm emitter bug: rl.on operands");
}
const id = host.emitExpr(e.args[0]!);
const cb = host.emitExpr(e.args[1]!);
const ev = host.emitExpr(evArg);
const cb = host.emitExpr(e.args[2]!);
const once = host.emitExpr(e.args[3]!);
host.moveTemp(cb);
host.declare(`declare void @scr_rl_on_close(double, ptr)`);
B.line(`call void @scr_rl_on_close(double ${id.name}, ptr ${cb.name})`);
let adapter = "null";
if (evArg.value === 0 || evArg.value === 4) {
const param = cbT.params[0];
const kind =
param === undefined ? "0" : param.kind === "dyn" ? "_dyn" : evArg.value === 0 ? "_str" : "";
const name = evArg.value === 0 ? `scr_rl_answer_thunk${kind}` : `scr_rl_error_thunk${kind}`;
host.declare(`declare void @${name}(ptr, ptr)`);
adapter = `@${name}`;
}
host.declare(`declare void @scr_rl_on(double, double, ptr, ptr, i1)`);
B.line(
`call void @scr_rl_on(double ${id.name}, double ${ev.name}, ptr ${cb.name}, ptr ${adapter}, i1 ${once.name})`,
);
return { name: "", type: e.type };
}
return host.emitGenericLibCall(e);
@@ -315,7 +383,12 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
export function emitLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue {
if (USES_TIMERS_LIB_FNS.has(e.fn)) host.usesTimers = true;
if (e.fn === "process.nextTick") return host.emitAsyncContextLibCall(e);
if (e.fn === "sp.pipeline") return host.emitStreamLibCall(e);
if (
e.fn === "sp.pipeline" ||
e.fn.startsWith("sp.pipelineOpts") ||
e.fn.startsWith("sp.finishedOpts")
)
return host.emitStreamLibCall(e);
if (e.fn === "bigint.parse" && e.args[0]?.kind === "strLit") {
// Literals (`123n`) and constant BigInt("...") texts are immortal
// statics instead of a parse at every evaluation.
@@ -853,6 +853,18 @@ export const LIB_FN_SYMS: Record<string, string> = {
"readable.resume": "scr_stream_resume",
"readable.isPaused": "scr_stream_is_paused",
"readable.setEncoding": "scr_stream_set_encoding",
// node:readline's plain method calls (scr_readline.c): borrowed
// operands; getPrompt answers +1, iterNext a +1 promise.
"rl.close": "scr_rl_close",
"rl.pause": "scr_rl_pause",
"rl.resume": "scr_rl_resume",
"rl.prompt": "scr_rl_prompt",
"rl.setPrompt": "scr_rl_set_prompt",
"rl.getPrompt": "scr_rl_get_prompt",
"rl.write": "scr_rl_write",
"rl.iterOpen": "scr_rl_iter_open",
"rl.iterNext": "scr_rl_iter_next",
"rl.iterReturn": "scr_rl_iter_return",
"readable.nextChunk": "scr_stream_next_chunk",
"readable.nextChunkDyn": "scr_stream_next_chunk_dyn",
"readable.fromArr": "scr_stream_from_arr",
@@ -1242,6 +1254,12 @@ export const USES_TIMERS_LIB_FNS = new Set<string>([
"stream.pipelineDyn",
"sp.finished",
"sp.pipeline",
"sp.pipelineOpts",
"sp.pipelineOptsSignal",
"sp.finishedOpts",
"sp.finishedOptsSignal",
"stream.finishedOpts",
"stream.finishedOptsSignal",
"sc.text",
"sc.json",
"sc.buffer",
@@ -13,6 +13,29 @@ const STREAM_CANONICAL_CBS: Record<string, ("r" | "w" | "f" | "d" | "t" | "l")[]
export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue {
const B = host.B;
if (e.fn === "stream.addAbortSignal" || e.fn === "stream.addAbortSignalFn") {
// stream.addAbortSignal: [stream, signal] — the constructors' signal
// option chained over the fresh stream (a falsy signal is ignored);
// stream.addAbortSignalFn: [signal, stream] — the module function, in
// source order. Both answer the stream +1, or NULL with
// ERR_INVALID_ARG_TYPE pending; addAbortListener comes from the
// event-target unit the call links.
host.usesTimers = true;
const option = e.fn === "stream.addAbortSignal";
const first = host.emitExpr(e.args[0]!);
const second = host.emitExpr(e.args[1]!);
const stream = option ? first : second;
const signal = option ? second : first;
host.declare(`declare ptr @scr_events_add_abort_listener(ptr, ptr)`);
host.declare(`declare ptr @scr_stream_add_abort_signal(ptr, ptr, i1, ptr)`);
const t = B.tmp();
B.line(
`${t} = call ptr @scr_stream_add_abort_signal(ptr ${stream.name}, ptr ${signal.name}, i1 ${option}, ptr @scr_events_add_abort_listener)`,
);
const out = host.own({ name: t, type: e.type });
host.emitPendingCheck();
return out;
}
if (e.fn === "stream.onData") {
// The callback MOVES into the stream's listener registry; the
// adapter is per callback shape — runtime-provided for the
@@ -500,6 +523,95 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
host.emitPendingCheck();
return out;
}
if (e.fn === "sp.pipelineOpts" || e.fn === "sp.pipelineOptsSignal") {
// pipeline(count, s1..sn, end[, signal]) settling a void promise.
const countArg = e.args[0]!;
if (countArg.kind !== "numLit")
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} count not a literal`);
const n = countArg.value;
const args = e.args.map((a) => host.emitExpr(a));
const arr = B.slot();
B.entryAllocas.push(`${arr} = alloca [${n} x ptr]`);
for (let i = 0; i < n; i++) {
const p = B.tmp();
B.line(`${p} = getelementptr inbounds [${n} x ptr], ptr ${arr}, i64 0, i64 ${i}`);
B.line(`store ptr ${args[1 + i]!.name}, ptr ${p}`);
}
const end = args[1 + n]!.name;
let signal = "null";
let add = "null";
if (e.fn === "sp.pipelineOptsSignal") {
signal = args[2 + n]!.name;
host.declare(`declare ptr @scr_events_add_abort_listener(ptr, ptr)`);
add = "@scr_events_add_abort_listener";
}
const t = B.tmp();
host.declare(`declare ptr @scr_sp_pipeline_opts(double, ptr, i1, ptr, ptr)`);
B.line(
`${t} = call ptr @scr_sp_pipeline_opts(double ${f64Lit(n)}, ptr ${arr}, i1 ${end}, ptr ${signal}, ptr ${add})`,
);
const out = host.own({ name: t, type: e.type });
host.emitPendingCheck();
return out;
}
if (e.fn === "sp.finishedOpts" || e.fn === "sp.finishedOptsSignal") {
// finished(s, options) → a pending void promise (+1).
const args = e.args.map((a) => host.emitExpr(a));
let signal = "null";
let add = "null";
if (e.fn === "sp.finishedOptsSignal") {
signal = args[5]!.name;
host.declare(`declare ptr @scr_events_add_abort_listener(ptr, ptr)`);
add = "@scr_events_add_abort_listener";
}
const t = B.tmp();
host.declare(`declare ptr @scr_sp_finished_opts(ptr, double, double, i1, i1, ptr, ptr)`);
B.line(
`${t} = call ptr @scr_sp_finished_opts(ptr ${args[0]!.name}, double ${args[1]!.name}, double ${args[2]!.name}, i1 ${args[3]!.name}, i1 ${args[4]!.name}, ptr ${signal}, ptr ${add})`,
);
return host.own({ name: t, type: e.type });
}
if (e.fn === "stream.finishedOpts" || e.fn === "stream.finishedOptsSignal") {
// finished(s, options, cb) → the +1 cleanup closure; typed callbacks
// ride the "e" thunk, checked-dynamic ones the runtime's own.
const args = e.args.map((a) => host.emitExpr(a));
const cbIdx = e.args.length - 1;
let signal = "null";
let add = "null";
if (e.fn === "stream.finishedOptsSignal") {
signal = args[4]!.name;
host.declare(`declare ptr @scr_events_add_abort_listener(ptr, ptr)`);
add = "@scr_events_add_abort_listener";
}
const head = `ptr ${args[0]!.name}`;
const flags = `double ${args[1]!.name}, double ${args[2]!.name}, i1 ${args[3]!.name}, ptr ${signal}, ptr ${add}`;
const t = B.tmp();
const cbT = e.args[cbIdx]!.type;
if (cbT.kind === "dyn") {
host.declare(
`declare ptr @scr_stream_finished_opts_dyn(ptr, ptr, double, double, i1, ptr, ptr)`,
);
B.line(
`${t} = call ptr @scr_stream_finished_opts_dyn(${head}, ptr ${args[cbIdx]!.name}, ${flags})`,
);
} else {
if (cbT.kind !== "func")
throw new InternalCompilerError(
"llvm emitter bug: stream.finishedOpts callback not a func",
);
host.moveTemp(args[cbIdx]!);
const thunk = host.streamCbThunkFor("e", cbT);
host.declare(
`declare ptr @scr_stream_finished_opts(ptr, ptr, ptr, double, double, i1, ptr, ptr)`,
);
B.line(
`${t} = call ptr @scr_stream_finished_opts(${head}, ptr ${args[cbIdx]!.name}, ptr @${thunk}, ${flags})`,
);
}
const out = host.own({ name: t, type: e.type });
host.emitPendingCheck();
return out;
}
if (e.fn === "stream.pipeline" || e.fn === "stream.pipelineDyn") {
// pipeline(count, s1..sn, cb): the destination answers +1. The
// stream list rides a stack array (the C compound literal).
@@ -21,6 +21,7 @@ export const SUPPORTED_BUILTIN_MODULES = [
"string_decoder",
"querystring",
"readline",
"readline/promises",
"http2",
"assert",
"assert/strict",
@@ -181,7 +181,10 @@ export function lowerBuiltinModuleCall(
const lowered = lowerDiagnosticsChannelCall(lowerer, expr, bi, loc);
if (lowered) return lowered;
}
if (bi.module === "readline" && bi.member === "createInterface") {
if (
(bi.module === "readline" || bi.module === "readline/promises") &&
bi.member === "createInterface"
) {
return lowerReadlineCreateCall(lowerer, expr, loc);
}
if (bi.module === "fs" && bi.member === "readFileSync" && expr.arguments.length === 2) {
@@ -1,39 +1,91 @@
import * as ts from "../../ts7/adapter.js";
import { type Lowerer } from "../lowerer.js";
import { locOf } from "../../program.js";
import { F64, type IrExpr, type SrcLoc, STRING, VOID } from "../../../ir/ir.js";
import { DYN, F64, type IrExpr, type SrcLoc, STRING, VOID } from "../../../ir/ir.js";
import { boolLit, numLit } from "../../../ir/build.js";
import { READLINE_DOCUMENTED_OPTIONS, fenceOrDropOptionKey } from "../surfaces.js";
import { streamSidesOf } from "../lower-stream.js";
/** True when `node`'s checker type is readline's Interface (stdlib
* provenance + the enclosing "readline" ambient module — the name alone
* is too generic). The interface maps to an f64 handle, so the IR type
* cannot discriminate it from a plain number. */
function isReadlineTyped(lowerer: Lowerer, node: ts.Expression): boolean {
/** The ambient module declaring `d`, or null. */
function ambientModuleOf(d: ts.Node): string | null {
let up: ts.Node | undefined = d.parent;
while (up) {
if (ts.isModuleDeclaration(up) && ts.isStringLiteral(up.name)) return up.name.text;
up = up.parent;
}
return null;
}
function isReadlineModule(spec: string | null): boolean {
return (
spec === "readline" ||
spec === "node:readline" ||
spec === "readline/promises" ||
spec === "node:readline/promises"
);
}
/** True when `node`'s checker type is a readline Interface (stdlib
* provenance + the enclosing "readline" or "readline/promises" ambient
* module — the name alone is too generic). The interface maps to an f64
* handle, so the IR type cannot discriminate it from a plain number. */
export function isReadlineTyped(lowerer: Lowerer, node: ts.Expression): boolean {
const t = lowerer.checker.getTypeAtLocation(node);
const sym = t.getAliasSymbol() ?? t.getSymbol();
if (sym?.name !== "Interface") return false;
return lowerer.checker.declarationsOf(sym).some((d) => {
if (!ts.isClassDeclaration(d) && !ts.isInterfaceDeclaration(d)) return false;
if (!lowerer.isStdlibFile(d.getSourceFile())) return false;
let up: ts.Node | undefined = d.parent;
while (up) {
if (ts.isModuleDeclaration(up) && ts.isStringLiteral(up.name)) {
return up.name.text === "readline" || up.name.text === "node:readline";
}
up = up.parent;
}
return false;
return isReadlineModule(ambientModuleOf(d));
});
}
/** Method calls on readline Interface receivers: `rl.question(query, cb)`
* writes the query to stdout and delivers the next stdin line's text to
* the callback (one (answer: string) parameter, or none); `rl.close()`
* fires the 'close' listeners synchronously (Node's inline emit) and
* releases the loop; `rl.on("close", cb)` registers a zero-arg
* listener. Everything else the lib declares (on("line"), prompt,
* setPrompt, ...) fences member-qualified. Null for non-Interface
* receivers. */
/** The runtime's listener event numbers (scr_readline.c). */
const RL_EVENTS: Record<string, number> = { line: 0, close: 1, pause: 2, resume: 3, error: 4 };
/** True when the call's resolved signature answers a Promise — the
* readline/promises question. */
function returnsPromise(lowerer: Lowerer, call: ts.CallExpression): boolean {
const t = lowerer.checker.getTypeAtLocation(call);
return (t.getSymbol()?.name ?? "") === "Promise";
}
/** The `{ signal }` options argument of the promise question: the signal
* expression, or null when absent. Other members fence by name. */
function questionSignal(lowerer: Lowerer, node: ts.Expression | undefined): ts.Expression | null {
if (node === undefined) return null;
if (!ts.isObjectLiteralExpression(node)) {
lowerer.noLowering(
"readline/promises question options that are not an object literal",
node,
"write the options inline: question(query, { signal })",
);
}
let signal: ts.Expression | null = null;
for (const p of node.properties) {
if (ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === "signal") {
signal = p.initializer;
continue;
}
lowerer.noLowering(
"readline/promises question with this option",
p,
"signal is the supported question option",
);
}
return signal;
}
/** Method calls on readline Interface receivers (both modules):
* - question(query, cb) — the callback form; question(query, { signal }?)
* — the promise form (readline/promises);
* - close(), pause(), resume(), prompt(), setPrompt(p), getPrompt(),
* write(data);
* - on/once/addListener for 'line' ((input: string) or no parameters),
* 'close'/'pause'/'resume' (no parameters) and 'error' ((err: Error) or
* no parameters), as statements.
* Everything else the lib declares fences member-qualified. Null for
* non-Interface receivers. */
export function lowerReadlineMethodCall(
lowerer: Lowerer,
call: ts.CallExpression,
@@ -44,159 +96,314 @@ export function lowerReadlineMethodCall(
if (!lowerer.isStdlibMember(access)) return null;
const name = access.name.text;
const loc = locOf(call);
if (name === "question" && call.arguments.length === 2) {
const receiver = lowerer.lowerExprExpecting(access.expression, F64);
const query = lowerer.lowerExprExpecting(call.arguments[0]!, STRING);
const cb = lowerer.lowerExpr(call.arguments[1]!);
const args = call.arguments;
const receiver = (): IrExpr => lowerer.lowerExprExpecting(access.expression, F64);
if (
name === "question" &&
returnsPromise(lowerer, call) &&
args.length >= 1 &&
args.length <= 2
) {
const recv = receiver();
const query = lowerer.lowerExprExpecting(args[0]!, STRING);
const signal = questionSignal(lowerer, args[1]);
const type = { kind: "promise", inner: STRING } as const;
if (signal === null) {
return { kind: "libCall", fn: "rl.questionAsync", args: [recv, query], type, loc };
}
const sig = lowerer.lowerExprExpecting(signal, DYN);
return { kind: "libCall", fn: "rl.questionAsyncSignal", args: [recv, query, sig], type, loc };
}
if (name === "question" && args.length === 2) {
const recv = receiver();
const query = lowerer.lowerExprExpecting(args[0]!, STRING);
const cb = lowerer.lowerExpr(args[1]!);
if (cb.type.kind !== "func" || cb.type.ret.kind !== "void" || cb.type.params.length > 1) {
lowerer.unsupported(
"SC1090",
call.arguments[1]!,
args[1]!,
"question callbacks with more than one parameter or a return value",
);
}
const param = cb.type.params[0];
if (param !== undefined && param.kind !== "string") {
if (param !== undefined && param.kind !== "string" && param.kind !== "dyn") {
lowerer.unsupported(
"SC1090",
call.arguments[1]!,
args[1]!,
`question callbacks whose parameter is not 'string' (got '${lowerer.fmt(param)}')`,
);
}
return { kind: "libCall", fn: "rl.question", args: [receiver, query, cb], type: VOID, loc };
return { kind: "libCall", fn: "rl.question", args: [recv, query, cb], type: VOID, loc };
}
if (name === "close" && call.arguments.length === 0) {
const receiver = lowerer.lowerExprExpecting(access.expression, F64);
return { kind: "libCall", fn: "rl.close", args: [receiver], type: VOID, loc };
}
if (name === "on" && call.arguments.length === 2) {
const evT = lowerer.typeOf(call.arguments[0]!);
const event = evT.isStringLiteralType() ? evT.value : null;
if (event !== "close") {
const simple: Record<string, "rl.close" | "rl.pause" | "rl.resume" | "rl.prompt"> = {
close: "rl.close",
pause: "rl.pause",
resume: "rl.resume",
prompt: "rl.prompt",
};
const simpleFn = simple[name];
if (simpleFn !== undefined && (args.length === 0 || (name === "prompt" && args.length === 1))) {
if ((name === "pause" || name === "resume") && !ts.isExpressionStatement(call.parent)) {
lowerer.unsupported(
"SC1090",
call,
`using the result of readline ${name}() (call it as its own statement)`,
);
}
// prompt(preserveCursor) only moves the cursor of a terminal
// interface: a boolean literal is accepted and has no effect here.
const flag = args[0];
if (
flag !== undefined &&
flag.kind !== ts.SyntaxKind.TrueKeyword &&
flag.kind !== ts.SyntaxKind.FalseKeyword
) {
lowerer.noLowering(
`readline.Interface.on(${event === null ? "non-literal event" : `"${event}"`}, ...)`,
call.arguments[0]!,
'"close" is the supported readline event (question(query, cb) is the line consumer)',
"readline prompt() with a non-literal preserveCursor",
flag,
"preserveCursor only affects terminal interfaces — pass a boolean literal or omit it",
);
}
return { kind: "libCall", fn: simpleFn, args: [receiver()], type: VOID, loc };
}
if (name === "setPrompt" && args.length === 1) {
const recv = receiver();
const prompt = lowerer.lowerExprExpecting(args[0]!, STRING);
return { kind: "libCall", fn: "rl.setPrompt", args: [recv, prompt], type: VOID, loc };
}
if (name === "getPrompt" && args.length === 0) {
return { kind: "libCall", fn: "rl.getPrompt", args: [receiver()], type: STRING, loc };
}
if (name === "write" && args.length === 1) {
const recv = receiver();
const data = lowerer.lowerExprExpecting(args[0]!, STRING);
return { kind: "libCall", fn: "rl.write", args: [recv, data], type: VOID, loc };
}
if ((name === "on" || name === "once" || name === "addListener") && args.length === 2) {
const evT = lowerer.typeOf(args[0]!);
const event = evT.isStringLiteralType() ? evT.value : null;
const ev = event === null ? undefined : RL_EVENTS[event];
if (ev === undefined) {
lowerer.noLowering(
`readline.Interface.${name}(${event === null ? "non-literal event" : `"${event}"`}, ...)`,
args[0]!,
'"line", "close", "pause", "resume" and "error" are the supported readline events',
);
}
if (!ts.isExpressionStatement(call.parent)) {
lowerer.unsupported(
"SC1090",
call,
"chaining readline listener registration (the result is void here — register each listener as its own statement)",
"chaining readline listener registration (register each listener as its own statement)",
);
}
const receiver = lowerer.lowerExprExpecting(access.expression, F64);
const cb = lowerer.lowerExpr(call.arguments[1]!);
if (cb.type.kind !== "func" || cb.type.ret.kind !== "void" || cb.type.params.length > 0) {
const recv = receiver();
const cb = lowerer.lowerExpr(args[1]!);
const want = event === "line" ? "string" : event === "error" ? "error" : null;
const param = cb.type.kind === "func" ? cb.type.params[0] : undefined;
const paramOk =
param === undefined ||
(want !== null && param.kind === "dyn") ||
(want === "string" && param.kind === "string") ||
(want === "error" && param.kind === "object" && param.className === "Error");
if (
cb.type.kind !== "func" ||
cb.type.ret.kind !== "void" ||
cb.type.params.length > (want === null ? 0 : 1) ||
!paramOk
) {
lowerer.unsupported(
"SC1090",
call.arguments[1]!,
"close listeners with parameters or a return value (use ())",
args[1]!,
want === "string"
? "'line' listeners other than (input: string) => void"
: want === "error"
? "'error' listeners other than (err: Error) => void"
: `'${event}' listeners with parameters or a return value (use ())`,
);
}
return { kind: "libCall", fn: "rl.onClose", args: [receiver, cb], type: VOID, loc };
return {
kind: "libCall",
fn: "rl.on",
args: [recv, numLit(ev, loc), cb, boolLit(name === "once", loc)],
type: VOID,
loc,
};
}
lowerer.noLowering(
`readline.Interface.${name}`,
call,
'question(query, cb), close(), and on("close", cb) are the supported Interface members',
"question, close, pause, resume, prompt, setPrompt, getPrompt, write, for await, and on/once for 'line', 'close', 'pause', 'resume' and 'error' are the supported Interface members",
lowerer.checker.getSymbolAtLocation(access.name),
);
}
// readline.createInterface({ input: process.stdin, output:
// process.stdout }): exactly that options shape — the runtime reads
// fd 0 and writes prompts to stdout, so any OTHER stream would be a
// lie. `terminal` is accepted only as the literal false (the pipe
// behavior this implements); other members fence by name.
/** The interface's input: process.stdin (null) or a readable stream. */
function lowerInput(lowerer: Lowerer, node: ts.Expression): IrExpr | null {
if (
ts.isPropertyAccessExpression(node) &&
lowerer.stdlibGlobalMember(node, "process") === "stdin"
) {
return null;
}
const v = lowerer.lowerExpr(node);
const info = v.type.kind === "object" ? lowerer.classes.get(v.type.className) : undefined;
const sides = streamSidesOf(lowerer, info);
if (sides !== "r" && sides !== "rw") {
lowerer.noLowering(
"createInterface with this input",
node,
"process.stdin and readable streams are the supported inputs",
);
}
return v;
}
/** The interface's output: "stdout", a writable stream, or "none". */
function lowerOutput(lowerer: Lowerer, node: ts.Expression): IrExpr | "stdout" | "none" {
if (
ts.isPropertyAccessExpression(node) &&
lowerer.stdlibGlobalMember(node, "process") === "stdout"
) {
return "stdout";
}
if (node.kind === ts.SyntaxKind.NullKeyword) {
return "none";
}
if (ts.isIdentifier(node) && node.text === "undefined") return "none";
const v = lowerer.lowerExpr(node);
const info = v.type.kind === "object" ? lowerer.classes.get(v.type.className) : undefined;
const sides = streamSidesOf(lowerer, info);
if (sides !== "w" && sides !== "rw") {
lowerer.noLowering(
"createInterface with this output",
node,
"process.stdout and writable streams are the supported outputs",
);
}
return v;
}
/** createInterface({ input, output?, terminal?: false, crlfDelay?,
* prompt?, signal? }) or createInterface(input, output?), from
* node:readline or node:readline/promises. The input is process.stdin
* or a readable stream; the output process.stdout, a writable stream, or
* absent (prompts are then not written). rl.create's arguments: the mode
* literal (1 stream input, 2 stream output, 4 stdout output, 8 signal,
* 16 prompt), then the input and output streams when present, crlfDelay,
* the prompt and the signal when present. */
export function lowerReadlineCreateCall(
lowerer: Lowerer,
expr: ts.CallExpression,
loc: SrcLoc,
): IrExpr {
const optsNode = expr.arguments.length === 1 ? expr.arguments[0] : undefined;
if (!optsNode || !ts.isObjectLiteralExpression(optsNode)) {
let input: IrExpr | null = null;
let output: IrExpr | "stdout" | "none" = "none";
let crlf: IrExpr = numLit(0, loc);
let prompt: IrExpr | null = null;
let signal: IrExpr | null = null;
const optsNode = expr.arguments[0];
if (optsNode === undefined) {
lowerer.noLowering(
"createInterface with this argument shape",
"createInterface without arguments",
expr,
"the supported form is createInterface({ input: process.stdin, output: process.stdout })",
"pass { input: process.stdin, output: process.stdout } or createInterface(input, output)",
);
}
let sawInput = false;
for (const p of optsNode.properties) {
if (!ts.isPropertyAssignment(p) || !ts.isIdentifier(p.name)) {
if (!ts.isObjectLiteralExpression(optsNode)) {
if (expr.arguments.length > 2) {
lowerer.noLowering(
"createInterface with this options shape",
p,
"spreads, computed keys, and shorthand options have no lowering — write each member inline",
"createInterface with a completer or terminal argument",
expr.arguments[2]!,
"the positional form createInterface(input, output) is supported (terminal line editing has no lowering)",
);
}
const member = p.name.text;
const streamOf = (node: ts.Expression): string | null =>
ts.isPropertyAccessExpression(node) ? lowerer.stdlibGlobalMember(node, "process") : null;
if (member === "input") {
if (streamOf(p.initializer) !== "stdin") {
lowerer.noLowering(
"createInterface with a non-stdin input",
p.initializer,
"process.stdin is the one supported input stream",
);
}
sawInput = true;
} else if (member === "output") {
if (streamOf(p.initializer) !== "stdout") {
lowerer.noLowering(
"createInterface with a non-stdout output",
p.initializer,
"process.stdout is the one supported output stream",
);
}
} else if (member === "terminal") {
const t = lowerer.typeOf(p.initializer);
if (!(t.flags & ts.TypeFlags.BooleanLiteral) || lowerer.checker.typeToString(t) !== "false") {
lowerer.noLowering(
"createInterface with terminal enabled",
p.initializer,
"terminal line editing has no lowering — the pipe behavior is what compiles",
);
}
} else if (member === "crlfDelay") {
// Infinity states the lowered behavior: the splitter holds a
// trailing \r until the next chunk decides \r\n vs \r, with no
// time limit (scr_readline.c) — Node's crlfDelay: Infinity.
// Finite delays would need a timer the splitter does not have.
if (!ts.isIdentifier(p.initializer) || p.initializer.text !== "Infinity") {
lowerer.noLowering(
"createInterface with a finite crlfDelay",
p.initializer,
"the lowered splitter always joins \\r\\n across chunks (Node's crlfDelay: Infinity) — Infinity is the accepted value",
);
}
} else if (member === "completer") {
input = lowerInput(lowerer, optsNode);
if (expr.arguments[1] !== undefined) output = lowerOutput(lowerer, expr.arguments[1]);
} else {
if (expr.arguments.length > 1) {
lowerer.noLowering(
"createInterface with a completer",
p,
"tab completion needs an interactive terminal — the lowered interface reads piped lines (terminal: false)",
"createInterface with more than one argument",
expr.arguments[1]!,
"pass every option in the one options object",
);
} else {
fenceOrDropOptionKey(
lowerer,
p,
member,
"createInterface",
READLINE_DOCUMENTED_OPTIONS,
"input, output, terminal: false, and crlfDelay: Infinity are the supported options",
}
let sawInput = false;
for (const p of optsNode.properties) {
if (!ts.isPropertyAssignment(p) || !ts.isIdentifier(p.name)) {
lowerer.noLowering(
"createInterface with this options shape",
p,
"spreads, computed keys, and shorthand options have no lowering — write each member inline",
);
}
const member = p.name.text;
if (member === "input") {
input = lowerInput(lowerer, p.initializer);
sawInput = true;
} else if (member === "output") {
output = lowerOutput(lowerer, p.initializer);
} else if (member === "terminal") {
const t = lowerer.typeOf(p.initializer);
if (
!(t.flags & ts.TypeFlags.BooleanLiteral) ||
lowerer.checker.typeToString(t) !== "false"
) {
lowerer.noLowering(
"createInterface with terminal enabled",
p.initializer,
"terminal line editing has no lowering — the pipe behavior is what compiles",
);
}
} else if (member === "crlfDelay") {
crlf = lowerer.lowerExprExpecting(p.initializer, F64);
} else if (member === "prompt") {
prompt = lowerer.lowerExprExpecting(p.initializer, STRING);
} else if (member === "signal") {
signal = lowerer.lowerExprExpecting(p.initializer, DYN);
} else if (member === "completer") {
lowerer.noLowering(
"createInterface with a completer",
p,
"tab completion needs an interactive terminal — the lowered interface reads piped lines (terminal: false)",
);
} else {
fenceOrDropOptionKey(
lowerer,
p,
member,
"createInterface",
READLINE_DOCUMENTED_OPTIONS,
"input, output, terminal: false, crlfDelay, prompt and signal are the supported options",
);
// An undocumented key, dropped like Node drops it.
}
}
if (!sawInput) {
lowerer.noLowering(
"createInterface without an input stream",
optsNode,
"pass { input: process.stdin, output: process.stdout }",
);
// An undocumented key, dropped like Node drops it.
}
}
if (!sawInput) {
lowerer.noLowering(
"createInterface without an input stream",
optsNode,
"pass { input: process.stdin, output: process.stdout }",
);
}
return { kind: "libCall", fn: "rl.create", args: [], type: F64, loc };
const mode =
(input !== null ? 1 : 0) |
(typeof output === "object" ? 2 : 0) |
(output === "stdout" ? 4 : 0) |
(signal !== null ? 8 : 0) |
(prompt !== null ? 16 : 0);
const args: IrExpr[] = [numLit(mode, loc)];
if (input !== null) args.push(input);
if (typeof output === "object") args.push(output);
args.push(crlf);
if (prompt !== null) args.push(prompt);
if (signal !== null) args.push(signal);
return {
kind: "libCall",
fn: signal !== null ? "rl.createSignal" : "rl.create",
args,
type: F64,
loc,
};
}
@@ -7,13 +7,23 @@ import { dynUndefinedExpr } from "../../ir/build.js";
import * as ts from "../ts7/adapter.js";
import { InternalCompilerError } from "../../errors.js";
import type { Lowerer } from "./lowerer.js";
import { BOOL, DYN, type IrExpr, type IrStmt, type IrType, VOID } from "../../ir/ir.js";
import {
BOOL,
DYN,
F64,
type IrExpr,
type IrStmt,
type IrType,
STRING,
VOID,
} from "../../ir/ir.js";
import { locOf } from "../program.js";
import { extractIteratorValue } from "./lower-generators.js";
import { streamSidesOf } from "./lower-stream.js";
import { staticReadableStreamElementType } from "./lower-stdlib.js";
import { genResultRecord } from "../type-mapper.js";
import { sideEffectFreeOptionValue } from "./surfaces.js";
import { isReadlineTyped } from "./builtins/readline.js";
interface NodeReadablePlan {
source: ts.Expression;
@@ -828,7 +838,143 @@ function lowerClassAsyncIterator(
/** Claims the built-in static async-iterator families. Typed async
* generators and process.stdin retain their specialized drivers; this is
* the common stream protocol path. */
/** The readline Interface a for-await iterates: the interface itself or
* an explicit rl[Symbol.asyncIterator]() call. */
function readlinePlan(lowerer: Lowerer, expression: ts.Expression): ts.Expression | null {
if (isReadlineTyped(lowerer, expression)) return expression;
const call = symbolAsyncIteratorCall(lowerer, expression);
if (call === null) return null;
const source = (call.expression as ts.PropertyAccessExpression | ts.ElementAccessExpression)
.expression;
return isReadlineTyped(lowerer, source) ? source : null;
}
/** for await (const line of rl): the interface's one async iterator
* (events.on over 'line' — scr_readline.c). Each line binds as a string;
* an abrupt completion while the body owns a line calls return(), which
* stops the iterator listening while queued lines stay readable. */
function lowerReadlineLines(
lowerer: Lowerer,
stmt: ts.ForOfStatement,
source: ts.Expression,
): IrStmt {
const binding = checkForAwaitBinding(lowerer, stmt);
const loc = locOf(stmt);
lowerer.scopes.push(new Map());
try {
const rl = lowerer.declareHiddenLocal("%faReadline", F64);
const active = lowerer.declareHiddenLocal("%faActive", BOOL);
active.mutable = true;
const promiseT: IrType = { kind: "promise", inner: DYN };
const promise = lowerer.declareHiddenLocal("%rlNext", promiseT);
const value = lowerer.declareHiddenLocal("%rlValue", DYN);
const line = lowerer.declareLocal(
binding.declaration.name,
binding.declaration.name.text,
STRING,
binding.mutable,
);
const rlRef = (): IrExpr => ({ kind: "varRef", localId: rl.id, type: F64, loc });
const valueRef = (): IrExpr => ({ kind: "varRef", localId: value.id, type: DYN, loc });
const body = lowerer.inCtl("loop", () => lowerer.lowerScopedBlock(stmt.statement));
const loop: IrStmt = {
kind: "while",
cond: { kind: "boolLit", value: true, type: BOOL, loc },
body: [
{
kind: "assign",
localId: active.id,
value: { kind: "boolLit", value: false, type: BOOL, loc },
loc,
},
{
kind: "varDecl",
localId: promise.id,
init: { kind: "libCall", fn: "rl.iterNext", args: [rlRef()], type: promiseT, loc },
loc,
},
{
kind: "varDecl",
localId: value.id,
init: {
kind: "awaitExpr",
value: { kind: "varRef", localId: promise.id, type: promiseT, loc },
type: DYN,
loc,
},
loc,
},
{
kind: "if",
cond: { kind: "dynTest", test: "undefined", value: valueRef(), type: BOOL, loc },
then: [{ kind: "break", loc }],
else_: null,
loc,
},
{
kind: "varDecl",
localId: line.id,
init: { kind: "dynCheck", value: valueRef(), type: STRING, loc },
loc,
},
{
kind: "assign",
localId: active.id,
value: { kind: "boolLit", value: true, type: BOOL, loc },
loc,
},
...body,
],
loc,
};
return {
kind: "block",
body: [
{ kind: "varDecl", localId: rl.id, init: lowerer.lowerExprExpecting(source, F64), loc },
{
kind: "exprStmt",
expr: { kind: "libCall", fn: "rl.iterOpen", args: [rlRef()], type: VOID, loc },
loc,
},
{
kind: "varDecl",
localId: active.id,
init: { kind: "boolLit", value: false, type: BOOL, loc },
loc,
},
{
kind: "tryCatch",
tryBody: [loop],
catchBody: null,
catchLocalId: null,
finallyBody: [
{
kind: "if",
cond: boolRef(active.id, loc),
then: [
{
kind: "exprStmt",
expr: { kind: "libCall", fn: "rl.iterReturn", args: [rlRef()], type: VOID, loc },
loc,
},
],
else_: null,
loc,
},
],
loc,
},
],
loc,
};
} finally {
lowerer.scopes.pop();
}
}
export function lowerForAwaitBuiltin(lowerer: Lowerer, stmt: ts.ForOfStatement): IrStmt | null {
const readline = readlinePlan(lowerer, stmt.expression);
if (readline) return lowerReadlineLines(lowerer, stmt, readline);
const node = nodeReadablePlan(lowerer, stmt.expression);
if (node) return lowerNodeReadable(lowerer, stmt, node);
const web = webReadablePlan(lowerer, stmt.expression);
@@ -37,6 +37,7 @@ import { newFnCtx, own } from "./lowerer.js";
import { appendImplicitUndefinedReturn } from "./function-returns.js";
import { bufEncoding, knownBufEncoding } from "./containers/bytes.js";
import { tryLowerExpression } from "./expressions/try-lower-expression.js";
import { sideEffectFreeOptionValue } from "./surfaces.js";
import {
BOOL,
DYN,
@@ -60,6 +61,29 @@ import {
const BYTES = BYTES_U8;
/** An expression that only reads bindings and properties (no calls,
* assignments or construction): reading it a step later observes the
* same values. */
function pureRead(node: ts.Expression): boolean {
if (
ts.isParenthesizedExpression(node) ||
ts.isNonNullExpression(node) ||
ts.isAsExpression(node)
) {
return pureRead(node.expression);
}
if (ts.isIdentifier(node) || node.kind === ts.SyntaxKind.ThisKeyword) return true;
if (ts.isPropertyAccessExpression(node)) return pureRead(node.expression);
if (ts.isElementAccessExpression(node)) {
return (
(ts.isStringLiteral(node.argumentExpression) ||
ts.isNumericLiteral(node.argumentExpression)) &&
pureRead(node.expression)
);
}
return false;
}
/** The stream sides of a receiver class: the nearest stream-class
* ancestor's, or null off the stream hierarchy. */
export function streamSidesOf(
@@ -334,6 +358,10 @@ interface StreamOptions {
* as a follow-up readable.pushEncoding — push(string) decodes through
* it (Node's Buffer.from(chunk, state.defaultEncoding)). */
pushEncoding: string | null;
/** The `signal` option's value: applied with stream.addAbortSignal
* right after construction (Node's constructors call addAbortSignal
* once the state exists), or null when absent. */
signal: IrExpr | null;
/** Present callbacks in canonical order with their flag bits. */
flags: number;
cbs: IrExpr[];
@@ -559,6 +587,7 @@ function parseStreamOptions(
encoding: null,
badEncoding: null,
pushEncoding: null,
signal: null,
flags: 0,
cbs: [],
};
@@ -635,6 +664,11 @@ function parseStreamOptions(
present.set(name, lowerStreamCallback(lowerer, name, fnNode, thisType, tuple));
continue;
}
// `{ signal }` shorthand: the binding is the value.
if (name === "signal" && ts.isShorthandPropertyAssignment(prop)) {
if (ts.isIdentifier(prop.name)) out.signal = lowerer.lowerExprExpecting(prop.name, DYN);
continue;
}
// Scalar options.
if (!ts.isPropertyAssignment(prop)) {
lowerer.unsupported(
@@ -729,13 +763,22 @@ function parseStreamOptions(
"utf8 (the default) is the supported write-side string encoding",
);
break;
case "signal":
lowerer.noLowering(
`the ${ctorName} option 'signal'`,
prop,
"AbortSignal-driven destruction is not lowered yet",
);
case "signal": {
// `if (options.signal) addAbortSignal(options.signal, this)`:
// applied over the fresh stream, so the value is read after the
// constructor — only values whose evaluation has no effects.
if (value.kind === ts.SyntaxKind.NullKeyword) break;
if (ts.isIdentifier(value) && value.text === "undefined") break;
if (!sideEffectFreeOptionValue(value) && !pureRead(value)) {
lowerer.noLowering(
`the ${ctorName} option 'signal' with this value`,
prop,
"pass a variable or property read (const signal = controller.signal) — the signal is read after construction",
);
}
out.signal = lowerer.lowerExprExpecting(value, DYN);
break;
}
case "captureRejections": {
// `false` is the default — admit the literal; the rejection-
// capturing mode has no lowering (async listeners' promises are
@@ -824,6 +867,7 @@ function streamCtorArgs(
encoding: string | null;
badEncoding: string | null;
pushEncoding: string | null;
signal: IrExpr | null;
} {
const shape = streamCtorShape(cls);
const o = parseStreamOptions(
@@ -852,6 +896,7 @@ function streamCtorArgs(
encoding: o.encoding,
badEncoding: o.badEncoding,
pushEncoding: o.pushEncoding,
signal: o.signal,
};
}
@@ -907,13 +952,23 @@ export function lowerStreamNew(lowerer: Lowerer, expr: ts.NewExpression, info: C
loc,
};
}
if (parsed.encoding === null) return built;
// { encoding }: chain a setEncoding over the fresh stream (the operand
// temp releases with the frame; setEncoding answers the receiver +1).
if (parsed.encoding !== null) {
// { encoding }: chain a setEncoding over the fresh stream (the operand
// temp releases with the frame; setEncoding answers the receiver +1).
built = {
kind: "libCall",
fn: "readable.setEncoding",
args: [built, strLit(parsed.encoding, loc)],
type: thisType,
loc,
};
}
if (parsed.signal === null) return built;
// { signal }: addAbortSignal over the fresh stream (answers it +1).
return {
kind: "libCall",
fn: "readable.setEncoding",
args: [built, strLit(parsed.encoding, loc)],
fn: "stream.addAbortSignal",
args: [built, parsed.signal],
type: thisType,
loc,
};
@@ -1178,6 +1233,19 @@ export function lowerStreamSuperCall(
loc,
});
}
if (parsed.signal !== null) {
out.push({
kind: "exprStmt",
expr: {
kind: "libCall",
fn: "stream.addAbortSignal",
args: [thisRef(), parsed.signal],
type: thisType,
loc,
},
loc,
});
}
return out;
}
@@ -1423,7 +1491,7 @@ const PIPELINE_STAGE_EXPECTED =
function lowerStreamArg(
lowerer: Lowerer,
node: ts.Expression,
what: "finished" | "pipeline",
what: "finished" | "pipeline" | "addAbortSignal",
): IrExpr {
const v = lowerer.lowerExpr(node);
const info = v.type.kind === "object" ? lowerer.classes.get(v.type.className) : undefined;
@@ -1563,6 +1631,113 @@ function lowerEosCallback(
);
}
/** finished()'s options literal: readable/writable/error (and cleanup
* for the promise form) as boolean literals, signal as any expression.
* readable/writable answer -1 when absent (Node's default: whether the
* stream has that side). */
function eosOptions(
lowerer: Lowerer,
node: ts.Expression,
promiseForm: boolean,
): { readable: number; writable: number; error: boolean; cleanup: boolean; signal: IrExpr | null } {
const out = {
readable: -1,
writable: -1,
error: true,
cleanup: false,
signal: null as IrExpr | null,
};
if (!ts.isObjectLiteralExpression(node)) {
lowerer.noLowering(
"finished options that are not an object literal",
node,
"write the options inline: finished(stream, { readable, writable, error, signal })",
);
}
const literal = (p: ts.PropertyAssignment, key: string): boolean => {
if (p.initializer.kind === ts.SyntaxKind.TrueKeyword) return true;
if (p.initializer.kind === ts.SyntaxKind.FalseKeyword) return false;
lowerer.noLowering(
`the finished option '${key}' with a non-literal value`,
p.initializer,
"pass true or false",
);
};
for (const p of node.properties) {
const key =
(ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p)) && ts.isIdentifier(p.name)
? p.name.text
: null;
if (key === "signal") {
out.signal = ts.isShorthandPropertyAssignment(p)
? lowerer.lowerExprExpecting(p.name as ts.Identifier, DYN)
: lowerer.lowerExprExpecting((p as ts.PropertyAssignment).initializer, DYN);
continue;
}
if (key !== null && ts.isPropertyAssignment(p)) {
if (key === "readable") out.readable = literal(p, key) ? 1 : 0;
else if (key === "writable") out.writable = literal(p, key) ? 1 : 0;
else if (key === "error") out.error = literal(p, key);
else if (key === "cleanup" && promiseForm) out.cleanup = literal(p, key);
else {
lowerer.noLowering(
`the finished option '${key}'`,
p,
promiseForm
? "readable, writable, error, cleanup and signal are the supported options"
: "readable, writable, error and signal are the supported options",
);
}
continue;
}
lowerer.noLowering(
"finished options with this member",
p,
"write each option as a plain property",
);
}
return out;
}
/** pipeline()'s trailing options literal (the promise form): signal as
* any expression, end as a boolean literal. */
function pipelineOptions(
lowerer: Lowerer,
node: ts.ObjectLiteralExpression,
): { end: boolean; signal: IrExpr | null } {
const out = { end: true, signal: null as IrExpr | null };
for (const p of node.properties) {
const key =
(ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p)) && ts.isIdentifier(p.name)
? p.name.text
: null;
if (key === "signal") {
out.signal = ts.isShorthandPropertyAssignment(p)
? lowerer.lowerExprExpecting(p.name as ts.Identifier, DYN)
: lowerer.lowerExprExpecting((p as ts.PropertyAssignment).initializer, DYN);
continue;
}
if (key === "end" && ts.isPropertyAssignment(p)) {
if (p.initializer.kind === ts.SyntaxKind.TrueKeyword) continue;
if (p.initializer.kind === ts.SyntaxKind.FalseKeyword) {
out.end = false;
continue;
}
lowerer.noLowering(
"the pipeline option 'end' with a non-literal value",
p.initializer,
"pass true or false",
);
}
lowerer.noLowering(
key === null ? "pipeline options with this member" : `the pipeline option '${key}'`,
p,
"signal and end are the supported pipeline options",
);
}
return out;
}
/** `finished(...)` / `pipeline(...)` / `getDefaultHighWaterMark(...)`
* imported from node:stream (named imports, destructured requires, and
* namespace-member calls all land here). Null for members this spoke
@@ -1580,13 +1755,11 @@ export function lowerStreamModuleCall(
// ERR_STREAM_PREMATURE_CLOSE — the callback form's status, boxed
// through the promise instead). The options argument has no lowering.
if (bi.member === "finished") {
if (args.length !== 1) {
if (args.length !== 1 && args.length !== 2) {
lowerer.noLowering(
`stream/promises.finished with ${args.length} arguments`,
call,
args.length === 2
? "the options argument has no lowering yet — the one-argument form is supported"
: "the supported form is finished(stream)",
"the supported forms are finished(stream) and finished(stream, options)",
);
}
let recv: IrExpr;
@@ -1596,6 +1769,24 @@ export function lowerStreamModuleCall(
if (e instanceof StreamArgTypeThrow) return e.expr;
throw e;
}
const optsNode = args[1];
if (optsNode !== undefined && optsNode.kind !== ts.SyntaxKind.NullKeyword) {
const o = eosOptions(lowerer, optsNode, true);
const head: IrExpr[] = [
recv,
numLit(o.readable, loc),
numLit(o.writable, loc),
boolLit(o.error, loc),
boolLit(o.cleanup, loc),
];
return {
kind: "libCall",
fn: o.signal ? "sp.finishedOptsSignal" : "sp.finishedOpts",
args: o.signal ? [...head, o.signal] : head,
type: { kind: "promise", inner: VOID },
loc,
};
}
return {
kind: "libCall",
fn: "sp.finished",
@@ -1616,13 +1807,29 @@ export function lowerStreamModuleCall(
"the supported form is pipeline(source, ...streams)",
);
}
const lastArg = args[args.length - 1]!;
const opts =
ts.isObjectLiteralExpression(lastArg) && args.length >= 3
? pipelineOptions(lowerer, lastArg)
: null;
const stageArgs = opts ? args.slice(0, -1) : args;
let streams: IrExpr[];
try {
streams = args.map((a) => lowerStreamArg(lowerer, a, "pipeline"));
streams = stageArgs.map((a) => lowerStreamArg(lowerer, a, "pipeline"));
} catch (e) {
if (e instanceof StreamArgTypeThrow) return e.expr;
throw e;
}
if (opts) {
const head = [numLit(streams.length, loc), ...streams, boolLit(opts.end, loc)];
return {
kind: "libCall",
fn: opts.signal ? "sp.pipelineOptsSignal" : "sp.pipelineOpts",
args: opts.signal ? [...head, opts.signal] : head,
type: { kind: "promise", inner: VOID },
loc,
};
}
return {
kind: "libCall",
fn: "sp.pipeline",
@@ -1703,14 +1910,39 @@ export function lowerStreamModuleCall(
);
}
if (bi.member === "finished") {
if (bi.member === "addAbortSignal") {
// addAbortSignal(signal, stream) → the stream: destroyed with an
// AbortError when the signal aborts (now, if it already has).
if (args.length !== 2) {
lowerer.noLowering(
`addAbortSignal with ${args.length} arguments`,
call,
"the supported form is addAbortSignal(signal, stream)",
);
}
const signal = lowerer.lowerExprExpecting(args[0]!, DYN);
let stream: IrExpr;
try {
stream = lowerStreamArg(lowerer, args[1]!, "addAbortSignal");
} catch (e) {
if (e instanceof StreamArgTypeThrow) return e.expr;
throw e;
}
return {
kind: "libCall",
fn: "stream.addAbortSignalFn",
args: [signal, stream],
type: stream.type,
loc,
};
}
if (bi.member === "finished") {
if (args.length !== 2 && args.length !== 3) {
lowerer.noLowering(
`finished with ${args.length} arguments`,
call,
args.length === 3
? "the options argument has no lowering yet — the two-argument form is supported"
: "the supported form is finished(stream, callback)",
"the supported forms are finished(stream, callback) and finished(stream, options, callback)",
);
}
let recv: IrExpr;
@@ -1720,7 +1952,24 @@ export function lowerStreamModuleCall(
if (e instanceof StreamArgTypeThrow) return e.expr;
throw e;
}
const { cb, dyn } = lowerEosCallback(lowerer, "finished", args[1]!, recv.type);
if (args.length === 3 && args[1]!.kind !== ts.SyntaxKind.NullKeyword) {
const o = eosOptions(lowerer, args[1]!, false);
const { cb } = lowerEosCallback(lowerer, "finished", args[2]!, recv.type);
const head: IrExpr[] = [
recv,
numLit(o.readable, loc),
numLit(o.writable, loc),
boolLit(o.error, loc),
];
return {
kind: "libCall",
fn: o.signal ? "stream.finishedOptsSignal" : "stream.finishedOpts",
args: o.signal ? [...head, o.signal, cb] : [...head, cb],
type: funcOf([], VOID),
loc,
};
}
const { cb, dyn } = lowerEosCallback(lowerer, "finished", args[args.length - 1]!, recv.type);
return {
kind: "libCall",
fn: dyn ? "stream.finishedDyn" : "stream.finished",
@@ -1617,6 +1617,11 @@ export const BUILTIN_MODULE_FNS: Record<
readline: {
createInterface: { fn: "rl.create", params: [], result: F64 },
},
// node:readline/promises: the same Interface with the promise question
// (lowerReadlineMethodCall tells the forms apart by the call's result).
"readline/promises": {
createInterface: { fn: "rl.create", params: [], result: F64 },
},
// node:net and node:http lower ENTIRELY through the server spoke
// (lower-server.ts — every call shape is special-cased: closures, the
// optional connect host, writeHead's literal headers); the member list
@@ -1928,11 +1928,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null {
) {
return { kind: "object", className: RUNTIME_EMITTER_CLASS };
}
// readline.Interface: the interface value is an f64 handle into the
// runtime's registry (the Timeout-id precedent). The NAME is generic,
// so the provenance check adds the enclosing ambient module, like
// net.Server (@types/node also declares Interface in
// readline/promises — that one stays unmapped and fences).
// readline.Interface and readline/promises.Interface: the interface
// value is an f64 handle into the runtime's registry (the Timeout-id
// precedent). The NAME is generic, so the provenance check adds the
// enclosing ambient module, like net.Server.
if (
psym?.name === "Interface" &&
checker
@@ -1941,7 +1940,8 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null {
(d) =>
(ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) &&
ctx.isStdlibFile(d.getSourceFile()) &&
isDeclaredInAmbientModule(d, "readline"),
(isDeclaredInAmbientModule(d, "readline") ||
isDeclaredInAmbientModule(d, "readline/promises")),
)
) {
return F64;
@@ -374,6 +374,15 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
"https.requestFn",
"https.requestFnCb",
"rl.question",
"rl.createSignal",
"stream.finishedOpts",
"stream.finishedOptsSignal",
"stream.addAbortSignal",
"stream.addAbortSignalFn",
"rl.pause",
"rl.resume",
"rl.prompt",
"rl.write",
"json.parse",
"json.parseReviver",
+65 -19
View File
@@ -3018,6 +3018,14 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
* propagates the first error by destroying the rest, calls cb after the
* last 'close'; answers the destination +1. The Dyn twins take the
* callback as a checked-dynamic VALUE (mustCall wrappers). */
/** stream.addAbortSignal: [stream, signal] — the constructors' signal
* option, chained over the fresh stream (falsy signals are ignored);
* stream.addAbortSignalFn: [signal, stream] — addAbortSignal(signal,
* stream). Both answer the stream (+1) and throw ERR_INVALID_ARG_TYPE
* for a value that is not an AbortSignal; they link the event-target
* unit (addAbortListener). */
"stream.addAbortSignal": { argTypes: [null, DYN], result: VOID },
"stream.addAbortSignalFn": { argTypes: [DYN, null], result: VOID },
"stream.finished": { argTypes: [null, null], result: VOID },
"stream.finishedDyn": { argTypes: [null, DYN], result: VOID },
"stream.pipeline": { argTypes: [F64], result: VOID },
@@ -3028,6 +3036,25 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
* chaining/destroyer semantics settling a void promise (fulfilled on a
* clean finish, rejected with the finish status otherwise). */
"sp.finished": { argTypes: [null], result: { kind: "promise", inner: VOID } },
/** The options forms: finished(s, { readable, writable, error, cleanup,
* signal }) as [s, readable, writable, error, cleanup, signal?] —
* readable/writable -1 (absent), 0 or 1 — and pipeline(...streams,
* { signal, end }) as [count, s1..sn, end, signal?]. The Signal forms
* link the event-target unit (addAbortListener). The callback form
* stream.finishedOpts: [s, readable, writable, error, signal?, cb] →
* the cleanup closure. */
"sp.finishedOpts": {
argTypes: [null, F64, F64, BOOL, BOOL],
result: { kind: "promise", inner: VOID },
},
"sp.finishedOptsSignal": {
argTypes: [null, F64, F64, BOOL, BOOL, DYN],
result: { kind: "promise", inner: VOID },
},
"sp.pipelineOpts": { argTypes: [F64], result: { kind: "promise", inner: VOID } },
"sp.pipelineOptsSignal": { argTypes: [F64], result: { kind: "promise", inner: VOID } },
"stream.finishedOpts": { argTypes: [null, F64, F64, BOOL, null], result: VOID },
"stream.finishedOptsSignal": { argTypes: [null, F64, F64, BOOL, DYN, null], result: VOID },
"sp.pipeline": { argTypes: [F64], result: { kind: "promise", inner: VOID } },
/** node:stream/consumers — the promise consumers over the readable
* machinery: (s) → a pending promise settled at the terminal point
@@ -3293,27 +3320,46 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
"strdec.next": { argTypes: [STRING, F64, BYTES_U8], result: F64 },
"strdec.end": { argTypes: [STRING, F64], result: STRING },
// node:readline: the interface handle is f64; the callbacks' func types
// are program-dependent (zero-param or (answer: string) — the emitter
// picks the adapter), so those slots are null like child.onExit's.
/** node:readline's question/close slice (scr_readline.c, linked under
* the events gate — these fns imply moduleUsesProcessEvents). The
* interface value is an f64 handle (the Timeout-id precedent).
* rl.create: [] → f64 — createInterface({ input: process.stdin,
* output: process.stdout }), registering the unit's shared stdin
* consumer (an OPEN interface keeps the loop alive until close/EOF,
* Node's semantics). rl.question: [handle, query, cb] — writes the
* query to stdout (Node writes under pipes too) and delivers the next
* line's text through a backend-picked adapter (zero-param or
* (answer: string)); THROWS Node's "readline was closed" on a closed
* interface (may-throw). rl.close: [handle] — fires 'close' listeners
* SYNCHRONOUSLY (Node's inline emit) and detaches the consumer (the
* loop stops waiting on fd 0). rl.onClose: [handle, cb] — a zero-arg
* listener (moves); stdin EOF closes every open interface with the
* buffered partial line DISCARDED, like Node. */
"rl.create": { argTypes: [], result: F64 },
// are program-dependent (the emitter picks the adapter by parameter
// count), so those slots are null like child.onExit's.
/** node:readline and node:readline/promises (scr_readline.c, linked
* under the events gate — these fns imply moduleUsesProcessEvents; a
* stream input or output adds scr_stream.c's bridge). The interface
* value is an f64 handle (the Timeout-id precedent); an open interface
* on stdin keeps the loop alive until close/EOF.
* rl.create: [mode, input?, output?, crlfDelay, prompt?] → f64, mode
* bits 1 stream input, 2 stream output, 4 stdout output, 16 prompt
* (lowerReadlineCreateCall); rl.createSignal: the same plus the
* trailing AbortSignal (mode bit 8), which links the event-target unit
* for addAbortListener. rl.question: [handle, query, cb] — the callback
* form; THROWS ERR_USE_AFTER_CLOSE on a closed interface (may-throw).
* rl.questionAsync(Signal): [handle, query, signal?] → promise<string>,
* rejecting instead of throwing. rl.close/pause/resume/prompt:
* [handle] (pause/resume/prompt throw ERR_USE_AFTER_CLOSE when
* closed); rl.setPrompt/getPrompt; rl.write: [handle, data] (throws
* when closed). rl.on: [handle, event, cb, once] — the event number
* (0 'line', 1 'close', 2 'pause', 3 'resume', 4 'error') and the
* listener (moves). rl.iterOpen/iterNext/iterReturn: the async
* iterator — iterNext answers promise<dyn> of the line or undefined. */
"rl.create": { argTypes: [F64], result: F64 },
"rl.createSignal": { argTypes: [F64], result: F64 },
"rl.question": { argTypes: [F64, STRING, null], result: VOID },
"rl.questionAsync": { argTypes: [F64, STRING], result: { kind: "promise", inner: STRING } },
"rl.questionAsyncSignal": {
argTypes: [F64, STRING, DYN],
result: { kind: "promise", inner: STRING },
},
"rl.close": { argTypes: [F64], result: VOID },
"rl.onClose": { argTypes: [F64, null], result: VOID },
"rl.pause": { argTypes: [F64], result: VOID },
"rl.resume": { argTypes: [F64], result: VOID },
"rl.prompt": { argTypes: [F64], result: VOID },
"rl.setPrompt": { argTypes: [F64, STRING], result: VOID },
"rl.getPrompt": { argTypes: [F64], result: STRING },
"rl.write": { argTypes: [F64, STRING], result: VOID },
"rl.on": { argTypes: [F64, F64, null, BOOL], result: VOID },
"rl.iterOpen": { argTypes: [F64], result: VOID },
"rl.iterNext": { argTypes: [F64], result: { kind: "promise", inner: DYN } },
"rl.iterReturn": { argTypes: [F64], result: VOID },
/** node:timers/promises — the promisified pair (scr_async.c, beside
* the timer heap they ride): tp.setTimeout: [ms] → a pending void
* promise a one-shot heap timer fulfills (the loop's timer phase, FIFO
+31 -2
View File
@@ -164,6 +164,18 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run
features.legacyTextDecoder = true;
if (fn.startsWith("fetch.")) features.fetch = true;
if (fn.startsWith("event.") || fn === "events.addAbortListener") features.eventTarget = true;
// Signal-taking readline entry points call addAbortListener.
if (
fn === "rl.createSignal" ||
fn === "rl.questionAsyncSignal" ||
fn === "stream.addAbortSignal" ||
fn === "stream.addAbortSignalFn" ||
fn === "sp.finishedOptsSignal" ||
fn === "sp.pipelineOptsSignal" ||
fn === "stream.finishedOptsSignal"
) {
features.eventTarget = true;
}
if (PROCESS_EVENT_LIB_FNS.has(fn)) features.processEvents = true;
if (fn.startsWith("emitter.")) features.emitter = true;
if (
@@ -180,7 +192,12 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run
fn === "stream.errored" ||
fn === "sp.finished" ||
fn === "sp.pipeline" ||
fn === "console.bind"
fn === "console.bind" ||
fn === "stream.addAbortSignal" ||
fn === "stream.addAbortSignalFn" ||
fn.startsWith("sp.finishedOpts") ||
fn.startsWith("sp.pipelineOpts") ||
fn.startsWith("stream.finishedOpts")
)
features.stream = true;
if (fn.startsWith("zlib.")) features.zlib = true;
@@ -426,9 +443,21 @@ const PROCESS_EVENT_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
// node:readline rides the stdin unit (scr_readline.c links beside
// scr_events.c under the same gate).
"rl.create",
"rl.createSignal",
"rl.question",
"rl.questionAsync",
"rl.questionAsyncSignal",
"rl.close",
"rl.onClose",
"rl.pause",
"rl.resume",
"rl.prompt",
"rl.setPrompt",
"rl.getPrompt",
"rl.write",
"rl.on",
"rl.iterOpen",
"rl.iterNext",
"rl.iterReturn",
]);
/** True when the module uses the process-events surface — the link switch
+33 -2
View File
@@ -3960,11 +3960,15 @@ function validateFunction(
e.fn === "stream.pipeline" ||
e.fn === "stream.pipelineDyn" ||
e.fn === "sp.pipeline" ||
e.fn === "sp.pipelineOpts" ||
e.fn === "sp.pipelineOptsSignal" ||
e.fn === "writable.write" ||
e.fn === "writable.writeStr" ||
e.fn === "writable.writeU" ||
e.fn === "writable.end" ||
e.fn === "readable.unpipe";
e.fn === "readable.unpipe" ||
e.fn === "rl.create" ||
e.fn === "rl.createSignal";
if (variadic ? e.args.length < sig.argTypes.length : e.args.length !== sig.argTypes.length) {
err(`libCall ${e.fn}: ${e.args.length} args, expected ${sig.argTypes.length}`, e.loc);
}
@@ -5163,7 +5167,11 @@ function validateFunction(
e.fn === "stream.finished" ||
e.fn === "stream.finishedDyn" ||
e.fn === "stream.pipeline" ||
e.fn === "stream.pipelineDyn"
e.fn === "stream.pipelineDyn" ||
e.fn === "stream.addAbortSignal" ||
e.fn === "stream.addAbortSignalFn" ||
e.fn === "stream.finishedOpts" ||
e.fn === "stream.finishedOptsSignal"
) {
// Receiver/result: stream-hierarchy objects (a class whose base
// chain reaches a runtime stream class). Constructors have no
@@ -5179,6 +5187,15 @@ function validateFunction(
}
return false;
};
if (e.fn === "stream.addAbortSignal" || e.fn === "stream.addAbortSignalFn") {
// [stream, signal] (the option) or [signal, stream] (the module
// function): the result is the stream argument's type.
const s = e.args[e.fn === "stream.addAbortSignal" ? 0 : 1];
if (!isStreamObject(s?.type) || !typeEquals(e.type, s!.type)) {
err(`libCall ${e.fn} must take and return a stream-hierarchy object`, e.loc);
}
return true;
}
if (e.fn.endsWith(".new")) {
if (!isStreamObject(e.type)) {
err(`libCall ${e.fn} must return a stream class, got ${e.type.kind}`, e.loc);
@@ -5210,6 +5227,20 @@ function validateFunction(
if (e.type.kind !== "void") err(`libCall ${e.fn} must be void`, e.loc);
return true;
}
if (e.fn === "stream.finishedOpts" || e.fn === "stream.finishedOptsSignal") {
// (recv, readable, writable, error[, signal], cb) → the cleanup
// closure; the callback is a func or a checked-dynamic value.
if (!isStreamObject(e.args[0]?.type)) {
err(`libCall ${e.fn} receiver must be a stream-hierarchy object`, e.loc);
}
const cbK = e.args[e.args.length - 1]?.type.kind;
if (cbK !== "func" && cbK !== "dyn")
err(`libCall ${e.fn} callback must be a func or dyn`, e.loc);
if (e.type.kind !== "func") {
err(`libCall ${e.fn} must return the cleanup closure, got ${e.type.kind}`, e.loc);
}
return true;
}
if (e.fn === "stream.finished" || e.fn === "stream.finishedDyn") {
// (recv, cb) → the cleanup closure.
if (!isStreamObject(e.args[0]?.type)) {
+13
View File
@@ -2049,6 +2049,19 @@
"name": "readline.createInterface",
"status": "static"
},
{
"id": "node-builtin.readline.promises",
"kind": "node-builtin",
"name": "readline/promises",
"status": "static",
"note": "recognized module (bare and node:-prefixed specifiers)"
},
{
"id": "node-builtin.readline.promises.createInterface",
"kind": "node-builtin",
"name": "readline/promises.createInterface",
"status": "static"
},
{
"id": "node-builtin.stream",
"kind": "node-builtin",
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/5295-readline-promises-question.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/5296-readline-lines.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/5297-readline-callback-events.cjs"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/5298-readline-abort.cjs"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/5299-readline-stdin.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/5300-stream-add-abort-signal.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/5301-stream-add-abort-signal-errors.cjs"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/5302-stream-promises-options.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/5303-stream-finished-callback-opts.cjs"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/fixtures/node-types/readline-promises.ts"
],
"diags": []
}
File diff suppressed because it is too large Load Diff
+77 -12
View File
@@ -2992,6 +2992,10 @@ extern SCR_TL void (*scr_emitter_on_hook)(ScrEmitter *em, ScrStr *name);
* are called by compiler-emitted completion-callback closures. */
typedef struct ScrStreamState ScrStreamState;
typedef struct ScrBytes ScrBytes; /* full definition below (C11 repeat) */
/* events.addAbortListener (scr_events_add_abort_listener): passed to the
* signal-taking stream and readline entry points by programs that link
* the event-target unit. */
typedef ScrDyn *(*ScrAbortAddFn)(ScrDyn *signal, ScrDyn *listener);
typedef struct ScrStream {
size_t rc;
@@ -3227,6 +3231,30 @@ ScrStream *scr_stream_destroy(ScrStream *s, ScrError *err); /* recv +1 */
* the iterator's internal error consumer suppresses an unhandled crash when
* no user error listener exists. Receiver +1. */
ScrStream *scr_stream_iterator_close(ScrStream *s);
/* stream.addAbortSignal(signal, s) and the constructors' signal option
* (from_option: a falsy signal is ignored). Borrows; answers s +1, or
* NULL with ERR_INVALID_ARG_TYPE pending. */
ScrStream *scr_stream_add_abort_signal(ScrStream *s, ScrDyn *signal, bool from_option,
ScrAbortAddFn add);
/* finished(s, options[, cb]) — Node's eos with its listeners and state
* predicates: readable/writable are -1 (the default), 0 or 1; on_error is
* options.error !== false; signal is NULL or borrowed (ERR_INVALID_ARG_TYPE
* pending and NULL answered for a non-signal). The callback forms answer
* the +1 cleanup closure (cb moves; the typed form takes its invoke
* adapter); the promise form rejects instead of throwing. */
ScrClosure *scr_stream_finished_opts(ScrStream *s, ScrClosure *cb, ScrStreamErrInv inv,
double readable, double writable, bool on_error,
ScrDyn *signal, ScrAbortAddFn add);
ScrClosure *scr_stream_finished_opts_dyn(ScrStream *s, ScrDyn *cb, double readable,
double writable, bool on_error, ScrDyn *signal,
ScrAbortAddFn add);
ScrPromise *scr_sp_finished_opts(ScrStream *s, double readable, double writable, bool on_error,
bool cleanup, ScrDyn *signal, ScrAbortAddFn add);
/* stream/promises pipeline(...streams, { signal, end }): end false leaves
* the destination open; a non-signal value rejects with
* ERR_INVALID_ARG_TYPE. Streams borrowed. */
ScrPromise *scr_sp_pipeline_opts(double n, ScrStream **streams, bool end, ScrDyn *signal,
ScrAbortAddFn add);
double scr_stream_prop(ScrStream *s, const char *name);
ScrError *scr_stream_errored(ScrStream *s); /* +1 or NULL */
@@ -6494,22 +6522,59 @@ bool scr_stdin_pending(void);
void scr_stdin_remove_data(ScrClosure *cb);
bool scr_stdin_ended(void);
/* ── node:readline (scr_readline.c, linked under the events gate) ──────
* The question/close slice, Node's semantics pinned under pipes (the file
* header has the full model): create answers the f64 interface handle;
* question writes the query to stdout and delivers the next line's text
* through the adapter (fn(cb, +1 answer)) — throwing Node's
* "readline was closed" on a closed interface (may-throw); close fires
* the 'close' listeners SYNCHRONOUSLY (Node's inline emit) and detaches
* the stdin consumer; onClose registers a zero-arg listener (moves).
* Answer adapters: thunk0 ignores the line, thunk_str passes it. */
double scr_rl_create(void);
void scr_rl_question(double id, const ScrStr *query, ScrClosure *cb /*moves*/,
/* ── node:readline and node:readline/promises (scr_readline.c, linked
* under the events gate) ─────────────────────────────────────────────
* The non-terminal Interface (the file header has the model). Interfaces
* are f64 handles. A stream input or output goes through ScrRlIo, which
* scr_stream.c provides (scr_stream_rl_open), so stdin-only programs
* never link the stream unit; the stream side feeds input through
* scr_rl_input_*. Signal-taking entry points get addAbortListener as
* `add` (scr_events_add_abort_listener), passed only by programs that
* link the event-target unit. Listener event numbers: 0 'line',
* 1 'close', 2 'pause', 3 'resume', 4 'error'. */
typedef struct ScrRlIo {
void (*pause)(void *s);
void (*resume)(void *s);
void (*detach)(void *s, double id); /* removes the interface's input listeners */
void (*write)(void *s, ScrStr *text); /* borrows text */
void (*release)(void *s);
} ScrRlIo;
double scr_rl_open(const ScrRlIo *io, void *in /*+1 or NULL: stdin*/, void *out /*+1 or NULL*/,
bool out_stdout, double crlf_delay, ScrStr *prompt, ScrDyn *signal,
ScrAbortAddFn add);
void scr_rl_input_bytes(double id, ScrBytes *chunk);
void scr_rl_input_str(double id, ScrStr *chunk);
void scr_rl_input_end(double id);
void scr_rl_input_error(double id, ScrError *err);
/* question: throws ERR_USE_AFTER_CLOSE on a closed interface (may-throw);
* fn(cb, +1 answer). The promise form never throws. */
void scr_rl_question(double id, ScrStr *query, ScrClosure *cb /*moves*/,
void (*fn)(ScrClosure *, ScrStr *));
ScrPromise *scr_rl_question_promise(double id, ScrStr *query, ScrDyn *signal, ScrAbortAddFn add);
void scr_rl_close(double id);
void scr_rl_on_close(double id, ScrClosure *cb /*moves*/);
void scr_rl_pause(double id); /* may throw ERR_USE_AFTER_CLOSE */
void scr_rl_resume(double id); /* may throw ERR_USE_AFTER_CLOSE */
void scr_rl_prompt(double id); /* may throw ERR_USE_AFTER_CLOSE */
void scr_rl_set_prompt(double id, ScrStr *prompt);
ScrStr *scr_rl_get_prompt(double id); /* +1 */
void scr_rl_write(double id, ScrStr *data); /* may throw ERR_USE_AFTER_CLOSE */
void scr_rl_on(double id, double ev, ScrClosure *cb /*moves*/, void *fn, bool once);
void scr_rl_iter_open(double id);
ScrPromise *scr_rl_iter_next(double id); /* +1 promise<dyn>: the line, or undefined */
void scr_rl_iter_return(double id);
/* Listener adapters: line/answer (fn(cb, +1 text)) and error listeners. */
void scr_rl_answer_thunk0(ScrClosure *cb, ScrStr *answer);
void scr_rl_answer_thunk_str(ScrClosure *cb, ScrStr *answer);
void scr_rl_error_thunk(ScrClosure *cb, ScrError *err);
void scr_rl_error_thunk0(ScrClosure *cb, ScrError *err);
void scr_rl_answer_thunk_dyn(ScrClosure *cb, ScrStr *answer);
void scr_rl_error_thunk_dyn(ScrClosure *cb, ScrError *err);
/* createInterface with a stream input and/or output (scr_stream.c): in is
* the input stream or NULL for process.stdin; out the output stream or
* NULL (out_stdout picks process.stdout). Both borrowed, like prompt
* (NULL: the default "> "). */
double scr_stream_rl_open(ScrStream *in, ScrStream *out, bool out_stdout, double crlf_delay,
ScrStr *prompt, ScrDyn *signal, ScrAbortAddFn add);
void scr_stdin_data_thunk0(ScrClosure *cb, ScrBytes *chunk);
void scr_stdin_data_thunk_bytes(ScrClosure *cb, ScrBytes *chunk);
/* The loop-side registration (scr_async.c, always linked) and the
+800 -8
View File
@@ -2097,12 +2097,40 @@ void scr_stream_finished_dyn_inv(ScrClosure *cb, ScrStream *s, ScrError *err) {
typedef struct {
size_t rc;
size_t n, closed;
size_t watched; /* stages with a watcher: all, or all but the last (end: false) */
ScrStream **streams; /* owned */
ScrClosure *cb; /* owned; NULL once fired */
ScrStreamErrInv inv;
ScrError *err; /* first error, owned */
ScrDyn *dispose; /* the signal listener's disposable (+1), or NULL */
/* contexts holding a disposable: it holds the abort listener, which
* holds the context, so exit releases what a pipeline that never
* finished still holds */
void *live_prev, *live_next;
} ScrStreamPipeCtx;
static SCR_TL ScrStreamPipeCtx *scr_pipe_live = NULL;
static void scr_pipe_unlink(ScrStreamPipeCtx *ctx) {
ScrStreamPipeCtx *prev = ctx->live_prev, *next = ctx->live_next;
if (prev) prev->live_next = next;
else if (scr_pipe_live == ctx) scr_pipe_live = next;
if (next) next->live_prev = prev;
ctx->live_prev = ctx->live_next = NULL;
}
static void scr_pipe_atexit(void) {
while (scr_pipe_live) {
ScrStreamPipeCtx *ctx = scr_pipe_live;
scr_pipe_unlink(ctx);
if (ctx->dispose) {
ScrDyn *d = ctx->dispose;
ctx->dispose = NULL;
scr_dyn_release(d);
}
}
}
static void *scr_stream_pipectx_retain_v(void *p) {
((ScrStreamPipeCtx *)p)->rc++;
return p;
@@ -2115,24 +2143,38 @@ static void scr_stream_pipectx_release_v(void *p) {
free(ctx->streams);
if (ctx->cb) scr_closure_release(ctx->cb);
if (ctx->err) scr_error_release(ctx->err);
if (ctx->dispose) scr_dyn_release(ctx->dispose);
scr_pipe_unlink(ctx);
free(ctx);
}
static void scr_stream_pipectx_dispose(ScrStreamPipeCtx *ctx) {
if (!ctx->dispose) return;
scr_pipe_unlink(ctx);
ScrDyn *d = ctx->dispose;
ctx->dispose = NULL;
ScrDyn *r = scr_dyn_call(d, NULL, 0, "dispose");
if (r) scr_dyn_release(r);
scr_dyn_release(d);
}
static void scr_stream_pipeline_watch_inv(ScrClosure *cb, ScrStream *s, ScrError *err) {
ScrStreamPipeCtx *ctx = scr_box_get_ref(cb->caps[0]); /* +1 */
ctx->closed++;
if (err != NULL && ctx->err == NULL) {
ctx->err = scr_error_retain(err);
scr_stream_pipectx_dispose(ctx);
/* Node's pipeline destroyer: every other stream goes down with the
* SAME error, pipeline order (each emits 'error' then 'close'). */
for (size_t i = 0; i < ctx->n; i++) {
for (size_t i = 0; i < ctx->watched; i++) {
if (ctx->streams[i] != s) {
ScrStream *chain = scr_stream_destroy(ctx->streams[i], ctx->err);
scr_stream_release(chain);
}
}
}
if (ctx->closed == ctx->n && ctx->cb != NULL) {
if (ctx->closed == ctx->watched && ctx->cb != NULL) {
scr_stream_pipectx_dispose(ctx);
ScrClosure *ucb = ctx->cb;
ctx->cb = NULL;
ctx->inv(ucb, ctx->streams[ctx->n - 1], ctx->err);
@@ -2154,31 +2196,90 @@ ScrStream *scr_stream_pipeline_dyn(double n, ScrStream **streams, ScrDyn *cb) {
return scr_stream_pipeline(n, streams, c, &scr_stream_finished_dyn_inv);
}
static ScrError *scr_stream_abort_error(ScrDyn *signal);
static ScrDyn *scr_stream_signal_prop(ScrDyn *signal, const char *key);
static void *scr_stream_dyn_retain_v(void *p);
static void scr_stream_dyn_release_v(void *p);
/* pipeline's signal: an abort destroys every watched stage with
* new AbortError(undefined, { cause: signal.reason }) (finishImpl's
* destroyers); the callback follows the last close. caps[0] the context,
* caps[1] the signal. */
static ScrDyn *scr_stream_pipeline_abort_thunk(ScrClosure *clo, ScrDyn *const *args, size_t argc) {
(void)args;
(void)argc;
ScrStreamPipeCtx *ctx = scr_box_get_ref(clo->caps[0]); /* +1 */
ScrDyn *signal = scr_box_get_ref(clo->caps[1]); /* +1 */
if (ctx->err == NULL && ctx->cb != NULL) {
ctx->err = scr_stream_abort_error(signal);
scr_stream_pipectx_dispose(ctx);
for (size_t i = 0; i < ctx->watched; i++) {
ScrStream *chain = scr_stream_destroy(ctx->streams[i], ctx->err);
scr_stream_release(chain);
}
}
scr_dyn_release(signal);
scr_stream_pipectx_release_v(ctx);
return scr_dyn_retain(scr_dyn_undefined());
}
static ScrStream *scr_stream_pipeline_ex(size_t n, ScrStream **streams, ScrClosure *cb,
ScrStreamErrInv inv, bool end, ScrDyn *signal,
ScrAbortAddFn add);
ScrStream *scr_stream_pipeline(double n_d, ScrStream **streams /*borrowed*/,
ScrClosure *cb /*moves*/, ScrStreamErrInv inv) {
size_t n = (size_t)n_d;
return scr_stream_pipeline_ex((size_t)n_d, streams, cb, inv, true, NULL, NULL);
}
static ScrStream *scr_stream_pipeline_ex(size_t n, ScrStream **streams, ScrClosure *cb,
ScrStreamErrInv inv, bool end, ScrDyn *signal,
ScrAbortAddFn add) {
ScrStreamPipeCtx *ctx = calloc(1, sizeof *ctx);
if (!ctx) scr_stream_oom();
ctx->rc = 1;
ctx->n = n;
ctx->watched = end ? n : n - 1;
ctx->streams = calloc(n, sizeof *ctx->streams);
if (!ctx->streams) scr_stream_oom();
for (size_t i = 0; i < n; i++) ctx->streams[i] = scr_stream_retain(streams[i]);
ctx->cb = cb;
ctx->inv = inv;
/* Chain the pipes first (end: true — Node's pipeline forwards EOF). */
/* Chain the pipes first (Node's pipeline forwards EOF; end: false
* leaves the destination open). */
for (size_t i = 0; i + 1 < n; i++) {
ScrStream *d = scr_stream_pipe(streams[i], streams[i + 1], true);
ScrStream *d = scr_stream_pipe(streams[i], streams[i + 1], end || i + 2 < n);
scr_stream_release(d);
}
/* One watcher per stream: the terminal statuses drive error propagation
* and the final callback (after the LAST 'close'). */
for (size_t i = 0; i < n; i++) {
/* One watcher per stream (the destination is not watched under end:
* false): the terminal statuses drive error propagation and the final
* callback (after the LAST 'close'). */
for (size_t i = 0; i < ctx->watched; i++) {
ScrClosure *w = scr_closure_new((void *)&scr_stream_pipeline_watch_inv, 1);
w->caps[0] = scr_box_new_obj(&scr_stream_pipectx_retain_v, &scr_stream_pipectx_release_v, NULL);
scr_box_set_ref(w->caps[0], scr_stream_pipectx_retain_v(ctx));
scr_stream_fin_add(streams[i]->st, w, &scr_stream_pipeline_watch_inv);
}
if (signal) {
ScrClosure *clo = scr_closure_new((void *)&scr_stream_pipeline_abort_thunk, 2);
clo->caps[0] = scr_box_new_obj(&scr_stream_pipectx_retain_v, &scr_stream_pipectx_release_v, NULL);
scr_box_set_ref(clo->caps[0], scr_stream_pipectx_retain_v(ctx));
clo->caps[1] = scr_box_new_obj(&scr_stream_dyn_retain_v, &scr_stream_dyn_release_v, NULL);
scr_box_set_ref(clo->caps[1], scr_dyn_retain(signal));
ScrDyn *listener = scr_dyn_new_func(clo, &scr_stream_pipeline_abort_thunk, 0, "()", "abort");
ctx->dispose = add(signal, listener);
scr_dyn_release(listener);
if (ctx->dispose) {
static SCR_TL bool registered = false;
if (!registered) {
registered = true;
scr_atexit(scr_pipe_atexit);
}
ctx->live_next = scr_pipe_live;
if (scr_pipe_live) scr_pipe_live->live_prev = ctx;
scr_pipe_live = ctx;
}
}
scr_stream_pipectx_release_v(ctx); /* the boxes own it now */
return scr_stream_retain(streams[n - 1]);
}
@@ -2226,6 +2327,32 @@ ScrPromise *scr_sp_pipeline(double n, ScrStream **streams) {
return p;
}
/* pipeline(...streams, { signal, end }): end false leaves the destination
* open (the promise settles once the stages before it finish); signal is
* NULL or borrowed — a value that is not an AbortSignal rejects with
* ERR_INVALID_ARG_TYPE before anything is piped. */
ScrPromise *scr_sp_pipeline_opts(double n, ScrStream **streams, bool end, ScrDyn *signal,
ScrAbortAddFn add) {
ScrPromise *p = scr_promise_new();
if (signal && scr_dyn_is_nullish(signal)) signal = NULL;
if (signal) {
ScrDyn *aborted = NULL;
if (signal->kind == SCR_DYN_OBJ || signal->kind == SCR_DYN_HANDLE) {
aborted = scr_stream_signal_prop(signal, "aborted");
}
if (!aborted) {
scr_dyn_prop_type_fail("options.signal", "an instance of AbortSignal", signal);
scr_promise_reject_pending(p);
return p;
}
scr_dyn_release(aborted);
}
ScrStream *dst = scr_stream_pipeline_ex((size_t)n, streams, scr_sp_watcher(p),
&scr_sp_settle_inv, end, signal, add);
scr_stream_release(dst);
return p;
}
/* ── node:stream/consumers ────────────────────────────────────────────
* text/json/buffer/arrayBuffer over the readable machinery: a native 'data' listener
* accumulates every chunk (Buffer chunks as-is; string chunks — an
@@ -2369,6 +2496,671 @@ ScrPromise *scr_sc_json(ScrStream *s) { return scr_sc_consume(s, SCR_SC_JSON); }
ScrPromise *scr_sc_buffer(ScrStream *s) { return scr_sc_consume(s, SCR_SC_BUFFER); }
ScrPromise *scr_sc_array_buffer(ScrStream *s) { return scr_sc_consume(s, SCR_SC_ARRAY_BUFFER); }
/* ── stream.addAbortSignal and the constructors' signal option ─────────
* lib/internal/streams/add-abort-signal.js: the signal must be an object
* with an `aborted` property (ERR_INVALID_ARG_TYPE otherwise). An aborted
* signal destroys the stream now with new AbortError(undefined, { cause:
* signal.reason }); otherwise an abort listener does it later, and the
* listener is removed when the stream finishes (eos — whose own 'error'
* listener also marks the stream's errors handled). The signal's
* properties are read through the generic checked-dynamic getters, and
* addAbortListener arrives as `add` from the event-target unit, so this
* unit links without it. */
static ScrDyn *scr_stream_signal_prop(ScrDyn *signal, const char *key) {
size_t n = strlen(key);
if (signal->kind == SCR_DYN_OBJ) {
ScrDyn *v = scr_dyn_obj_get(signal, key, n);
return v ? scr_dyn_retain(v) : NULL;
}
ScrStr *k = scr_str_new(key, n);
ScrDyn *v = scr_dyn_handle_key_get(signal, k);
scr_str_release(k);
return v;
}
static ScrError *scr_stream_abort_error(ScrDyn *signal) {
ScrStr *message = scr_str_new("The operation was aborted", 25);
ScrError *e = scr_error_new(SCR_ERR_ERROR, message);
scr_str_release(message);
scr_str_release(e->name);
e->name = scr_str_new("AbortError", 10);
/* AbortError assigns code, then name: own enumerable properties */
scr_error_set_code(e, "ABORT_ERR");
e->name_present = true;
e->name_enumerable = true;
ScrDyn *reason = scr_stream_signal_prop(signal, "reason");
scr_error_define_cause(e, reason ? reason : scr_dyn_undefined());
if (reason) scr_dyn_release(reason);
return e;
}
static void scr_stream_abort_now(ScrStream *s, ScrDyn *signal) {
ScrError *e = scr_stream_abort_error(signal);
scr_stream_release(scr_stream_destroy(s, e));
scr_error_release(e);
}
/* onAbort: caps[0] the stream, caps[1] the signal. */
static ScrDyn *scr_stream_abort_thunk(ScrClosure *clo, ScrDyn *const *args, size_t argc) {
(void)args;
(void)argc;
ScrStream *s = scr_box_get_ref(clo->caps[0]); /* +1, NULL after exit teardown */
ScrDyn *signal = scr_box_get_ref(clo->caps[1]); /* +1 */
if (s) scr_stream_abort_now(s, signal);
scr_dyn_release(signal);
if (s) scr_stream_release(s);
return scr_dyn_retain(scr_dyn_undefined());
}
/* A pending abort hook: the listener closure (borrowed) holds the stream,
* and the stream's watcher holds the listener through the disposable — a
* cycle the stream's finish breaks. Hooks still pending at exit drop the
* listener's stream so the stream can be freed. */
typedef struct ScrAbortHook {
ScrClosure *listener; /* borrowed */
struct ScrAbortHook *prev, *next;
} ScrAbortHook;
static SCR_TL ScrAbortHook *scr_abort_hooks = NULL;
static void scr_abort_hook_unlink(ScrAbortHook *h) {
if (h->prev) h->prev->next = h->next;
else if (scr_abort_hooks == h) scr_abort_hooks = h->next;
if (h->next) h->next->prev = h->prev;
free(h);
}
static void scr_abort_hooks_atexit(void) {
while (scr_abort_hooks) {
ScrAbortHook *h = scr_abort_hooks;
scr_box_set_ref(h->listener->caps[0], NULL);
scr_abort_hook_unlink(h);
}
}
static void *scr_stream_ptr_keep_v(void *p) { return p; }
static void scr_stream_ptr_drop_v(void *p) { (void)p; }
/* The eos callback: the disposable (caps[0]) removes the abort listener;
* caps[1] is the pending hook. */
static void scr_stream_abort_dispose_inv(ScrClosure *cb, ScrStream *s, ScrError *err) {
(void)s;
(void)err;
ScrAbortHook *h = scr_box_get_ref(cb->caps[1]);
if (h) {
scr_box_set_ref(cb->caps[1], NULL);
scr_abort_hook_unlink(h);
}
ScrDyn *dispose = scr_box_get_ref(cb->caps[0]); /* +1 */
ScrDyn *r = scr_dyn_call(dispose, NULL, 0, "dispose");
if (r) scr_dyn_release(r);
scr_dyn_release(dispose);
}
static void *scr_stream_dyn_retain_v(void *p) { return p ? scr_dyn_retain(p) : NULL; }
static void scr_stream_dyn_release_v(void *p) { scr_dyn_release(p); }
/* addAbortSignal(signal, stream), or the `signal` option (from_option:
* a falsy value is ignored, Node's `if (options.signal)`). Borrows both;
* answers the stream +1, or NULL with ERR_INVALID_ARG_TYPE pending. */
ScrStream *scr_stream_add_abort_signal(ScrStream *s, ScrDyn *signal, bool from_option,
ScrAbortAddFn add) {
if (from_option && (signal == NULL || !scr_dyn_truthy(signal))) return scr_stream_retain(s);
if (signal && signal->kind == SCR_DYN_NULL) {
/* typeof null is 'object': the `in` test itself throws */
static const char m[] = "Cannot use 'in' operator to search for 'aborted' in null";
scr_throw_error_msg(SCR_ERR_TYPE, m, sizeof m - 1);
return NULL;
}
ScrDyn *aborted = NULL;
if (signal && (signal->kind == SCR_DYN_OBJ || signal->kind == SCR_DYN_HANDLE)) {
aborted = scr_stream_signal_prop(signal, "aborted");
}
if (aborted == NULL) {
scr_dyn_arg_type_fail("signal", "an instance of AbortSignal",
signal ? signal : scr_dyn_undefined());
return NULL;
}
bool is_aborted = scr_dyn_truthy(aborted);
scr_dyn_release(aborted);
if (is_aborted) {
scr_stream_abort_now(s, signal);
return scr_stream_retain(s);
}
ScrClosure *clo = scr_closure_new((void *)&scr_stream_abort_thunk, 2);
clo->caps[0] = scr_box_new_obj(&scr_stream_retain_v, &scr_stream_release_v, &scr_stream_trace);
scr_box_set_ref(clo->caps[0], scr_stream_retain(s));
clo->caps[1] = scr_box_new_obj(&scr_stream_dyn_retain_v, &scr_stream_dyn_release_v, NULL);
scr_box_set_ref(clo->caps[1], scr_dyn_retain(signal));
ScrDyn *listener = scr_dyn_new_func(clo, &scr_stream_abort_thunk, 0, "()", "onAbort");
ScrDyn *dispose = add(signal, listener);
scr_dyn_release(listener);
if (dispose == NULL) return NULL;
static SCR_TL bool registered = false;
if (!registered) {
registered = true;
scr_atexit(scr_abort_hooks_atexit);
}
ScrAbortHook *h = calloc(1, sizeof *h);
if (!h) scr_stream_oom();
h->listener = clo;
h->next = scr_abort_hooks;
if (scr_abort_hooks) scr_abort_hooks->prev = h;
scr_abort_hooks = h;
ScrClosure *w = scr_closure_new((void *)&scr_stream_abort_dispose_inv, 2);
w->caps[0] = scr_box_new_obj(&scr_stream_dyn_retain_v, &scr_stream_dyn_release_v, NULL);
scr_box_set_ref(w->caps[0], dispose); /* moves */
w->caps[1] = scr_box_new_obj(&scr_stream_ptr_keep_v, &scr_stream_ptr_drop_v, NULL);
scr_box_set_ref(w->caps[1], h);
scr_closure_release(scr_stream_finished(s, w, &scr_stream_abort_dispose_inv));
return scr_stream_retain(s);
}
/* ── node:readline over stream inputs and outputs ─────────────────────
* The Interface's stream side (scr_readline.c has the model): 'data',
* 'end' and 'error' listeners on the input, registered in that order like
* Node's constructor ('error' first there; it is never observable before
* the others), removed again when the interface closes, and prompt
* writes to the output. The input resumes at creation (input.resume()). */
typedef struct ScrRlHook {
ScrStream *s; /* borrowed: the interface holds the stream */
double id;
ScrClosure *data, *end, *error; /* borrowed: the emitter owns them */
struct ScrRlHook *next;
} ScrRlHook;
static SCR_TL ScrRlHook *scr_rl_hooks = NULL;
static double scr_stream_rl_id(ScrClosure *cb) { return scr_box_get_f64(cb->caps[0]); }
static void scr_stream_rl_data(ScrClosure *cb, void *b, void *str) {
if (b != NULL) scr_rl_input_bytes(scr_stream_rl_id(cb), (ScrBytes *)b);
else if (str != NULL) scr_rl_input_str(scr_stream_rl_id(cb), (ScrStr *)str);
}
static void scr_stream_rl_end(ScrClosure *cb) { scr_rl_input_end(scr_stream_rl_id(cb)); }
static void scr_stream_rl_error(ScrClosure *cb, void *err) {
scr_rl_input_error(scr_stream_rl_id(cb), (ScrError *)err);
}
static ScrClosure *scr_stream_rl_listener(void *fn, double id) {
ScrClosure *c = scr_closure_new(fn, 1);
c->caps[0] = scr_box_new(SCR_BOX_F64);
scr_box_set_f64(c->caps[0], id);
return c;
}
static void scr_stream_rl_on(ScrStream *s, const char *name, ScrClosure *cb, ScrEeInvoke inv) {
ScrStr *n = scr_str_new(name, strlen(name));
scr_emitter_release(scr_emitter_on((ScrEmitter *)s, n, cb, inv, false, false));
scr_str_release(n);
}
static void scr_stream_rl_off(ScrStream *s, const char *name, ScrClosure *cb) {
ScrStr *n = scr_str_new(name, strlen(name));
scr_emitter_release(scr_emitter_off((ScrEmitter *)s, n, cb));
scr_str_release(n);
}
static void scr_stream_rl_pause(void *s) { scr_stream_release(scr_stream_pause(s)); }
static void scr_stream_rl_resume(void *s) { scr_stream_release(scr_stream_resume(s)); }
static void scr_stream_rl_write(void *s, ScrStr *text) { scr_stream_write_str(s, text, NULL); }
static void scr_stream_rl_release(void *s) { scr_stream_release(s); }
static void scr_stream_rl_detach(void *s, double id) {
for (ScrRlHook **at = &scr_rl_hooks; *at; at = &(*at)->next) {
ScrRlHook *h = *at;
if (h->s != s || h->id != id) continue;
*at = h->next;
scr_stream_rl_off(h->s, "data", h->data);
scr_stream_rl_off(h->s, "error", h->error);
scr_stream_rl_off(h->s, "end", h->end);
free(h);
return;
}
}
static const ScrRlIo scr_stream_rl_io = {
&scr_stream_rl_pause, &scr_stream_rl_resume, &scr_stream_rl_detach,
&scr_stream_rl_write, &scr_stream_rl_release,
};
static void scr_stream_rl_hooks_free(void) {
while (scr_rl_hooks) {
ScrRlHook *h = scr_rl_hooks;
scr_rl_hooks = h->next;
free(h);
}
}
double scr_stream_rl_open(ScrStream *in, ScrStream *out, bool out_stdout, double crlf_delay,
ScrStr *prompt, ScrDyn *signal, ScrAbortAddFn add) {
double id = scr_rl_open(&scr_stream_rl_io, in ? scr_stream_retain(in) : NULL,
out ? scr_stream_retain(out) : NULL, out_stdout, crlf_delay, prompt,
signal, add);
if (in == NULL) return id;
static SCR_TL bool registered = false;
if (!registered) {
registered = true;
scr_atexit(scr_stream_rl_hooks_free);
}
ScrRlHook *h = calloc(1, sizeof *h);
if (!h) scr_stream_oom();
h->s = in;
h->id = id;
h->error = scr_stream_rl_listener((void *)&scr_stream_rl_error, id);
h->data = scr_stream_rl_listener((void *)&scr_stream_rl_data, id);
h->end = scr_stream_rl_listener((void *)&scr_stream_rl_end, id);
h->next = scr_rl_hooks;
scr_rl_hooks = h;
scr_stream_rl_on(in, "error", h->error, scr_ee_inv_fixed1);
scr_stream_rl_on(in, "data", h->data, scr_ee_inv_fixed2);
scr_stream_rl_on(in, "end", h->end, scr_ee_inv_fixed0);
scr_stream_rl_resume(in);
return id;
}
/* ── finished(stream, options) (lib/internal/streams/end-of-stream.js) ──
* The options form registers Node's own listeners on the stream — 'end',
* 'finish', 'error' (unless options.error is false) and 'close', in that
* order — and decides with Node's predicates over the stream state:
* options.readable/writable (default: whether the stream has that side)
* choose which ends count, and willEmitClose (autoDestroy, emitClose,
* not yet closed, and both sides counted) defers the callback to 'close'.
* options.signal: an abort removes the listeners and calls back with
* new AbortError(undefined, { cause: signal.reason }). The callback runs
* once; the answered cleanup closure removes the listeners. */
typedef struct ScrEos {
size_t rc;
/* Borrowed: the stream's listeners hold the context, so the context
* must not hold the stream; every closure that can run after the
* listeners are gone (ticks, the abort listener, cleanup) holds the
* stream itself. */
ScrStream *s;
bool pending; /* the callback has not run */
ScrClosure *cb; /* owned until it runs or cleanup drops it (callback form) */
ScrStreamErrInv inv;
bool readable, writable;
bool will_emit_close;
bool readable_finished, writable_finished;
bool on_error;
ScrClosure *l_end, *l_finish, *l_error, *l_close; /* emitter-owned */
ScrDyn *dispose; /* the signal listener's disposable (+1) */
ScrDyn *signal; /* +1 */
ScrPromise *promise; /* the promise form (+1) */
bool auto_cleanup;
struct ScrEos *live_prev, *live_next; /* pending contexts, released at exit */
} ScrEos;
static SCR_TL ScrEos *scr_eos_live = NULL;
static void scr_eos_unlink(ScrEos *e) {
if (e->live_prev) e->live_prev->live_next = e->live_next;
else if (scr_eos_live == e) scr_eos_live = e->live_next;
if (e->live_next) e->live_next->live_prev = e->live_prev;
e->live_prev = e->live_next = NULL;
}
/* The callback and promise a context still holds: dropped once it
* settles, is cleaned up, or the program exits. */
static void scr_eos_drop(ScrEos *e) {
e->pending = false;
scr_eos_unlink(e);
if (e->cb) {
scr_closure_release(e->cb);
e->cb = NULL;
}
if (e->promise) {
scr_promise_release(e->promise);
e->promise = NULL;
}
}
static void *scr_eos_retain_v(void *p);
static void scr_eos_release_v(void *p);
/* At exit the disposable goes too: it holds the abort listener, which
* holds this context. */
static void scr_eos_atexit(void) {
while (scr_eos_live) {
ScrEos *e = scr_eos_retain_v(scr_eos_live);
ScrDyn *d = e->dispose;
e->dispose = NULL;
scr_eos_drop(e);
if (d) scr_dyn_release(d);
scr_eos_release_v(e);
}
}
static void *scr_eos_retain_v(void *p) {
((ScrEos *)p)->rc++;
return p;
}
static void scr_eos_release_v(void *p) {
ScrEos *e = p;
if (--e->rc > 0) return;
if (e->cb) scr_closure_release(e->cb);
if (e->dispose) scr_dyn_release(e->dispose);
if (e->signal) scr_dyn_release(e->signal);
if (e->promise) scr_promise_release(e->promise);
scr_eos_unlink(e);
free(e);
}
/* A closure over the context; `keep` also holds the stream. */
static ScrClosure *scr_eos_closure_k(void *fn, ScrEos *e, bool keep) {
ScrClosure *c = scr_closure_new(fn, keep ? 2 : 1);
c->caps[0] = scr_box_new_obj(&scr_eos_retain_v, &scr_eos_release_v, NULL);
scr_box_set_ref(c->caps[0], scr_eos_retain_v(e));
if (keep) {
c->caps[1] = scr_box_new_obj(&scr_stream_retain_v, &scr_stream_release_v, &scr_stream_trace);
scr_box_set_ref(c->caps[1], scr_stream_retain(e->s));
}
return c;
}
static ScrClosure *scr_eos_closure(void *fn, ScrEos *e) { return scr_eos_closure_k(fn, e, false); }
static ScrEos *scr_eos_of(ScrClosure *c) { return scr_box_get_ref(c->caps[0]); /* +1 */ }
/* Node's predicates (internal/streams/utils.js) over the state. */
static bool scr_eos_closed(const ScrStreamState *st) { return st->close_scheduled || st->close_emitted; }
static bool scr_eos_readable_prop(const ScrStreamState *st) {
return st->has_r && !st->destroyed && !st->error_emitted && !st->r.end_emitted;
}
static bool scr_eos_writable_prop(const ScrStreamState *st) {
return st->has_w && !st->destroyed && !st->errored && !st->w.ending;
}
static bool scr_eos_readable_finished(const ScrStreamState *st, bool strict) {
if (!st->has_r || st->errored) return false;
return st->r.end_emitted || (!strict && st->r.ended && st->r.length == 0);
}
static bool scr_eos_writable_finished(const ScrStreamState *st, bool strict) {
if (!st->has_w) return false;
if (st->w.finished) return true;
if (st->errored) return false;
return !strict && st->w.ending && st->w.length == 0;
}
/* isReadable/isWritable: -1 for null (no such property), else 0/1. */
static int scr_eos_is_readable(const ScrStreamState *st) {
if (!st->has_r) return -1;
if (st->destroyed) return 0;
return scr_eos_readable_prop(st) && !st->r.end_emitted;
}
static int scr_eos_is_writable(const ScrStreamState *st) {
if (!st->has_w) return -1;
if (st->destroyed) return 0;
return scr_eos_writable_prop(st) && !st->w.ending;
}
static void scr_eos_unlisten(ScrEos *e) {
ScrStream *s = e->s;
if (e->l_end) scr_stream_rl_off(s, "end", e->l_end);
if (e->l_finish) scr_stream_rl_off(s, "finish", e->l_finish);
if (e->l_error) scr_stream_rl_off(s, "error", e->l_error);
if (e->l_close) scr_stream_rl_off(s, "close", e->l_close);
e->l_end = e->l_finish = e->l_error = e->l_close = NULL;
}
static void scr_eos_dispose_signal(ScrEos *e) {
if (!e->dispose) return;
ScrDyn *d = e->dispose;
e->dispose = NULL;
ScrDyn *r = scr_dyn_call(d, NULL, 0, "dispose");
if (r) scr_dyn_release(r);
scr_dyn_release(d);
}
static void scr_eos_settle(ScrEos *e, ScrError *err);
/* callback.call(stream, err): once; borrows err. */
static void scr_eos_fire(ScrEos *e, ScrError *err) {
if (!e->pending) return;
scr_eos_dispose_signal(e);
scr_eos_settle(e, err);
}
/* Runs the callback (or settles the promise) and drops them. */
static void scr_eos_settle(ScrEos *e, ScrError *err) {
scr_eos_retain_v(e);
ScrClosure *cb = e->cb;
ScrPromise *p = e->promise;
e->cb = NULL;
e->promise = NULL;
e->pending = false;
scr_eos_unlink(e);
if (p) {
if (e->auto_cleanup) scr_eos_unlisten(e);
if (err != NULL) {
scr_throw_obj(scr_error_retain(err), &scr_error_retain_v, &scr_error_release_v,
scr_error_trace_arg());
scr_promise_reject_pending(p);
} else {
scr_promise_fulfill_void(p);
}
scr_promise_release(p);
} else if (cb) {
e->inv(cb, e->s, err);
scr_closure_release(cb);
}
scr_eos_release_v(e);
}
static void scr_eos_premature(ScrEos *e) {
ScrError *err = scr_stream_mkerr("ERR_STREAM_PREMATURE_CLOSE", "Premature close");
scr_eos_fire(e, err);
scr_error_release(err);
}
static void scr_eos_onfinish(ScrClosure *c) {
ScrEos *e = scr_eos_of(c);
ScrStreamState *st = e->s->st;
e->writable_finished = true;
if (st->destroyed) e->will_emit_close = false;
if (!(e->will_emit_close && (!scr_eos_readable_prop(st) || e->readable))) {
if (!e->readable || e->readable_finished) scr_eos_fire(e, NULL);
}
scr_eos_release_v(e);
}
static void scr_eos_onend(ScrClosure *c) {
ScrEos *e = scr_eos_of(c);
ScrStreamState *st = e->s->st;
e->readable_finished = true;
if (st->destroyed) e->will_emit_close = false;
if (!(e->will_emit_close && (!scr_eos_writable_prop(st) || e->writable))) {
if (!e->writable || e->writable_finished) scr_eos_fire(e, NULL);
}
scr_eos_release_v(e);
}
static void scr_eos_onerror(ScrClosure *c, void *err) {
ScrEos *e = scr_eos_of(c);
scr_eos_fire(e, (ScrError *)err);
scr_eos_release_v(e);
}
static void scr_eos_onclose(ScrClosure *c) {
ScrEos *e = scr_eos_of(c);
ScrStreamState *st = e->s->st;
if (st->errored) {
scr_eos_fire(e, st->errored);
} else if (e->readable && !e->readable_finished && st->has_r &&
!scr_eos_readable_finished(st, false)) {
scr_eos_premature(e);
} else if (e->writable && !e->writable_finished && !scr_eos_writable_finished(st, false)) {
scr_eos_premature(e);
} else {
scr_eos_fire(e, NULL);
}
scr_eos_release_v(e);
}
static void scr_eos_onclosed(ScrClosure *c) {
ScrEos *e = scr_eos_of(c);
ScrStreamState *st = e->s->st;
scr_eos_fire(e, st->errored);
scr_eos_release_v(e);
}
/* abort: the listeners go (cleanup keeps the callback for this call),
* then the callback runs with the AbortError. */
static void scr_eos_abort(ScrEos *e) {
if (!e->pending) return;
scr_eos_unlisten(e);
scr_eos_dispose_signal(e);
ScrError *err = scr_stream_abort_error(e->signal);
scr_eos_settle(e, err);
scr_error_release(err);
}
static void scr_eos_abort_tick(ScrClosure *c) {
ScrEos *e = scr_eos_of(c);
scr_eos_abort(e);
scr_eos_release_v(e);
}
static ScrDyn *scr_eos_abort_thunk(ScrClosure *c, ScrDyn *const *args, size_t argc) {
(void)args;
(void)argc;
ScrEos *e = scr_eos_of(c);
scr_eos_abort(e);
scr_eos_release_v(e);
return scr_dyn_retain(scr_dyn_undefined());
}
static void scr_eos_cleanup_fn(ScrClosure *c) {
ScrEos *e = scr_eos_of(c);
scr_eos_unlisten(e);
scr_eos_dispose_signal(e);
if (e->pending) scr_eos_drop(e);
scr_eos_release_v(e);
}
/* readable/writable: -1 for the default; on_error: options.error !==
* false; signal: NULL when absent (borrowed). cb moves. Answers the +1
* cleanup closure, or NULL with ERR_INVALID_ARG_TYPE pending for a signal
* that is not an AbortSignal. */
static ScrClosure *scr_stream_eos_impl(ScrStream *s, ScrClosure *cb, ScrStreamErrInv inv,
ScrPromise *p, bool auto_cleanup, int readable,
int writable, bool on_error, ScrDyn *signal,
ScrAbortAddFn add) {
ScrStreamState *st = s->st;
static SCR_TL bool registered = false;
if (!registered) {
registered = true;
scr_atexit(scr_eos_atexit);
}
if (signal && !scr_dyn_is_nullish(signal)) {
ScrDyn *aborted = NULL;
if (signal->kind == SCR_DYN_OBJ || signal->kind == SCR_DYN_HANDLE) {
aborted = scr_stream_signal_prop(signal, "aborted");
}
if (!aborted) {
if (cb) scr_closure_release(cb);
scr_dyn_prop_type_fail("options.signal", "an instance of AbortSignal", signal);
return NULL;
}
scr_dyn_release(aborted);
} else {
signal = NULL;
}
ScrEos *e = calloc(1, sizeof *e);
if (!e) scr_stream_oom();
e->rc = 1;
e->s = s;
e->pending = true;
e->cb = cb;
e->inv = inv;
e->promise = p ? scr_promise_retain(p) : NULL;
e->auto_cleanup = auto_cleanup;
e->live_next = scr_eos_live;
if (scr_eos_live) scr_eos_live->live_prev = e;
scr_eos_live = e;
e->readable = readable < 0 ? st->has_r : readable != 0;
e->writable = writable < 0 ? st->has_w : writable != 0;
e->on_error = on_error;
e->will_emit_close = st->auto_destroy && st->emit_close && !scr_eos_closed(st) &&
st->has_r == e->readable && st->has_w == e->writable;
e->writable_finished = scr_eos_writable_finished(st, false);
e->readable_finished = scr_eos_readable_finished(st, false);
e->l_end = scr_eos_closure((void *)&scr_eos_onend, e);
e->l_finish = scr_eos_closure((void *)&scr_eos_onfinish, e);
e->l_close = scr_eos_closure((void *)&scr_eos_onclose, e);
scr_stream_rl_on(s, "end", e->l_end, scr_ee_inv_fixed0);
scr_stream_rl_on(s, "finish", e->l_finish, scr_ee_inv_fixed0);
if (on_error) {
e->l_error = scr_eos_closure((void *)&scr_eos_onerror, e);
scr_stream_rl_on(s, "error", e->l_error, scr_ee_inv_fixed1);
}
scr_stream_rl_on(s, "close", e->l_close, scr_ee_inv_fixed0);
bool closed = scr_eos_closed(st);
if (closed) {
scr_next_tick(scr_eos_closure_k((void *)&scr_eos_onclose, e, true));
} else if (st->error_emitted) {
if (!e->will_emit_close) scr_next_tick(scr_eos_closure_k((void *)&scr_eos_onclosed, e, true));
} else if (!e->readable && (!e->will_emit_close || scr_eos_is_readable(st) == 1) &&
(e->writable_finished || scr_eos_is_writable(st) == 0) &&
(!st->has_w || (!st->w.writing && st->w.head == st->w.n))) {
scr_next_tick(scr_eos_closure_k((void *)&scr_eos_onclosed, e, true));
} else if (!e->writable && (!e->will_emit_close || scr_eos_is_writable(st) == 1) &&
(e->readable_finished || scr_eos_is_readable(st) == 0)) {
scr_next_tick(scr_eos_closure_k((void *)&scr_eos_onclosed, e, true));
}
if (signal && !closed) {
e->signal = scr_dyn_retain(signal);
ScrDyn *aborted = scr_stream_signal_prop(signal, "aborted");
bool is_aborted = aborted && scr_dyn_truthy(aborted);
if (aborted) scr_dyn_release(aborted);
if (is_aborted) {
scr_next_tick(scr_eos_closure_k((void *)&scr_eos_abort_tick, e, true));
} else {
ScrClosure *clo = scr_eos_closure_k((void *)&scr_eos_abort_thunk, e, true);
ScrDyn *listener = scr_dyn_new_func(clo, &scr_eos_abort_thunk, 0, "()", "abort");
e->dispose = add(signal, listener);
scr_dyn_release(listener);
}
}
ScrClosure *cleanup = scr_eos_closure_k((void *)&scr_eos_cleanup_fn, e, true);
scr_eos_release_v(e);
return cleanup;
}
ScrClosure *scr_stream_finished_opts(ScrStream *s, ScrClosure *cb /*moves*/, ScrStreamErrInv inv,
double readable, double writable, bool on_error,
ScrDyn *signal, ScrAbortAddFn add) {
return scr_stream_eos_impl(s, cb, inv, NULL, false, (int)readable, (int)writable, on_error,
signal, add);
}
ScrClosure *scr_stream_finished_opts_dyn(ScrStream *s, ScrDyn *cb, double readable,
double writable, bool on_error, ScrDyn *signal,
ScrAbortAddFn add) {
ScrClosure *c = scr_stream_dynopt_clo(cb, (void *)&scr_stream_finished_dyn_inv);
return scr_stream_eos_impl(s, c, &scr_stream_finished_dyn_inv, NULL, false, (int)readable,
(int)writable, on_error, signal, add);
}
/* stream/promises finished(stream, options): cleanup removes the
* listeners once the promise settles. */
ScrPromise *scr_sp_finished_opts(ScrStream *s, double readable, double writable, bool on_error,
bool cleanup, ScrDyn *signal, ScrAbortAddFn add) {
ScrPromise *p = scr_promise_new();
ScrClosure *cleanup_fn = scr_stream_eos_impl(s, NULL, NULL, p, cleanup, (int)readable,
(int)writable, on_error, signal, add);
if (cleanup_fn == NULL) {
scr_promise_reject_pending(p);
return p;
}
scr_closure_release(cleanup_fn);
return p;
}
/* ── node:console Console instances over writable streams ─────────────
* Node's kWriteToConsole: with ignoreErrors (the default) one shared noop
* 'error' listener brackets the write when the stream has none, and the
@@ -0,0 +1,49 @@
// node:readline/promises question() over a stream input and output: the
// prompt is written to the output, the next line answers, a second
// question while one is pending writes the first prompt again and never
// settles, close() leaves a pending question unsettled, and a question on
// a closed interface rejects with ERR_USE_AFTER_CLOSE. Without an output
// nothing is written. setPrompt/getPrompt/prompt manage the prompt, and
// write() feeds text as if it were input.
import * as readline from "node:readline/promises";
import { PassThrough, Readable } from "node:stream";
const out = new PassThrough();
out.on("data", (c: Buffer) => console.log("output:", JSON.stringify(c.toString())));
const rl = readline.createInterface({ input: Readable.from(["alpha\nbeta\n"]), output: out });
const first = rl.question("first? ");
const second = rl.question("second? ");
first.then((answer: string) => console.log("first answer:", answer));
second.then((answer: string) => console.log("second answer (never):", answer));
rl.on("line", (line: string) => console.log("line:", line));
rl.on("pause", () => console.log("pause"));
rl.on("close", () => console.log("close"));
await new Promise<void>((resolve) => setTimeout(resolve, 10));
const quiet = readline.createInterface({ input: new Readable({ read() {} }) });
quiet.question("not written ").then((answer: string) => console.log("never:", answer));
quiet.close();
try {
await quiet.question("again ");
} catch (e) {
console.log("after close:", (e as Error & { code?: string }).code, (e as Error).message);
}
const prompter = readline.createInterface({
input: new Readable({ read() {} }),
output: out,
prompt: "$ ",
});
console.log("prompt:", JSON.stringify(prompter.getPrompt()));
prompter.prompt();
prompter.setPrompt("> ");
prompter.prompt(true);
prompter.on("line", (line: string) => console.log("typed:", line));
prompter.write("one\ntwo");
prompter.write("\n");
const typed = prompter.question("q? ");
prompter.write("answer\n");
console.log("answered:", await typed);
console.log("prompt restored:", JSON.stringify(prompter.getPrompt()));
prompter.close();
+43
View File
@@ -0,0 +1,43 @@
// Line splitting and async iteration on node:readline/promises: \n, \r\n
// and a lone \r end lines, a \n that arrives in the next chunk after a
// \r is dropped (within crlfDelay), UTF-8 sequences split across chunks
// decode whole, and the unfinished last line is emitted at the end of
// input. for await reads the same lines; after a break the lines already
// queued are still read by the next loop. More than 1024 queued lines
// pause the input until the loop catches up.
import { createInterface } from "node:readline/promises";
import { Readable } from "node:stream";
const src = new Readable({ read() {} });
const rl = createInterface({ input: src, crlfDelay: Infinity });
rl.on("line", (line: string) => console.log("line", JSON.stringify(line)));
rl.on("close", () => console.log("closed"));
for (const chunk of ["one\r", "\ntwo\r\nthree\rfour", "\n", "cafÃ", "©\nlast"]) {
src.push(Buffer.from(chunk, "latin1"));
}
src.push(null);
await new Promise<void>((resolve) => setTimeout(resolve, 10));
const lines = createInterface({ input: Readable.from(["1\n2\n3\n4"]) });
for await (const line of lines) {
console.log("first loop", line);
break;
}
for await (const line of lines) console.log("second loop", line);
console.log("loops done");
const many: string[] = [];
for (let i = 0; i < 1500; i++) many.push(`row ${i}`);
const big = createInterface({ input: Readable.from([many.join("\n") + "\n"]) });
let pauses = 0;
let resumes = 0;
big.on("pause", () => pauses++);
big.on("resume", () => resumes++);
let count = 0;
let lastLine = "";
for await (const line of big) {
count++;
lastLine = line;
if (count === 1) await new Promise<void>((resolve) => setTimeout(resolve, 5));
}
console.log("read", count, lastLine, "pauses", pauses, "resumes", resumes);
@@ -0,0 +1,32 @@
// The callback node:readline Interface from CommonJS: 'line' listeners
// (on and once) in registration order, a question answered by the next
// line while other lines go to 'line', 'pause' before 'close' when the
// input ends, listeners added after 'close' never running, and a question
// after close throwing ERR_USE_AFTER_CLOSE. An error on the input reaches
// the interface's 'error' listener.
'use strict';
const readline = require('node:readline');
const { Readable } = require('node:stream');
const rl = readline.createInterface({ input: Readable.from(['a\nb\nc\nd']) });
rl.on('line', (line) => console.log('on', line));
rl.once('line', (line) => console.log('once', line));
rl.question('', (answer) => console.log('answer', answer));
rl.on('pause', () => console.log('pause'));
rl.on('close', () => {
console.log('close');
rl.on('close', () => console.log('never'));
try {
rl.question('again', () => {});
} catch (e) {
console.log('question after close:', e.code, e.message);
}
rl.close();
});
const broken = new Readable({ read() {} });
const rl2 = readline.createInterface({ input: broken });
rl2.on('error', (err) => console.log('interface error:', err.message));
rl2.on('line', (line) => console.log('rl2 line', line));
broken.push('ok\n');
setTimeout(() => broken.destroy(new Error('input failed')), 5);
+46
View File
@@ -0,0 +1,46 @@
// AbortSignal with node:readline/promises: an aborted question rejects
// with an AbortError whose cause is the signal's reason and writes
// clearLine's "\r\n"; an already aborted signal rejects before the prompt
// is written. The createInterface signal option closes the interface when
// it aborts, on the next tick when it is already aborted.
'use strict';
const { createInterface } = require('node:readline/promises');
const { PassThrough, Readable } = require('node:stream');
async function main() {
const out = new PassThrough();
out.on('data', (c) => console.log('output:', JSON.stringify(c.toString())));
const rl = createInterface({ input: new Readable({ read() {} }), output: out });
const ac = new AbortController();
const pending = rl.question('name? ', { signal: ac.signal });
ac.abort();
try {
await pending;
} catch (e) {
console.log(e.name, e.code, e.message, e.cause instanceof Error, e.cause.name);
}
const pre = new AbortController();
pre.abort('why');
try {
await rl.question('again? ', { signal: pre.signal });
} catch (e) {
console.log('pre-aborted:', e.name, e.cause);
}
const answered = new AbortController();
rl.write('');
const later = rl.question('third? ', { signal: answered.signal });
rl.write('yes\n');
console.log('answered:', await later);
answered.abort();
const closer = new AbortController();
const rl2 = createInterface({ input: new Readable({ read() {} }), signal: closer.signal });
rl2.on('close', () => console.log('rl2 closed by abort'));
closer.abort();
const early = new AbortController();
early.abort();
const rl3 = createInterface({ input: new Readable({ read() {} }), signal: early.signal });
rl3.on('close', () => console.log('rl3 closed on the next tick'));
console.log('rl3 created');
rl.close();
}
main();
+37
View File
@@ -0,0 +1,37 @@
// node:readline/promises over process.stdin: the program re-runs itself
// with piped input and prints the child's output. The first line answers
// the question (its prompt goes to stdout), the 'line' listener receives
// the rest, the trailing line without a newline included, and 'close'
// follows the end of input. A second child reads every line with for
// await, and a callback interface reports \r\n-terminated lines.
import { execFileSync } from "node:child_process";
import { createInterface } from "node:readline/promises";
import * as readlineCb from "node:readline";
const mode = process.argv[2];
if (mode === "question") {
const rl = createInterface({ input: process.stdin, output: process.stdout });
const answer = rl.question("name? ");
const rest: string[] = [];
rl.on("line", (line: string) => rest.push(line));
rl.on("close", () => console.log("closed after", rest.join(",")));
console.log(`\nhello ${await answer}`);
} else if (mode === "lines") {
const rl = createInterface({ input: process.stdin });
let n = 0;
for await (const line of rl) console.log(++n, line);
console.log("done");
} else if (mode === "callback") {
const rl = readlineCb.createInterface({ input: process.stdin });
rl.on("line", (line: string) => console.log("line:", line));
rl.on("close", () => console.log("closed"));
} else {
for (const [child, input] of [
["question", "Ada\nblue\nx\nz"],
["lines", "a\n\nb\r\nc"],
["callback", "first\r\nsecond\nthird"],
]) {
const stdout = execFileSync(process.execPath, [process.argv[1]!, child!], { input, encoding: "utf8" });
console.log(child, JSON.stringify(stdout));
}
}
@@ -0,0 +1,31 @@
// stream.addAbortSignal and the constructors' signal option: an abort
// destroys the stream with an AbortError (no unhandled error: the
// listener eos registers handles it), an already aborted signal destroys
// it during construction, the subclass super({ signal }) form applies it
// too, and an abort after the stream finished changes nothing.
import { addAbortSignal, Readable, Writable } from "node:stream";
const ac = new AbortController();
const r = addAbortSignal(ac.signal, new Readable({ read() {} }));
r.on("close", () => console.log("close", r.destroyed, r.errored?.name));
ac.abort();
console.log("after abort", r.destroyed);
const pre = new AbortController();
pre.abort("x");
const signal = pre.signal;
const w = new Writable({ write(_c, _e, cb) { cb(); }, signal });
console.log("pre", w.destroyed, w.errored?.name);
w.on("error", (e: Error) => console.log("w error", e.name, e.message));
w.on("close", () => console.log("w close"));
const ok = new Readable({ read() { this.push("a"); this.push(null); } });
const ac2 = new AbortController();
addAbortSignal(ac2.signal, ok).resume();
ok.on("close", () => { ac2.abort(); console.log("ok close, abort after finish", ok.errored); });
class Sub extends Readable {
constructor(s: AbortSignal) { super({ signal: s }); }
_read() {}
}
const ac3 = new AbortController();
const sub = new Sub(ac3.signal);
sub.on("error", (e: Error) => console.log("sub error", e.name));
ac3.abort();
@@ -0,0 +1,16 @@
// addAbortSignal's argument checks from CommonJS: Node's
// ERR_INVALID_ARG_TYPE messages for values that are not AbortSignals or
// streams (null fails the in-operator check), a falsy signal option is
// ignored, and the AbortError carries the abort reason as its cause.
const { addAbortSignal, Readable } = require("node:stream");
const r = new Readable({ read() {} });
for (const bad of [{}, null, 1, "x", undefined]) {
try { addAbortSignal(bad, r); console.log("accepted", bad); } catch (e) { console.log(e.code, e.message); }
}
try { addAbortSignal(new AbortController().signal, {}); } catch (e) { console.log(e.code, e.message); }
const ac = new AbortController();
ac.abort(new Error("reason here"));
const s = new Readable({ read() {}, signal: ac.signal });
s.on("error", (e) => console.log("error", e.name, e.cause.message, e.code));
const t = new Readable({ read() {}, signal: false });
console.log("falsy signal ignored", t.destroyed);
@@ -0,0 +1,87 @@
// stream/promises options: pipeline(..., { signal }) destroys every stage
// with an AbortError in pipeline order and rejects after the last close
// (an already aborted signal too), and { end: false } settles when the
// source is done while the destination stays open. finished(stream,
// options): readable/writable choose which end counts, error: false
// waits for close, signal rejects without destroying the stream, and
// cleanup removes the listeners. The callback form takes the options.
import { Duplex, PassThrough, Readable, Writable, finished as finishedCb } from "node:stream";
import { finished, pipeline } from "node:stream/promises";
{
const ac = new AbortController();
const src = new Readable({ read() {} });
const mid = new PassThrough();
const dst = new Writable({ write(c, _e, cb) { console.log("write", c.toString()); cb(); } });
src.on("error", (e: Error) => console.log("src error", e.name));
src.on("close", () => console.log("src close"));
mid.on("error", (e: Error) => console.log("mid error", e.name));
mid.on("close", () => console.log("mid close"));
dst.on("error", (e: Error) => console.log("dst error", e.name));
dst.on("close", () => console.log("dst close"));
const p = pipeline(src, mid, dst, { signal: ac.signal });
src.push("a");
setTimeout(() => ac.abort(), 5);
try { await p; } catch (e) { console.log("rejected", (e as Error).name); }
}
{
const ac = new AbortController();
ac.abort();
const src = Readable.from(["x"]);
const dst = new Writable({ write(_c, _e, cb) { cb(); } });
try { await pipeline(src, dst, { signal: ac.signal }); } catch (e) { console.log("pre", (e as Error).name, src.destroyed, dst.destroyed); }
}
{
const chunks: string[] = [];
const dst = new Writable({ write(c, _e, cb) { chunks.push(c.toString()); cb(); } });
dst.on("finish", () => console.log("dst finish"));
await pipeline(Readable.from(["1", "2"]), dst, { end: false });
console.log("resolved end:false", chunks.join(""), dst.writableEnded, dst.destroyed);
dst.end("3");
await new Promise<void>((r) => setTimeout(r, 5));
console.log("after manual end", chunks.join(""));
}
console.log("done");
{
const d = new Duplex({ read() {}, write(_c, _e, cb) { cb(); } });
d.on("finish", () => console.log("d finish"));
d.on("close", () => console.log("d close"));
const p = finished(d, { readable: false }).then(() => console.log("d settled (readable: false)"));
d.end("x");
await p;
}
{
const d = new PassThrough();
d.on("end", () => console.log("pt end"));
const p = finished(d, { writable: false }).then(() => console.log("pt settled (writable: false)"));
d.resume();
d.end("y");
await p;
}
{
const r = new Readable({ read() {} });
r.on("error", (e: Error) => console.log("r error event", e.message));
r.on("close", () => console.log("r close"));
const p = finished(r, { error: false }).catch((e) => console.log("r rejected", (e as Error).message));
r.destroy(new Error("boom"));
await p;
}
{
const ac = new AbortController();
const r = new Readable({ read() {} });
const p = finished(r, { signal: ac.signal }).catch((e) => console.log("aborted", (e as Error).name, r.destroyed));
ac.abort();
await p;
}
{
const r = Readable.from(["a"]);
r.resume();
await finished(r, { cleanup: true });
console.log("cleanup listeners", r.listenerCount("end"), r.listenerCount("close"));
}
{
const w = new Writable({ write(_c, _e, cb) { cb(); } });
finishedCb(w, { writable: true }, (err?: Error | null) => console.log("callback form", err));
w.end("z");
}
@@ -0,0 +1,35 @@
// finished(stream, options, callback) from CommonJS: the callback runs
// once with the error or no argument, `this` unused; readable: false on a
// Duplex settles at 'finish'; error: false leaves the 'error' event to the
// stream's own listeners and reports at close; the returned cleanup
// function removes the listeners so the callback never runs; signal
// aborts the wait with an AbortError; a non-signal value throws
// ERR_INVALID_ARG_TYPE.
'use strict';
const { Duplex, Readable, finished } = require('node:stream');
const d = new Duplex({ read() {}, write(c, e, cb) { cb(); } });
d.on('finish', () => console.log('finish'));
finished(d, { readable: false }, (err) => console.log('duplex settled', err));
d.end('x');
const r = new Readable({ read() {} });
r.on('error', (e) => console.log('error event', e.message));
finished(r, { error: false }, (err) => console.log('error:false settled', err.message));
r.destroy(new Error('bad'));
const quiet = new Readable({ read() {} });
const cleanup = finished(quiet, null, (err) => console.log('never', err));
cleanup();
quiet.destroy();
const ac = new AbortController();
const waiting = new Readable({ read() {} });
finished(waiting, { signal: ac.signal }, (err) => console.log('aborted', err.name, err.cause.name, waiting.destroyed));
ac.abort();
try {
finished(new Readable({ read() {} }), { signal: {} }, () => {});
} catch (e) {
console.log(e.code, e.message);
}
+2 -2
View File
@@ -46,9 +46,9 @@ fs.watch("/tmp", { persistent: false }, () => {});
dns.lookup("localhost", { family: 4, hints: 32 }, () => {});
dns.lookup("localhost", { family: 4, all: true }, () => {});
// readline: a finite crlfDelay is not the lowered splitter; completer
// readline: history only exists for terminal interfaces; completer
// needs an interactive terminal.
readline.createInterface({ input: process.stdin, output: process.stdout, crlfDelay: 100 });
readline.createInterface({ input: process.stdin, output: process.stdout, historySize: 10 });
readline.createInterface({ input: process.stdin, output: process.stdout, completer: (l: string) => [[l], l] });
// A bare __proto__: value entry is not an ordinary undocumented key: it
+18
View File
@@ -0,0 +1,18 @@
// readline/promises under @types/node: the Interface over process.stdin,
// a question answered through write() with the prompt on stdout, 'line'
// for text with no pending question, close(), and the rejection of a
// question asked after close.
import { createInterface } from "node:readline/promises";
const rl = createInterface({ input: process.stdin, output: process.stdout });
rl.on("line", (line: string) => console.log("line:", line));
rl.on("close", () => console.log("closed"));
const answer = rl.question("name? ");
rl.write("Ada\nextra\n");
console.log("\nname:", await answer);
rl.close();
try {
await rl.question("again? ");
} catch (err) {
console.log("after close:", (err as Error & { code?: string }).code);
}
@@ -106,18 +106,18 @@ options-record.ts:47:38 - error SC2020: 'lookup option 'all'' is part of the sta
hint: the all-addresses callback shape has no lowering — the lowered callback is (err, address, family) over one IPv4 answer
options-record.ts:51:85 - error SC2020: 'createInterface with a finite crlfDelay' is part of the standard library types but has no scriptc lowering yet
options-record.ts:51:74 - error SC2020: 'createInterface option 'historySize'' is part of the standard library types but has no scriptc lowering yet
50 | // needs an interactive terminal.
51 | readline.createInterface({ input: process.stdin, output: process.stdout, crlfDelay: 100 });
| ^~~
51 | readline.createInterface({ input: process.stdin, output: process.stdout, historySize: 10 });
| ^~~~~~~~~~~~~~~
52 | readline.createInterface({ input: process.stdin, output: process.stdout, completer: (l: string) => [[l], l] });
hint: the lowered splitter always joins \r\n across chunks (Node's crlfDelay: Infinity) — Infinity is the accepted value
hint: input, output, terminal: false, crlfDelay, prompt and signal are the supported options
options-record.ts:52:74 - error SC2020: 'createInterface with a completer' is part of the standard library types but has no scriptc lowering yet
51 | readline.createInterface({ input: process.stdin, output: process.stdout, crlfDelay: 100 });
51 | readline.createInterface({ input: process.stdin, output: process.stdout, historySize: 10 });
52 | readline.createInterface({ input: process.stdin, output: process.stdout, completer: (l: string) => [[l], l] });
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
53 |
+18
View File
@@ -126,6 +126,24 @@ test("node-types: util.styleText supports stored calls and standard streams unde
expect(comparableStderr(native.stderr)).toBe(node.stderr);
});
test("node-types: readline/promises over stdin compiles under @types/node", async () => {
const outDir = outDirFor("readline-promises");
const entry = join(nodeTypesDir, "readline-promises.ts");
const result = await compile(entry, {
outPath: join(outDir, "readline-promises"),
outDir,
sanitize,
});
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
if (!result.ok) return;
const [native, node] = await Promise.all([
execFileAsync(result.binaryPath),
execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]),
]);
expect(native.stdout).toBe(node.stdout);
expect(comparableStderr(native.stderr)).toBe(node.stderr);
});
function outDirFor(name: string): string {
return mkdtempSync(join(tmpdir(), `scriptc-config-${name}-`));
}