Commit Graph
277 Commits
Author SHA1 Message Date
codingetandClaude dfa4ab69cf feat(http): add sendResponse option to server hijack
hijack({ sendResponse: false }) flushes any pending write-buffer bytes
without sending the response headers/body. This is intended for use
with the upcoming 1xx support: a handler can send a 101 Switching
Protocols as an interim response via the 1xx mechanism, then call
hijack({ sendResponse: false }) to take ownership of the transport
without double-sending the response.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 15:34:24 +00:00
codingetandClaude f905206441 docs: update AI disclosure for hijack() implementation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 10:52:42 +00:00
codingetandClaude a70ba05ce3 feat(http): add hijack() to server and client responses for HTTP upgrades
ServerResponse.hijack() sends the current response then returns the
underlying RawTransport so handlers can switch to a different protocol
(WebSocket, raw TCP, etc.) without the server loop taking over again.

ClientResponse.hijack() returns the raw transport after a 101 response
so the caller can take over the connection for the new protocol.

Both variants prepend any bytes the ReadBuffer had already read ahead,
ensuring no data is lost. The server's handle() loop exits cleanly on
hijack and leaves the transport open. The pool integration in fetch.ts
rejects hijacked connections rather than returning them to the pool.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 10:52:26 +00:00
codingetandClaude f0ec13e9b7 docs(agents): keep PR description up to date on each push
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 23:40:21 +00:00
codingetandClaude 43a3345549 docs(agents): add coding style and dependency guidelines
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 23:33:38 +00:00
codingetandClaude 1014073090 docs: add AGENTS.md with agent workflow instructions
Covers: opening PRs with tea and the WIP: prefix convention, and
updating the README AI disclosure at the end of every branch.
CLAUDE.md is symlinked to AGENTS.md for Claude Code compatibility.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 23:25:15 +00:00
codingetandClaude 1b92611f7e docs: accurately reflect Claude Code contributions to packages/http
The previous disclosure significantly understated Claude Code's
involvement. It contributed the package scaffolding, a major transport
layer review and bugfix pass, the timeout feature, parse error message
improvements, and the full test suite — not just the initial empty
package and light spec guidance.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 23:10:43 +00:00
codingetandClaude 67994a2d4c docs: update AI disclosure — http test suite and bug fixes
The packages/http test suite was mostly generated by Claude Code, which
also surfaced and fixed three bugs during that work.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 23:04:27 +00:00
codingetandClaude 264c0adb65 refactor(http): remove dead try/catch in PooledDialer.shutdown()
The slot waiters and global waiters are Promise resolve/reject callbacks.
Promise callbacks cannot throw, so the try/catch around them was dead
code that also required c8 ignore annotations to suppress coverage gaps.
Removed both wrappers and moved the errors array to where it's actually
needed (connection close errors).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 22:54:24 +00:00
codingetandClaude 622707e7b2 chore(http): remove RUN_UNIT / skipIfNotUnit — unit tests always run
Excluding unit tests via an env flag doesn't make sense; the flag only
created confusion. RUN_INTEGRATION=false (via test:unit) is the only
useful gate. Removed the export from flags.ts and the { skip } options
from all unit test suites.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 22:54:00 +00:00
codingetandClaude 02b50c9fd0 fix(http): restore TypeError for connection parameter guards in pool
These are API-boundary parameter checks, not internal invariant
assertions. TypeError is the correct error type and the original message
communicates the problem clearly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 22:53:09 +00:00
codingetandClaude 570972b304 fix(http): adjust c8 ignore counts after prettier reformatted pool.ts
Prettier expanded two compact try/catch blocks from 1 line to 5 lines
each, and split an if/throw across two lines — bumping next 3 → next 5
for the shutdown() catch bodies and next → next 2 for the setTimeout
invariant guard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 19:55:40 +00:00
codinget fbb65c5d95 test(http): add lcov reporter 2026-05-28 19:52:25 +00:00
codingetandClaude abbaae44a4 test(http): 100% branch coverage — new tests and c8 ignore annotations
New tests:
- UnpooledDialer: exercise dialTls success path
- PooledDialer.rejectConnection: cover timeout.has branch by rejecting a
  previously-released connection; also fixes a bug where the connection
  was not removed from slot.idle before decrementing slot.alive
- PooledDialer.shutdown: cover the global-waiter-races-shutdown path
  (releaseConnection fires ok() then shutdown sets #shutdown before the
  microtask continuation runs)

