Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/per_isolate/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ at context creation, before any user code. Gated by the
acyclic (e.g., `webstreams/native` is a deliberate leaf).
- TypeScript `import type` / `export type` are used freely for type
plumbing (fully erased; the loader only sees `module.exports`).
- `main.ts` installs the (TEMPORARY, dev-only) lazy `globalThis.streams`
surface.
- `main.ts` installs the real stream globals (ReadableStream et al.) when
the `typescript_implemented_streams` compat flag is enabled, plus the
internal-testing `ReadableStreamDrainingReader` under
`expose_draining_reader`.
- Files are auto-discovered (`BUILD.bazel` and `tsconfig.json` both glob
`**/*.ts`). No registration needed for new files.
- Local convention: no copyright headers in this directory's bootstrap
Expand Down
16 changes: 10 additions & 6 deletions src/per_isolate/webstreams/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ private-brand dispatch, no `instanceof`) apply here — see
| `writable.ts` / `transform.ts` / `strategies.ts` | WHATWG writable/transform/strategies |
| `identity.ts` | IdentityTransformStream and FixedLengthStream (byte-capable identity transforms) |
| `encoding.ts` | TextEncoderStream and TextDecoderStream (pure JS codec transforms) |
| `streams.ts` | Module aggregator and temporary native-source exports |
| `streams.ts` | Module aggregator (user-visible classes + the flag-gated DrainingReader) |
| `types.d.ts` | TypeScript type definitions for the streams API |

## KEY RULES
Expand All @@ -39,11 +39,15 @@ private-brand dispatch, no `instanceof`) apply here — see
argument — the source checks `signal.aborted` before delivery and stashes
bytes for redelivery if aborted (race buffering lives source-side; the JS
conduit is uniformly bufferless).
- `kNativeSource` is TEMPORARILY re-exported via `streams.ts` for tests;
the real C++ handshake has landed, so this removal is now due
(follow-up; requires migrating the JS-mock tests off it).
- `nativeStreamInternals` (markers, extraction symbols, conduit
construction) is module-private, consumed only by readable.ts/writable.ts
and the C++ bridge via the API-symbol registry. The C++ mocks in
`js-readable-stream-test.c++` construct real `ReadableStreamNativeSource`
objects; no JS-visible marker export exists.

## ANTI-PATTERNS

- **NEVER** expose internals on user-visible exports (the temporary
`kNativeSource` exception is tracked for removal).
- **NEVER** expose internals on user-visible exports. `streams.ts` exports
exactly the user-visible classes plus `ReadableStreamDrainingReader`,
which `main.ts` installs only under the internal-testing
`expose_draining_reader` flag.
46 changes: 31 additions & 15 deletions src/per_isolate/webstreams/native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,7 @@
// settles without delivering, closing, or erroring while requests
// are still pending errors the stream. Aborted pulls are exempt.
//
// Nothing in this module is ever exposed to user code, with one TEMPORARY
// exception: streams.ts re-exports kNativeSource so mock native sources
// can be constructed from tests before the real C++ integration exists.
// Nothing in this module is ever exposed to user code.

import type {
ByteStreamConsumer as ByteStreamConsumerType,
Expand Down Expand Up @@ -158,8 +156,13 @@ const kNativeSource: symbol = utils.getApiSymbol('kNativeSource');
// native sinks need no new backend machinery — the standard
// WritableStream drives them via start/write/close/abort. The marker
// exists for pipe dispatch (is the dest native?) and extraction (hand
// the sink to C++ for the native+native fast path). The one extension:
// pipeFrom(source, options), the hook for the native+native pipe.
// the sink to C++ for the native+native fast path). Two extension hooks
// beyond the standard sink surface:
// - pipeFrom(source, options): the native+native pipe fast path,
// called by the pipe dispatch when both ends are native-backed.
// - detach(): called by detachWritableStream just before it drops its
// reference, releasing the C++ sink immediately when the underlying
// connection is taken over (e.g. Socket startTls).
const kNativeSink: symbol = utils.getApiSymbol('kNativeSink');

// Extraction marker for native-backed WritableStream instances. Mirrors
Expand All @@ -186,9 +189,9 @@ function isNativeUnderlyingSink(sink: object): boolean {
// stream: present -> extract the native source for a pure C++ data
// path; absent -> acquire a DrainingReader instead. The marker is KEPT
// after extraction (presence means "native-backed", not "extractable");
// the extractor's locked/disturbed preconditions make it one-shot.
// Bootstrap phase: regular symbol (temporarily exposed via streams.ts).
// Final implementation: runtime-provided private API symbol.
// the extractor's locked/disturbed preconditions make it one-shot. The
// symbol comes from the runtime's API-symbol registry, unreachable from
// user code.
const kExtractNativeSource: symbol = utils.getApiSymbol('kExtractNativeSource');

function isActualObject(value: unknown): value is object {
Expand All @@ -200,10 +203,10 @@ function isActualObject(value: unknown): value is object {
// present at all, this is a contract violation on the native/mock side,
// never a user-input condition.
//
// Hardening: OWN-property read via descriptor — a (temporarily exposed)
// symbol planted on Object.prototype must not convert every plain source
// into a native one, and a hostile accessor at the marker is never
// invoked (we read desc.value; accessor-defined markers are ignored).
// Hardening: OWN-property read via descriptor — a marker symbol planted
// on Object.prototype must not convert every plain source into a native
// one, and a hostile accessor at the marker is never invoked (we read
// desc.value; accessor-defined markers are ignored).
function isNativeUnderlyingSource(source: object): boolean {
const desc = ObjectGetOwnPropertyDescriptor(source, kNativeSource) as
PropertyDescriptor | undefined;
Expand Down Expand Up @@ -1192,6 +1195,14 @@ class NativePullConduit implements ByteStreamConsumerType {
this.#byobRequestCache = null;
return source;
}

// Non-detaching source access for read-only queries from the C++ bridge
// (the encoding-aware tryGetLength arm). The conduit's own status is
// deliberately not consulted: the C++ source object answers from its own
// lifecycle (a completed or extracted source reports no length).
peekSource(): object {
return this.#source;
}
}

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -1345,9 +1356,13 @@ function nativeControllerExpectedLength(
return getControllerConduit(controller).expectedLength;
}

// Internal namespace, never re-exported to users (kNativeSource and
// kExtractNativeSource excepted, TEMPORARILY, via streams.ts — see the
// module header).
function nativeControllerPeekSource(
controller: NativeReadableStreamController
): object {
return getControllerConduit(controller).peekSource();
}

// Internal namespace, never re-exported to users.
const nativeStreamInternals = {
kNativeSource,
kExtractNativeSource,
Expand All @@ -1364,6 +1379,7 @@ const nativeStreamInternals = {
nativeControllerTeeSource,
nativeControllerExtractSource,
nativeControllerExpectedLength,
nativeControllerPeekSource,
};

// Type-only exports (fully erased at runtime — the loader sees only the
Expand Down
Loading
Loading