Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,12 +190,12 @@ These are intentional. The header of
identity, so `removeListener(name, fn)` on req/res cannot match a specific
listener — use `removeAllListeners(name)`.
- Request `set-cookie` duplicates join with `", "` instead of becoming an
array (`req.headers` stays a string→string map); `getHeader` returns
multi-value headers joined with `", "`.
array (a `req.headers` value is a string or `undefined`, never an array);
`getHeader` returns multi-value headers joined with `", "`.
- `writeHead(status, statusMessage, headers)` 3-arg form is unsupported — set
`res.statusMessage` instead.
- Bodies are byte-preserving strings, not Buffers; chunk trailers are parsed
and discarded; `res.req` is not provided.
and discarded.
- `node:cluster` ([runtime/node/cluster.ts](runtime/node/cluster.ts)) forks
worker processes (a re-exec of the binary; `isPrimary`/`isWorker`,
`fork(env)`, `worker.id/process.pid`, `kill`/`disconnect`, `'online'`,
Expand Down
7 changes: 6 additions & 1 deletion apps/hono-bridge/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ app.post('/body/text', async (c) => {

const server = createServer(async (req, res) => {
const method = req.method ?? 'GET'
const init: RequestInit = { method, headers: req.headers }
const headers: Record<string, string> = {}
for (const name in req.headers) {
const value = req.headers[name]
if (value !== undefined) headers[name] = value
}
const init: RequestInit = { method, headers }
// `readBodyBytes` rather than `Buffer.from(...)`: a bare `Buffer` read in the
// entry script is refused (the global is installed through the global object).
if (method !== 'GET' && method !== 'HEAD') init.body = req.readBodyBytes()
Expand Down
37 changes: 37 additions & 0 deletions apps/nest-node-probe/correctness/dictionary-missing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
interface StringMap {
[name: string]: string
}
interface OptionalStringMap {
[name: string]: string | undefined
}
const plain: StringMap = { a: 'x' }
const optional: OptionalStringMap = { a: 'x' }
const missing = plain['b']
console.log('plain present', plain['a'] === 'x')
console.log('plain missing === undefined', missing === undefined)
console.log('plain missing typeof', typeof missing)
console.log('plain missing !== undefined', plain['b'] !== undefined)
console.log('plain in', 'b' in plain)
console.log('optional missing === undefined', optional['b'] === undefined)
console.log('optional present', optional['a'] === 'x')
console.log('keys', Object.keys(plain).join(','))
console.log('nullish', plain['b'] ?? 'fallback')
console.log('falsy', plain['b'] ? 'truthy' : 'falsy')
const absent = optional['b']
console.log('optional missing typeof', typeof absent)
console.log('optional missing !== undefined', optional['b'] !== undefined)
console.log('optional nullish', optional['b'] ?? 'fallback')
console.log('optional falsy', optional['b'] ? 'truthy' : 'falsy')
console.log('optional or', optional['b'] || 'or-fallback')
console.log('optional not', !optional['b'])
console.log('optional present nullish', optional['a'] ?? 'fallback')
console.log('optional present or', optional['a'] || 'or-fallback')
const guarded = optional['a']
console.log('optional guarded length', guarded !== undefined ? guarded.length : -1)
function describe(value: string | undefined): string {
return value === undefined ? 'none' : `some(${value})`
}
console.log('optional passed', describe(optional['a']), describe(optional['b']))
console.log('optional keys', Object.keys(optional).join(','))
optional['c'] = undefined
console.log('optional set undefined in', 'c' in optional, Object.keys(optional).join(','))
39 changes: 39 additions & 0 deletions apps/nest-node-probe/correctness/http-server-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { createServer, IncomingMessage, ServerResponse } from 'node:http'
import { env } from 'node:process'

class AppRequest extends IncomingMessage {
params: { [name: string]: string } = {}

get path(): string {
const url = this.url ?? '/'
const query = url.indexOf('?')
return query === -1 ? url : url.slice(0, query)
}
}

class AppResponse<Request extends IncomingMessage = AppRequest> extends ServerResponse<Request> {
sendText(text: string): void {
this.setHeader('content-type', 'text/plain; charset=utf-8')
this.end(text)
}
}

const server = createServer({ IncomingMessage: AppRequest, ServerResponse: AppResponse }, (incoming: IncomingMessage, outgoing: ServerResponse) => {
const req = incoming as AppRequest
const res = outgoing as AppResponse
req.params = { path: req.path }
const lines = [
`request ${req instanceof AppRequest} ${req instanceof IncomingMessage}`,
`response ${res instanceof AppResponse} ${res instanceof ServerResponse}`,
`req ${res.req === req} ${res.req.params.path}`,
`method ${req.method} url ${req.url ?? ''} path ${req.path}`,
`headersSent ${res.headersSent}`,
`socket ${req.socket.writable} ${req.socket.readable} ${req.socket.destroyed} ${res.socket === req.socket}`
]
res.sendText(lines.join('\n') + '\n')
})

const PORT = Number(env.PORT ?? '3316')
server.listen(PORT, () => {
console.log(`http-server-options listening on http://127.0.0.1:${PORT}`)
})
105 changes: 105 additions & 0 deletions apps/nest-node-probe/correctness/url-can-parse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
const single: string[] = [
'http://example.com',
'https://example.com/a/b?c#d',
'HTTP://EXAMPLE.COM',
'http://user:pw@host:8080/p',
'http://[::1]:3000/',
'http://[::1',
'http://',
'http:///path',
'http:example.com',
'http:/example.com',
'http://exa mple.com',
'http://a<b',
'http://host:abc',
'http://host:65536',
'http://host:99999999',
'http://host:',
'ftp://x',
'ws://x',
'wss://x',
'file:///tmp/x',
'file://',
'file:x',
'file://a:80/',
'file://u@a/',
'mailto:a@b',
'data:text/plain,hi',
'javascript:alert(1)',
'a:',
'a:b',
'1a:b',
'-a:b',
'+a:b',
'a+b-c.d:x',
'/users/1?x=1',
'users/1',
'',
' http://x',
'http://x ',
'http://x\ty',
'//example.com/p',
'?x',
'#f',
'http://%41.com',
'http://a%20b.com',
'http://a_b.com',
'http://a|b',
'http://a^b',
'http://a\\b',
'http://xn--ls8h.la',
'http://例え.jp',
'http://a..b',
'http://192.168.0.300',
'http://0x7f.1',
'http://0x7f.1.',
'http://1.2.3.4.5',
'http://1..2',
'http://[v1.x]',
'http://a/b c',
'http://a/\u0007',
'http://a/%zz',
'http://:80',
'http://@a',
'http://%zz.com',
'http://012.1.1.1',
'http://1.1.1.0x',
'http://999999999999'
]
const pairs: [string, string][] = [
['/users/1', 'http://example.com'],
['users/1', 'http://example.com/a/'],
['?x', 'http://x'],
['', 'http://x'],
['/p', 'not a base'],
['/p', ''],
['http://y', 'not a base'],
['//z/p', 'http://x'],
['//[::1', 'http://x'],
['/p', 'mailto:a'],
['p', 'mailto:a'],
['#f', 'mailto:a'],
['/p', 'a:/x'],
['p', 'a:/x'],
['//z', 'a:/x'],
['/p', 'file:///tmp/'],
['/p', 'data:text/plain,hi'],
['\\\\z\\p', 'http://x']
]
const lines: string[] = []
for (const url of single) lines.push(`${URL.canParse(url)} ${JSON.stringify(url)}`)
for (const [url, base] of pairs) lines.push(`${URL.canParse(url, base)} ${JSON.stringify(url)} base=${JSON.stringify(base)}`)
lines.push(`${URL.canParse(new URL('http://x/'))} URL object`)
lines.push(`${URL.canParse('/p', new URL('http://x/'))} URL base`)
const constructs = (url: string, base?: string): string => {
try {
new URL(url, base)
return 'parses'
} catch (error) {
const thrown = error as { name: string; code?: string; message: string; input?: string; base?: string }
return `throws ${thrown.name} ${thrown.code} ${thrown.message} input=${thrown.input} base=${thrown.base}`
}
}
for (const url of single) lines.push(`${constructs(url)} ${JSON.stringify(url)}`)
for (const [url, base] of pairs) lines.push(`${constructs(url, base)} ${JSON.stringify(url)} base=${JSON.stringify(base)}`)
console.log(lines.join('\n'))
6 changes: 3 additions & 3 deletions runtime/node/generated/node24-compatibility.json
Original file line number Diff line number Diff line change
Expand Up @@ -10129,7 +10129,7 @@
"status": "partial",
"overlay": "http.ts",
"supportedCallSignatures": [
"(optionsOrListener: ServerOptions | RequestListener, requestListener?: RequestListener): Server"
"(optionsOrListener?: ServerOptions | RequestListener, requestListener?: RequestListener): Server"
]
},
{
Expand Down Expand Up @@ -10448,7 +10448,7 @@
"status": "partial",
"overlay": "http.ts",
"supportedConstructSignatures": [
"(requestListener: RequestListener): Server"
"(optionsOrListener?: ServerOptions | RequestListener, requestListener?: RequestListener): Server"
],
"members": [
{
Expand Down Expand Up @@ -10568,7 +10568,7 @@
"status": "partial",
"overlay": "http.ts",
"supportedConstructSignatures": [
"(connId: number, isHead: boolean, keepAliveRequested: boolean, http10: boolean, request: IncomingMessage): ServerResponse"
"<Request extends IncomingMessage = IncomingMessage>(connId: number, isHead: boolean, keepAliveRequested: boolean, http10: boolean, request: IncomingMessage): ServerResponse<Request>"
],
"members": [
{
Expand Down
Loading
Loading