Broken Invariant annotations:
- Standardise all invariant error messages to "Broken Invariant: ..."
- Add /* c8 ignore next */ to guards that TypeScript enforces on callers
  or that cannot fire under correct internal state:
  pool.ts: !connection checks, alive<0, idle>alive, not-in-idle-after-
    setTimeout, catch blocks around Promise resolvers, allSettled error
    collection, #notify's !slot guard
  buffer.ts: slice-returned-fewer-bytes guard
  node/transport.ts: string-chunk guard, closed-with-buffers branch,
    socket write error callback

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 19:34:29 +00:00
codingetandClaude 9eee7f51f2 chore: ignore coverage/ directories
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 19:08:04 +00:00
codingetandClaude bb2ae3c0b4 chore(http): deduplicate :unit scripts by delegating to base scripts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 18:40:33 +00:00
codingetandClaude 1f9ffc106b test(http): push branch coverage higher by targeting remaining uncovered paths
Cover previously missing branches:
- common/pair.ts: PairSync.close() idempotence and getA()-after-close
- common/reader.ts: _triggerFinishOk/Ko catch blocks (handler throws),
  BasicBodyReader #chunks pre-buffer path (slice returns multiple arrays),
  ChunkedBodyReader already-closed guard and truncated-chunk sanity check
- common/buffer.ts: WriteBuffer.flush() partial write of a second buffer
- client/objects.ts: method defaults to POST when body is truthy
- client/fetch.ts: rejectConnection called when conn.request() throws
- node/transport.ts: fast-fail read() path with null #readError (socket
  destroyed without error event)
- server/connection.ts: console.warn for missing requireHeaders entry,
  non-TimeoutError parse error propagates out of handle()

Result: 100% functions, 97.81% branches — remaining gaps are dead code
(defensive throws/catches that cannot fire under normal API usage).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 17:38:38 +00:00
codingetandClaude b14ca4fffa test(http): comprehensive test suite — 100% function coverage, bug fixes
Adds full test suites for all implementation files and fixes two transport bugs
discovered during testing:

- fix(loopback): `#maybeEnded` checked `this.closed` (requiring BOTH ends closed)
  instead of just `#readClosed`; a pending read would hang after the peer closed if
  the local write side was still open. Also add fast-fail in `read()` when
  `#readClosed` is already set.
- fix(node): same `#maybeEnded` bug as loopback. `close()` used `socket.end()`
  (FIN — waits for peer FIN) instead of `socket.destroy()` (RST — immediate), which
  caused the test suite to hang. Add `#readError` field so errors seen before
  `read()` is called are preserved for the fast-fail path.

New/extended test files:
- src/client/fetch.test.ts — fetch() and makeFetch() integration suite
- src/client/pool.test.ts — UnpooledDialer and PooledDialer (incl. TLS throw paths)
- src/client/connection.test.ts — extended with more parse/request edge cases
- src/loopback/transport.test.ts — loopback pair, listener, halfClose, error paths
- src/node/transport.test.ts — NodeDialer, NodeTransport, NodeListener (incl.
  buffered-data path, server error event, dialTls ECONNREFUSED)
- src/server/objects.test.ts — ServerRequestImpl, ServerResponseImpl
- src/server/router.test.ts — extended with optional-param and global middleware

Coverage result: 100% functions, ~99.3% lines, ~97.1% branches (remaining branch
gaps are V8/tsx source-map desync artifacts, not missing test paths).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 00:14:09 +00:00
codingetandClaude 185a528971 test(http): fix audit findings — wrong assertions, async traps, and missing edge cases
- Fix vacuous `!clientTransport.closed || conn.ended` → `serverTransport.readEnded`
- Move HTTP/1.0 success-path tests out of the parse error suite
- Fix fire-and-forget `.then()` in loopback whenClosed test (now awaited)
- Add CRLF split across chunk boundary, 3-buffer slice, multi-buffer forward tests
- Add negative chunk size and post-close onFinish handler tests for readers
- Add non-null body with bodyAllowed=false writer test
- Add maxTargetLength boundary, HEAD wire-level, and body auto-drain server tests
- Add global middleware on 404/405 and HEAD-in-Allow router tests
- Add setOptions effect and 1xx-todo client connection tests
- Add TLS path and multiple concurrent global waiters pool tests
- Add new src/client/utils.test.ts covering combineClientConnectionOptions

One test marked { todo }: 1xx interim responses not yet implemented.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 01:04:18 +00:00
codingetandClaude 27cc807796 chore(http): exclude test-helpers from build and coverage
Add src/test-helpers to tsconfig.json exclude so flags.ts is not
compiled into dist. Add a second --test-coverage-exclude glob to both
coverage scripts so the helper does not appear in the coverage report
or skew aggregate numbers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 17:23:14 +00:00
codingetandClaude 9129206916 test(http): default to unit+integration, add :unit script variants
Change RUN_INTEGRATION default to true so npm test / test:coverage /
test:watch run the full 284-test suite (unit + integration) by default.
RUN_EXTERNAL remains independent and must be set explicitly.

Add test:unit, test:coverage:unit and test:watch:unit convenience scripts
(RUN_INTEGRATION=false) for when only the 226 unit tests are wanted.
Remove test:all, which is now redundant with the new defaults.

Default coverage: 90.9% lines / 92.8% branches / 94.1% functions.
Unit-only coverage: 77.5% lines / 94.8% branches / 89.6% functions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 16:34:52 +00:00
codingetandClaude 45667ea9d2 test(http): classify tests as unit/integration/external with env-var gates
Add src/test-helpers/flags.ts (RUN_UNIT, RUN_INTEGRATION, RUN_EXTERNAL)
and apply { skip } to every suite accordingly:

  unit        — common/*, loopback/transport, server/router,
                server/connection parse sub-suite
  integration — client/connection, client/pool,
                server/connection send/handle sub-suites
  external    — node/fetch (hits real internet)

Default (no env vars): RUN_UNIT=true, others false — npm test, test:watch
and test:coverage run only the 226 unit tests. Add test:all convenience
script (RUN_INTEGRATION=true) to run the 284 unit+integration tests.
RUN_EXTERNAL=true also enables RUN_INTEGRATION implicitly.

Unit-only coverage: 77.5% lines / 94.6% branches / 89.6% functions.
Full (unit+integration) coverage: 96.4% lines / 92.4% branches / 95.3%
functions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 12:40:37 +00:00
codingetandClaude d026bea748 chore(http): add test:coverage script
Uses Node's built-in --experimental-test-coverage with tsx as an import
hook — no extra dependencies required.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 11:36:50 +00:00
codingetandClaude 530835607a test(http): add comprehensive unit test suite (296 tests)
Covers common/{buffer,connection,headers,objects,reader,utils,writer},
loopback/transport, server/{connection,router}, and client/{connection,pool}.
All tests use loopbackTransportPair for in-memory transport — no network
access required. Extends the existing server/connection.test.ts with
send() and handle() suites.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 11:26:38 +00:00
codingetandClaude 075ed71505 fix(http): maxBodyLength error swallowed on final body chunk
When the last chunk pushed bodyLength over the limit, the error was
thrown inside the try/catch and then caught — but because body.closed
was already true at that point, the catch block hit `continue` and
discarded the error silently.

Move the maxBodyLength check (and the yield) outside the try/catch so
only the awaited read is guarded, not the enforcement logic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 11:24:52 +00:00
codinget 4eee5f88fa fix(http): broken exports 2026-05-20 11:14:10 +00:00
codinget 7920a6db96 chore(example-app): import from @webnet/http/server to more clearly state intent 2026-05-19 23:47:09 +00:00
codinget bda28b3cef refactor(http): split exports into internal and external groups + root barrels for the core features 2026-05-19 23:46:37 +00:00
codingetandClaude 2d47f4862b feat(http): add headersTimeout, keepAliveTimeout and bodyTimeout support (#9)
Reviewed-on: #9
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Codinget <codinget@codi.moe>
Co-committed-by: Codinget <codinget@codi.moe>
2026-05-19 23:58:27 +02:00
codinget 3c9f21b6ee feat(http): add size limits to http connections and listen lifecycle hooks (#8)
Reviewed-on: #8
Co-authored-by: Codinget <codinget@codi.moe>
Co-committed-by: Codinget <codinget@codi.moe>
2026-05-19 22:50:34 +02:00
codingetandClaude d4b448071e fix(http): include offending values in parse error messages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 18:17:36 +00:00
codingetandClaude 240a7b8929 fix(http): review and improve the low-level transport layer
Bugs:
- ReadableHttpImpl.bytes() was copying data byte-by-byte; use set()
- NodeTransport and LoopbackTransportHalf were not clearing #errcallback
  after a successful read(), causing errors between reads to be silently
  swallowed; also gate #maybeEnded to only fire when a read() is pending
- ServerConnection.handle() was draining the request body after closing
  the connection; skip the drain entirely on close (it only matters for
  keep-alive reuse), and close after draining otherwise
- PooledDialer threw "Pool full" / "Per-origin full" instead of queuing
  waiters; implement queuing via slot.waiting and a new #globalWaiting
  list; also fix #shutdown never being set to true in shutdown(), and
  reserve the slot before the async connect to prevent races

Code quality:
- shouldClose() was comparing Connection header values case-sensitively;
  RFC 7230 requires case-insensitive comparison
- Headers._normalized was public (convention only); make it protected
- Headers.headers returned the original wire-case record while
  MutableHeaders.headers returned lowercase keys; unify both to return
  from _normalized so ServerRequest.headers and ServerResponse.headers
  are consistent (both lowercase)

Enhancements:
- RawTransport: add optional halfClose(), readEnded, whenClosed,
  remoteAddr, localAddr; NodeTransport and LoopbackTransportHalf
  implement all applicable members; addresses cached at construction
  before the socket handle can be invalidated
- RawListener: add optional addr; NodeListener exposes the bound
  address (critical when listening on port 0)
- LoopbackTransportHalf: internalize _closeFn/_writeFn as constructor
  parameters stored in private fields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 23:20:37 +00:00
codinget 56461cdd32 feat(http): basic support for the client 2026-05-11 22:57:18 +02:00
codinget bb0ce0c58e refactor(http): split chunked body writing out of Connection 2026-05-11 22:57:18 +02:00
codinget 4e17939891 refactor(http): split read/write buffer and body readers out of Connection 2026-05-11 22:57:18 +02:00
codinget 2b7c14c790 chore: avoid ambiguous characters 2026-05-11 20:54:56 +00:00
codingetandClaude e2d5017ec2 fix(query-utils): correct ICMP case in ping type error message
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 15:29:34 +00:00
codingetandClaude 2a8203f403 fix(query-utils): ping type validation, DNS fallback, whoIs bare-IP support
- ping: invalid type strings now reject immediately with an error
- queryDNS: SERVFAIL + no upstreams falls back to dialer then browser DNS
- whoIs: accepts bare IP addresses in addition to ip:port
- types: rename addrPort to addr in whoIs; update JSDoc

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 15:22:12 +00:00
codinget 7c67504fb6 feat(example-app): add SuggestExitNodeDebug 2026-05-10 14:53:02 +00:00
codingetandClaude 89c64e8c69 feat(tsconnect): add peerAPIURL to netmap types and localAPI wrapper
Add peerAPIURL field to IPNNetMapNode (shared by self and all peers).
Add localAPI(method, path, body?) method to IPN class and interface,
returning {status, body}. Bump tailscale submodule.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 01:23:25 +00:00
codingetandClaude 231b474d9c feat(tsconnect): add whoIs, queryDNS, ping, suggestExitNode TS wrappers
Add TypeScript types (WhoIsResponse, PingType, PingResult, DNSQueryResult,
ExitNodeSuggestion) and validated IPN class methods for the four new Go
bindings. Bump tailscale submodule.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 22:07:08 +00:00
codingetandClaude 2516be05d7 feat: add Funnel/HTTPS support (getCert, listenTLS, setFunnel)
Add TLSCertKeyPair type and TypeScript wrappers for getCert, listenTLS, and
setFunnel. Add Http example component that provisions a TLS cert, opens HTTP
and HTTPS listeners, serves "Hewwo, world!", and enables Tailscale Funnel on
port 443. Bump tailscale submodule to include the matching Go changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 11:19:59 +00:00
codinget 91e1b6afe8 feat(example-app): add caCerts and tailscale fetch 2026-04-18 23:44:53 +00:00
codinget ac658029a3 fix(example-app): add ts declarations 2026-04-18 23:42:33 +00:00
codingetandClaude 0665ff0acd feat: add turborepo for dependency-aware parallel builds
Replaces `npm run build --workspaces --if-present` with `turbo run build`,
which resolves build order from each package's package.json dependencies
and parallelises independent packages automatically.

- turbo.json: declares build task with `^build` topological dependency
- package.json: switches build script to turbo, adds packageManager field
  (required by turbo v2), adds turbo to devDependencies
- .gitignore: add .turbo/ cache directory
- tailscale: bump submodule for tsconfig types-pinning fix

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 20:15:43 +00:00
codingetandClaude da558c708b fix(tsconnect): bump submodule for skipLibCheck fix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 19:52:37 +00:00
codinget 5d28032aa6 feat(example-app): login to tailscale, show peers, set exit node, use taildrop 2026-04-18 19:39:05 +00:00
codinget 1237206ec2 feat(tsconnect): add redux and react bindings 2026-04-18 19:37:46 +00:00
codinget 79e17bd2e3 feat(tsconnect): add boolean fileOps flag to IPN to signal availability 2026-04-18 19:37:03 +00:00
codingetandClaude fc3d134ee0 feat(example-app): bind dev server on all interfaces
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 19:52:50 +00:00