From 8f0e49e506fd262dbd6a8cba1e9a89b0f19d4595 Mon Sep 17 00:00:00 2001 From: Samuel Mwangi Date: Tue, 24 Mar 2026 17:55:09 +0300 Subject: [PATCH 01/15] Restructure to accomodate API harmonization layer. --- .gitattributes | 8 +- .husky/.gitignore | 1 - .husky/pre-commit | 4 - .prettierignore | 6 +- Cargo.toml | 96 ++--- __test__/health.spec.ts | 2 +- __test__/middlewares/cookie_parser.spec.ts | 2 +- __test__/middlewares/json.spec.ts | 2 +- __test__/middlewares/raw.spec.ts | 2 +- __test__/middlewares/static.spec.ts | 2 +- __test__/middlewares/text.spec.ts | 2 +- __test__/middlewares/urlencoded.spec.ts | 2 +- __test__/request/accepts.spec.ts | 2 +- __test__/request/method.spec.ts | 2 +- __test__/request/range.spec.ts | 2 +- __test__/response/append.spec.ts | 2 +- __test__/response/attachment.spec.ts | 2 +- __test__/response/clear_cookie.spec.ts | 2 +- __test__/response/content_type.spec.ts | 2 +- __test__/response/cookie.spec.ts | 2 +- __test__/response/download.spec.ts | 2 +- __test__/response/end.spec.ts | 2 +- __test__/response/format.spec.ts | 2 +- __test__/response/links.spec.ts | 2 +- __test__/response/location.spec.ts | 2 +- __test__/response/redirect.spec.ts | 2 +- __test__/response/send_file.spec.ts | 2 +- __test__/response/set.spec.ts | 2 +- __test__/response/vary.spec.ts | 2 +- __test__/server-setup.ts | 58 +++ __test__/server.ts | 365 +++++++++++++--- __test__/utilities.spec.ts | 2 +- benchmark/bench.ts | 21 - benchmark/package.json | 3 - benchmark/tsconfig.json | 10 - browser.js | 1 - index.d.ts => hyperjs-core/index.d.ts | 0 index.js => hyperjs-core/index.js | 0 hyperjs-core/package.json | 1 + hyperjs.wasi-browser.js | 72 ---- hyperjs.wasi.cjs | 124 ------ package.json | 14 +- server.ts | 317 -------------- wasi-worker-browser.mjs | 32 -- wasi-worker.mjs | 63 --- yarn.lock | 477 +++++---------------- 46 files changed, 562 insertions(+), 1161 deletions(-) delete mode 100644 .husky/.gitignore delete mode 100755 .husky/pre-commit create mode 100644 __test__/server-setup.ts delete mode 100644 benchmark/bench.ts delete mode 100644 benchmark/package.json delete mode 100644 benchmark/tsconfig.json delete mode 100644 browser.js rename index.d.ts => hyperjs-core/index.d.ts (100%) rename index.js => hyperjs-core/index.js (100%) create mode 100644 hyperjs-core/package.json delete mode 100644 hyperjs.wasi-browser.js delete mode 100644 hyperjs.wasi.cjs delete mode 100644 server.ts delete mode 100644 wasi-worker-browser.mjs delete mode 100644 wasi-worker.mjs diff --git a/.gitattributes b/.gitattributes index 9778fb4..d0a55c4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -10,9 +10,5 @@ *.debug text eol=lf merge=union # Generated codes -index.js linguist-detectable=false -index.d.ts linguist-detectable=false -hyperjs.wasi-browser.js linguist-detectable=false -hyperjs.wasi.cjs linguist-detectable=false -wasi-worker-browser.mjs linguist-detectable=false -wasi-worker.mjs linguist-detectable=false +hyperjs-core/index.js linguist-detectable=false +hyperjs-core/index.d.ts linguist-detectable=false diff --git a/.husky/.gitignore b/.husky/.gitignore deleted file mode 100644 index 31354ec..0000000 --- a/.husky/.gitignore +++ /dev/null @@ -1 +0,0 @@ -_ diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100755 index d2ae35e..0000000 --- a/.husky/pre-commit +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -yarn lint-staged diff --git a/.prettierignore b/.prettierignore index 901e6b6..8120ecf 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,8 +1,4 @@ target .yarn -index.js -package-template.wasi-browser.js -package-template.wasi.cjs -wasi-worker-browser.mjs -wasi-worker.mjs +hyperjs-core/index.js .yarnrc.yml \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 5568270..75d2f75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,72 +2,72 @@ [package] authors = ["LongYinan "] edition = "2024" -name = "hyperjs" +name = "hyperjs" version = "0.1.0" [lib] crate-type = ["cdylib"] [dependencies] -ammonia = "4.1.2" -askama_escape = "0.15.2" -byte-unit = "5.2.0" -bytes = "1.11.0" -chrono = "0.4.43" -env_logger = "0.11.9" -futures = "0.3.31" -headers-accept = "0.3.0" -headers-core = "0.3.0" -http-body = "1.0.1" -http-body-util = "0.1.3" +ammonia = "4.1.2" +askama_escape = "0.15.2" +byte-unit = "5.2.0" +bytes = "1.11.0" +chrono = "0.4.43" +env_logger = "0.11.9" +futures = "0.3.31" +headers-accept = "0.3.0" +headers-core = "0.3.0" +http-body = "1.0.1" +http-body-util = "0.1.3" http-range-header = "0.4.2" -httpdate = "1.0.3" -hyper-staticfile = "0.10.1" -lazy_static = "1.5.0" -log = "0.4.29" -matchit = "0.9.1" -mediatype = "0.21.0" -mime_guess = "2.0.5" -napi-derive = "3.0.0" -percent-encoding = "2.3.2" -regex = "1.12.3" -rustls-acme = {version = "0.15.1", features = ["tokio"]} -serde_json = "1.0.148" -serde_qs = "1.0.0" -serde_urlencoded = "0.7.1" -tempfile = "3.24.0" -tokio-stream = {version = "0.1.18", features = ["net"]} -urlencoding = "2.1.3" +httpdate = "1.0.3" +hyper-staticfile = "0.10.1" +lazy_static = "1.5.0" +log = "0.4.29" +matchit = "0.9.1" +mediatype = "0.21.0" +mime_guess = "2.0.5" +napi-derive = "3.0.0" +percent-encoding = "2.3.2" +regex = "1.12.3" +rustls-acme = { version = "0.15.1", features = ["tokio"] } +serde_json = "1.0.148" +serde_qs = "1.0.0" +serde_urlencoded = "0.7.1" +tempfile = "3.24.0" +tokio-stream = { version = "0.1.18", features = ["net"] } +urlencoding = "2.1.3" -[dependencies.cookie] -features = ["percent-encode", "secure"] -version = "0.18.1" + [dependencies.cookie] + features = ["percent-encode", "secure"] + version = "0.18.1" -[dependencies.etag] -features = ["std"] -version = "4.0.0" + [dependencies.etag] + features = ["std"] + version = "4.0.0" -[dependencies.hyper] -features = ["full"] -version = "1.8.1" + [dependencies.hyper] + features = ["full"] + version = "1.8.1" -[dependencies.hyper-util] -features = ["tokio"] -version = "0.1.19" + [dependencies.hyper-util] + features = ["tokio"] + version = "0.1.19" -[dependencies.napi] -features = ["napi6", "async", "serde", "serde-json"] -version = "3.0.0" + [dependencies.napi] + features = ["napi6", "async", "serde", "serde-json"] + version = "3.0.0" -[dependencies.tokio] -features = ["rt", "net", "rt-multi-thread", "macros"] -version = "1.48.0" + [dependencies.tokio] + features = ["rt", "net", "rt-multi-thread", "macros"] + version = "1.48.0" [build-dependencies] napi-build = "2" [profile.release] -lto = true +lto = true strip = "symbols" [target.'cfg(unix)'.dependencies] diff --git a/__test__/health.spec.ts b/__test__/health.spec.ts index 3e93f87..bab160d 100644 --- a/__test__/health.spec.ts +++ b/__test__/health.spec.ts @@ -3,7 +3,7 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios from 'axios' -import * as server from './server.js' +import * as server from './server-setup.js' let serverApp: ChildProcess let port: number diff --git a/__test__/middlewares/cookie_parser.spec.ts b/__test__/middlewares/cookie_parser.spec.ts index 9acef99..50b90a4 100644 --- a/__test__/middlewares/cookie_parser.spec.ts +++ b/__test__/middlewares/cookie_parser.spec.ts @@ -3,7 +3,7 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios from 'axios' -import * as server from '../server.js' +import * as server from '../server-setup.js' let serverApp: ChildProcess let port: number diff --git a/__test__/middlewares/json.spec.ts b/__test__/middlewares/json.spec.ts index 3a4b6af..f9a4a09 100644 --- a/__test__/middlewares/json.spec.ts +++ b/__test__/middlewares/json.spec.ts @@ -3,7 +3,7 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios from 'axios' -import * as server from '../server.js' +import * as server from '../server-setup.js' let serverApp: ChildProcess let port: number diff --git a/__test__/middlewares/raw.spec.ts b/__test__/middlewares/raw.spec.ts index 72f3eef..9fa5522 100644 --- a/__test__/middlewares/raw.spec.ts +++ b/__test__/middlewares/raw.spec.ts @@ -3,7 +3,7 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios from 'axios' -import * as server from '../server.js' +import * as server from '../server-setup.js' let serverApp: ChildProcess let port: number diff --git a/__test__/middlewares/static.spec.ts b/__test__/middlewares/static.spec.ts index 4e9e5cb..2a2e5fc 100644 --- a/__test__/middlewares/static.spec.ts +++ b/__test__/middlewares/static.spec.ts @@ -3,7 +3,7 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios from 'axios' -import * as server from '../server.js' +import * as server from '../server-setup.js' import { readFileSync } from 'node:fs' import path, { dirname } from 'node:path' import { fileURLToPath } from 'node:url' diff --git a/__test__/middlewares/text.spec.ts b/__test__/middlewares/text.spec.ts index f5654f3..7fb4634 100644 --- a/__test__/middlewares/text.spec.ts +++ b/__test__/middlewares/text.spec.ts @@ -3,7 +3,7 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios from 'axios' -import * as server from '../server.js' +import * as server from '../server-setup.js' let serverApp: ChildProcess let port: number diff --git a/__test__/middlewares/urlencoded.spec.ts b/__test__/middlewares/urlencoded.spec.ts index 8e35fb0..d87d4cd 100644 --- a/__test__/middlewares/urlencoded.spec.ts +++ b/__test__/middlewares/urlencoded.spec.ts @@ -3,7 +3,7 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios from 'axios' -import * as server from '../server.js' +import * as server from '../server-setup.js' let serverApp: ChildProcess let port: number diff --git a/__test__/request/accepts.spec.ts b/__test__/request/accepts.spec.ts index cf4cc4d..aa89b19 100644 --- a/__test__/request/accepts.spec.ts +++ b/__test__/request/accepts.spec.ts @@ -1,6 +1,6 @@ import test from 'ava' -import { Request } from '../../index' +import { Request } from '../../hyperjs-core/index' test('accepts', (t) => { const req = new Request() diff --git a/__test__/request/method.spec.ts b/__test__/request/method.spec.ts index e5b8a76..33a9b29 100644 --- a/__test__/request/method.spec.ts +++ b/__test__/request/method.spec.ts @@ -2,7 +2,7 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios from 'axios' -import * as server from '../server.js' +import * as server from '../server-setup.js' let serverApp: ChildProcess let port: number diff --git a/__test__/request/range.spec.ts b/__test__/request/range.spec.ts index 3a9f3be..bb9186e 100644 --- a/__test__/request/range.spec.ts +++ b/__test__/request/range.spec.ts @@ -2,7 +2,7 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios from 'axios' -import * as server from '../server.js' +import * as server from '../server-setup.js' let serverApp: ChildProcess let port: number diff --git a/__test__/response/append.spec.ts b/__test__/response/append.spec.ts index 457a320..3e32266 100644 --- a/__test__/response/append.spec.ts +++ b/__test__/response/append.spec.ts @@ -1,6 +1,6 @@ import test from 'ava' -import { Response } from '../../index' +import { Response } from '../../hyperjs-core/index' test('append', (t) => { const res = new Response() diff --git a/__test__/response/attachment.spec.ts b/__test__/response/attachment.spec.ts index 1458b56..90c6de2 100644 --- a/__test__/response/attachment.spec.ts +++ b/__test__/response/attachment.spec.ts @@ -1,6 +1,6 @@ import test from 'ava' -import { Response } from '../../index' +import { Response } from '../../hyperjs-core/index' test('attachment - no value', (t) => { const res = new Response() diff --git a/__test__/response/clear_cookie.spec.ts b/__test__/response/clear_cookie.spec.ts index e6af6b2..5d85b39 100644 --- a/__test__/response/clear_cookie.spec.ts +++ b/__test__/response/clear_cookie.spec.ts @@ -1,6 +1,6 @@ import test from 'ava' -import { Response } from '../../index' +import { Response } from '../../hyperjs-core/index' test('clearCookie', (t) => { let res = new Response() diff --git a/__test__/response/content_type.spec.ts b/__test__/response/content_type.spec.ts index d4127ce..b35ff4d 100644 --- a/__test__/response/content_type.spec.ts +++ b/__test__/response/content_type.spec.ts @@ -1,6 +1,6 @@ import test from 'ava' -import { Response } from '../../index' +import { Response } from '../../hyperjs-core/index' test('type', (t) => { const res = new Response() diff --git a/__test__/response/cookie.spec.ts b/__test__/response/cookie.spec.ts index 92183c0..ee30626 100644 --- a/__test__/response/cookie.spec.ts +++ b/__test__/response/cookie.spec.ts @@ -1,6 +1,6 @@ import test from 'ava' -import { Response } from '../../index' +import { Response } from '../../hyperjs-core/index' test('cookie - multiple', (t) => { const res = new Response() diff --git a/__test__/response/download.spec.ts b/__test__/response/download.spec.ts index fd7c342..02842b5 100644 --- a/__test__/response/download.spec.ts +++ b/__test__/response/download.spec.ts @@ -2,7 +2,7 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios from 'axios' -import * as server from '../server.js' +import * as server from '../server-setup.js' import { readFileSync } from 'node:fs' import { fileURLToPath } from 'url' import { dirname } from 'path' diff --git a/__test__/response/end.spec.ts b/__test__/response/end.spec.ts index 480c097..2d67de1 100644 --- a/__test__/response/end.spec.ts +++ b/__test__/response/end.spec.ts @@ -3,7 +3,7 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios from 'axios' -import * as server from '../server.js' +import * as server from '../server-setup.js' let serverApp: ChildProcess let port: number diff --git a/__test__/response/format.spec.ts b/__test__/response/format.spec.ts index 2ff2d05..6ef1f7f 100644 --- a/__test__/response/format.spec.ts +++ b/__test__/response/format.spec.ts @@ -2,7 +2,7 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios from 'axios' -import * as server from '../server.js' +import * as server from '../server-setup.js' let serverApp: ChildProcess let port: number diff --git a/__test__/response/links.spec.ts b/__test__/response/links.spec.ts index 5dc0d68..aba91e9 100644 --- a/__test__/response/links.spec.ts +++ b/__test__/response/links.spec.ts @@ -1,6 +1,6 @@ import test from 'ava' -import { Response } from '../../index' +import { Response } from '../../hyperjs-core/index' test('links', (t) => { let res = new Response() diff --git a/__test__/response/location.spec.ts b/__test__/response/location.spec.ts index 48f4e87..c316248 100644 --- a/__test__/response/location.spec.ts +++ b/__test__/response/location.spec.ts @@ -1,6 +1,6 @@ import test from 'ava' -import { Response } from '../../index' +import { Response } from '../../hyperjs-core/index' test('location', (t) => { let res = new Response() diff --git a/__test__/response/redirect.spec.ts b/__test__/response/redirect.spec.ts index 94964a9..53a285d 100644 --- a/__test__/response/redirect.spec.ts +++ b/__test__/response/redirect.spec.ts @@ -3,7 +3,7 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios, { isAxiosError } from 'axios' -import * as server from '../server.js' +import * as server from '../server-setup.js' let serverApp: ChildProcess let port: number diff --git a/__test__/response/send_file.spec.ts b/__test__/response/send_file.spec.ts index c677947..bb1629e 100644 --- a/__test__/response/send_file.spec.ts +++ b/__test__/response/send_file.spec.ts @@ -2,7 +2,7 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios, { isAxiosError } from 'axios' -import * as server from '../server.js' +import * as server from '../server-setup.js' import { readFileSync } from 'node:fs' import { fileURLToPath } from 'url' import { dirname } from 'path' diff --git a/__test__/response/set.spec.ts b/__test__/response/set.spec.ts index 92eeb42..a79ae89 100644 --- a/__test__/response/set.spec.ts +++ b/__test__/response/set.spec.ts @@ -1,6 +1,6 @@ import test from 'ava' -import { Response } from '../../index' +import { Response } from '../../hyperjs-core/index' test('set - (string, string)', (t) => { let res = new Response() diff --git a/__test__/response/vary.spec.ts b/__test__/response/vary.spec.ts index e8f6f91..de3835c 100644 --- a/__test__/response/vary.spec.ts +++ b/__test__/response/vary.spec.ts @@ -1,6 +1,6 @@ import test from 'ava' -import { Response } from '../../index' +import { Response } from '../../hyperjs-core/index' test('vary', (t) => { let res = new Response() diff --git a/__test__/server-setup.ts b/__test__/server-setup.ts new file mode 100644 index 0000000..78b8a7f --- /dev/null +++ b/__test__/server-setup.ts @@ -0,0 +1,58 @@ +import { ChildProcess, spawn } from 'node:child_process' +import { join } from 'node:path' +import { pathToFileURL } from 'node:url' +import { createRequire } from 'node:module' + +const require = createRequire(import.meta.url) + +async function start(): Promise<{ process: ChildProcess; port: number }> { + const port = Math.floor(Math.random() * 10000) + 10000 + const serverPath = join(process.cwd(), '__test__', 'server.ts') + + // Let Node find the correct path to the tsx package automatically + const tsxEntry = require.resolve('tsx') + const tsxUrl = pathToFileURL(tsxEntry).href + + return await new Promise((resolve, reject) => { + const serverApp = spawn(process.execPath, ['--import', tsxUrl, '--no-warnings', serverPath], { + env: { ...process.env, PORT: String(port) }, + windowsHide: true, + // Use 'pipe' to ensure we can read the "Server listening" message + stdio: ['ignore', 'pipe', 'pipe'], + }) + + const timeout = setTimeout(() => { + serverApp.kill('SIGKILL') + reject(new Error(`Server startup timeout on port ${port}`)) + }, 20000) + + const onData = (data: Buffer) => { + if (data.toString().includes('Server listening')) { + clearTimeout(timeout) + serverApp.stdout?.off('data', onData) + resolve({ process: serverApp, port }) + } + } + + serverApp.stdout?.on('data', onData) + serverApp.stderr?.on('data', (data) => console.error(`[Server Error]: ${data}`)) + + serverApp.on('error', (err) => { + clearTimeout(timeout) + reject(err) + }) + }) +} + +function stop(serverApp?: ChildProcess) { + if (serverApp?.pid) { + try { + // Direct node process kill works on Windows/Linux when no .cmd wrapper is used + serverApp.kill('SIGKILL') + } catch (e) { + // Already dead + } + } +} + +export { start, stop } diff --git a/__test__/server.ts b/__test__/server.ts index 9b2f569..6956e34 100644 --- a/__test__/server.ts +++ b/__test__/server.ts @@ -1,58 +1,317 @@ -import { ChildProcess, spawn } from 'node:child_process' -import { join } from 'node:path' -import { pathToFileURL } from 'node:url' -import { createRequire } from 'node:module' - -const require = createRequire(import.meta.url) - -async function start(): Promise<{ process: ChildProcess; port: number }> { - const port = Math.floor(Math.random() * 10000) + 10000 - const serverPath = join(process.cwd(), 'server.ts') - - // Let Node find the correct path to the tsx package automatically - const tsxEntry = require.resolve('tsx') - const tsxUrl = pathToFileURL(tsxEntry).href - - return await new Promise((resolve, reject) => { - const serverApp = spawn(process.execPath, ['--import', tsxUrl, '--no-warnings', serverPath], { - env: { ...process.env, PORT: String(port) }, - windowsHide: true, - // Use 'pipe' to ensure we can read the "Server listening" message - stdio: ['ignore', 'pipe', 'pipe'], - }) - - const timeout = setTimeout(() => { - serverApp.kill('SIGKILL') - reject(new Error(`Server startup timeout on port ${port}`)) - }, 20000) - - const onData = (data: Buffer) => { - if (data.toString().includes('Server listening')) { - clearTimeout(timeout) - serverApp.stdout?.off('data', onData) - resolve({ process: serverApp, port }) - } - } - - serverApp.stdout?.on('data', onData) - serverApp.stderr?.on('data', (data) => console.error(`[Server Error]: ${data}`)) - - serverApp.on('error', (err) => { - clearTimeout(timeout) - reject(err) - }) +import { + Server, + Request, + Response, + StatusCode, + TextMiddleware, + JsonMiddleware, + RawMiddleware, + StaticMiddleware, + FileStat, + UrlencodedMiddleware, + CookieParserMiddleware, +} from '../hyperjs-core/index.js' +import path from 'path' +import process from 'process' + +const __dirname = process.cwd() + +// ============================================================================ +// SETUP: Create router and register routes +// ============================================================================ + +// Create app with router +const app = new Server() + +// ============================================================================ +// LETSENCRYPT: How to configure +// ============================================================================ + +// app.acmeConfigMeta({ +// domains: ['"example.com'], +// contactEmail: 'admin@foo.com', +// cacheDir: '/home/tomn/.local..', +// }) + +// ============================================================================ +// ROUTE DEFINITIONS +// ============================================================================ + +// // Middleware that continues the chain +app.use('/health', async (_req: Request, _res: Response) => { + console.log('JS: Logging middleware') + return true // Continue to next middleware +}) + +// Simple synchronous route +app.get('/health', (_req: Request, res: Response) => { + console.log('JS: GET /health callback called.') + res.sendStatus(StatusCode.ok()) +}) + +// Test Response.end +app.get('/end', (_req: Request, res: Response) => { + console.log('JS: GET /end callback called.') + res.status(StatusCode.ok()).end() +}) + +// GET | Support URL parameters +app.get('/users/{user_id}', async (req: Request, res: Response) => { + // Get URL parameters for the request object + console.log('Request:', req) + const params = req.params + console.log('URL parameters:', params) + + res.status(200).json(params) +}) + +// Text middleware +const textMiddleware = new TextMiddleware({ + limit: '100mb', +}) +app.use('/echo', (req: Request, res: Response) => textMiddleware.run(req, res)) + +// POST Echo +app.post('/echo', async (req: Request, res: Response) => { + console.log('JS: POST /echo callback called.') + if (typeof req.body === 'string') res.status(200).send(req.body) + else if (typeof req.body === 'undefined') res.sendStatus(200) + else res.status(500).send(`Expected string, found '${typeof req.body}'`) +}) + +// JSON middleware +const jsonMiddleware = new JsonMiddleware({ + strict: true, +}) +app.use('/json-echo', (req: Request, res: Response) => jsonMiddleware.run(req, res)) + +// POST Echo +app.post('/json-echo', async (req: Request, res: Response) => { + console.log('JS: POST /json-echo callback called.') + if (typeof req.body === 'object') res.status(200).send(req.body) + else if (typeof req.body === 'undefined') res.sendStatus(200) + else res.status(500).send(`Expected object, found '${typeof req.body}'`) +}) + +// RAW middleware +const rawMiddleware = new RawMiddleware() +app.use('/raw-echo', (req: Request, res: Response) => rawMiddleware.run(req, res)) + +// RAW Echo +app.post('/raw-echo', async (req: Request, res: Response) => { + console.log('JS: POST /raw-echo callback called.') + let data = req.body + if (data instanceof Buffer) res.status(200).send(data) + else if (typeof req.body === 'undefined') res.sendStatus(200) + else res.status(500).send(`Expected Buffer, found '${typeof req.body}'`) +}) + +// Urlencoded middleware +const urlencodedMiddleware = new UrlencodedMiddleware({ + extended: true, +}) +app.use('/urlencoded', (req: Request, res: Response) => urlencodedMiddleware.run(req, res)) + +// POST Echo +app.post('/urlencoded', async (req: Request, res: Response) => { + console.log('JS: POST /urlencoded callback called.') + let data = req.body + if (typeof data === 'object') res.status(200).send(data) + else if (typeof req.body === 'undefined') res.sendStatus(200) + else res.status(500).send(`Expected Buffer, found '${typeof req.body}'`) +}) + +// Async route with delay +app.get('/users', async (_req: Request, res: Response) => { + console.log('JS: GET /users callback called.') + // Simulate async database query + await new Promise((resolve) => setTimeout(resolve, 100)) + + const users = [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' }, + ] + + res.status(200).json(users) +}) + +app.get('/format', async (_req: Request, res: Response) => { + console.log('JS: GET /format callback called.') + res.format({ + 'text/plain'() { + console.log('JS: text/plain handler executed.') + res.send('hey') + }, + + 'text/html'() { + console.log('JS: text/htmp handler executed.') + res.send('

hey

') + }, + + 'application/json'() { + console.log('JS: application/json handler executed.') + res.send({ message: 'hey' }) + }, + + default() { + // log the request and respond with 406 + res.status(200).send('Not Acceptable') + }, }) -} +}) + +// POST endpoint +app.post('/users', async (_req: Request, res: Response) => { + console.log('JS: POST /users callback called.') + // In a real app, you'd parse the request body here + const newUser = { id: 3, name: 'Charlie' } -function stop(serverApp?: ChildProcess) { - if (serverApp?.pid) { - try { - // Direct node process kill works on Windows/Linux when no .cmd wrapper is used - serverApp.kill('SIGKILL') - } catch (e) { - // Already dead - } + res.status(201).json(newUser) +}) + +// Route with error handling +app.get('/error', async (_req: Request, _res: Response) => { + console.log('JS: GET /error callback called.') + throw new Error('Intentional error for testing') +}) + +// Request.method() test routes +app.get('/method', async (req: Request, res: Response) => { + console.log('JS: GET /method callback called.') + res.status(200).send(req.method) +}) +app.post('/method', async (req: Request, res: Response) => { + console.log('JS: POST /method callback called.') + res.status(200).send(req.method) +}) +app.put('/method', async (req: Request, res: Response) => { + console.log('JS: PUT /method callback called.') + res.status(200).send(req.method) +}) +app.delete('/method', async (req: Request, res: Response) => { + console.log('JS: DELETE /method callback called.') + res.status(200).send(req.method) +}) + +// Redirection +app.get('/redirect', async (_req: Request, res: Response) => { + console.log('JS: GET /redirect callback called.') + res.redirect('https://example.com') +}) + +// Range +app.get('/range', async (req: Request, res: Response) => { + console.log('JS: GET /range callback called.') + res.json(req.range(1000, { combine: true })) +}) + +// Send a file +app.get('/send-file/{dotfiles}/{name}', async (req: Request, res: Response) => { + const options = { + root: path.join(__dirname, 'public'), + dotfiles: (req.params as any).dotfiles, + extensions: ['html', 'htm'], + } + + console.log(options) + + const fileName = (req.params as any).name + console.log('JS: fileName =', fileName) + await res.sendFile(fileName, options) +}) + +// Respond with index.html +app.get('/folder', async (_req: Request, res: Response) => { + const options = { + root: path.join(__dirname, 'public'), + } + + await res.sendFile('/', options) +}) + +// Download a file +app.get('/download/{dotfiles}/{name}', async (req: Request, res: Response) => { + const options = { + root: path.join(__dirname, 'public'), + dotfiles: (req.params as any).dotfiles, + extensions: ['html', 'htm'], + } + + console.log(options) + + const fileName = (req.params as any).name + console.log('JS: fileName =', fileName) + await res.download(fileName, options) +}) + +// Cookie testing endpoints +// set-cookie +const cookieParserMiddleware = new CookieParserMiddleware(null, {}) +app.use(null, (req: Request, res: Response) => cookieParserMiddleware.run(req, res)) + +app.get('/cookie/show', async (req: Request, res: Response) => { + console.log(req.cookies, typeof req.cookies) + res.json(req.cookies as any) +}) + +// ============================================================================ +// AFTER-ROUTES APPLICATION-WIDE MIDDLEWARE DEFINITIONS +// ============================================================================ + +// Static middleware +const staticMiddleware = new StaticMiddleware('public', { + dotfiles: 'ignore', + etag: false, + extensions: ['htm', 'html'], + index: false, + redirect: false, + fallthrough: true, + setHeaders(res: Response, _path: string, _stat: FileStat) { + res.set('x-timestamp', Date.now().toString()) + }, +}) +app.use(null, (req: Request, res: Response) => staticMiddleware.run(req, res)) + +// ============================================================================ +// SERVER STARTUP +// ============================================================================ + +function startServer() { + try { + // Get port from environment variable or use a random port + const port = process.env.PORT || 8080 + const addr = `127.0.0.1:${port}` + + console.log(`Starting app on ${addr}...`) + // console.log(`Registered routes: ${router.getRoutes().join(', ')}`) + + // This will block and run the app + app.listen(addr) + + // Log this exact message so tests can detect when server is ready + console.log('Server listening') + } catch (error) { + console.error('Server error:', error) + process.exit(1) } } -export { start, stop } +// ============================================================================ +// GRACEFUL SHUTDOWN +// ============================================================================ + +process.on('SIGINT', () => { + console.log('\nShutting down gracefully...') + process.exit(0) +}) + +process.on('SIGTERM', () => { + console.log('\nShutting down gracefully...') + process.exit(0) +}) + +// ============================================================================ +// START +// ============================================================================ + +startServer() diff --git a/__test__/utilities.spec.ts b/__test__/utilities.spec.ts index b870d05..3237573 100644 --- a/__test__/utilities.spec.ts +++ b/__test__/utilities.spec.ts @@ -1,6 +1,6 @@ import test from 'ava' -import { serializeNapiObject } from '../index' +import { serializeNapiObject } from '../hyperjs-core/index' test('serializeNapiObject', (t) => { t.is(serializeNapiObject({}), '{}') diff --git a/benchmark/bench.ts b/benchmark/bench.ts deleted file mode 100644 index 7761e87..0000000 --- a/benchmark/bench.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Bench } from 'tinybench' - -import { plus100 } from '../index.js' - -function add(a: number) { - return a + 100 -} - -const b = new Bench() - -b.add('Native a + 100', () => { - plus100(10) -}) - -b.add('JavaScript a + 100', () => { - add(10) -}) - -await b.run() - -console.table(b.table()) diff --git a/benchmark/package.json b/benchmark/package.json deleted file mode 100644 index 3dbc1ca..0000000 --- a/benchmark/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "type": "module" -} diff --git a/benchmark/tsconfig.json b/benchmark/tsconfig.json deleted file mode 100644 index e95988d..0000000 --- a/benchmark/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "moduleResolution": "NodeNext", - "module": "NodeNext", - "outDir": "lib" - }, - "include": ["."], - "exclude": ["lib"] -} diff --git a/browser.js b/browser.js deleted file mode 100644 index 58f8a57..0000000 --- a/browser.js +++ /dev/null @@ -1 +0,0 @@ -export * from 'hyperjs-wasm32-wasi' diff --git a/index.d.ts b/hyperjs-core/index.d.ts similarity index 100% rename from index.d.ts rename to hyperjs-core/index.d.ts diff --git a/index.js b/hyperjs-core/index.js similarity index 100% rename from index.js rename to hyperjs-core/index.js diff --git a/hyperjs-core/package.json b/hyperjs-core/package.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/hyperjs-core/package.json @@ -0,0 +1 @@ +{} diff --git a/hyperjs.wasi-browser.js b/hyperjs.wasi-browser.js deleted file mode 100644 index 95deb56..0000000 --- a/hyperjs.wasi-browser.js +++ /dev/null @@ -1,72 +0,0 @@ -import { - createOnMessage as __wasmCreateOnMessageForFsProxy, - getDefaultContext as __emnapiGetDefaultContext, - instantiateNapiModuleSync as __emnapiInstantiateNapiModuleSync, - WASI as __WASI, -} from '@napi-rs/wasm-runtime' - - - -const __wasi = new __WASI({ - version: 'preview1', -}) - -const __wasmUrl = new URL('./hyperjs.wasm32-wasi.wasm', import.meta.url).href -const __emnapiContext = __emnapiGetDefaultContext() - - -const __sharedMemory = new WebAssembly.Memory({ - initial: 4000, - maximum: 65536, - shared: true, -}) - -const __wasmFile = await fetch(__wasmUrl).then((res) => res.arrayBuffer()) - -const { - instance: __napiInstance, - module: __wasiModule, - napiModule: __napiModule, -} = __emnapiInstantiateNapiModuleSync(__wasmFile, { - context: __emnapiContext, - asyncWorkPoolSize: 4, - wasi: __wasi, - onCreateWorker() { - const worker = new Worker(new URL('./wasi-worker-browser.mjs', import.meta.url), { - type: 'module', - }) - - return worker - }, - overwriteImports(importObject) { - importObject.env = { - ...importObject.env, - ...importObject.napi, - ...importObject.emnapi, - memory: __sharedMemory, - } - return importObject - }, - beforeInit({ instance }) { - for (const name of Object.keys(instance.exports)) { - if (name.startsWith('__napi_register__')) { - instance.exports[name]() - } - } - }, -}) -export default __napiModule.exports -export const Bytes = __napiModule.exports.Bytes -export const CookieParserMiddleware = __napiModule.exports.CookieParserMiddleware -export const FileStat = __napiModule.exports.FileStat -export const JsonMiddleware = __napiModule.exports.JsonMiddleware -export const RawMiddleware = __napiModule.exports.RawMiddleware -export const Request = __napiModule.exports.Request -export const Response = __napiModule.exports.Response -export const Server = __napiModule.exports.Server -export const StaticMiddleware = __napiModule.exports.StaticMiddleware -export const StatusCode = __napiModule.exports.StatusCode -export const TextMiddleware = __napiModule.exports.TextMiddleware -export const UrlencodedMiddleware = __napiModule.exports.UrlencodedMiddleware -export const Version = __napiModule.exports.Version -export const serializeNapiObject = __napiModule.exports.serializeNapiObject diff --git a/hyperjs.wasi.cjs b/hyperjs.wasi.cjs deleted file mode 100644 index 634a0c8..0000000 --- a/hyperjs.wasi.cjs +++ /dev/null @@ -1,124 +0,0 @@ -/* eslint-disable */ -/* prettier-ignore */ - -/* auto-generated by NAPI-RS */ - -const __nodeFs = require('node:fs') -const __nodePath = require('node:path') -const { WASI: __nodeWASI } = require('node:wasi') -const { Worker } = require('node:worker_threads') - -const { - createOnMessage: __wasmCreateOnMessageForFsProxy, - getDefaultContext: __emnapiGetDefaultContext, - instantiateNapiModuleSync: __emnapiInstantiateNapiModuleSync, -} = require('@napi-rs/wasm-runtime') - -const __rootDir = __nodePath.parse(process.cwd()).root - -const __wasi = new __nodeWASI({ - version: 'preview1', - env: process.env, - preopens: { - [__rootDir]: __rootDir, - } -}) - -const __emnapiContext = __emnapiGetDefaultContext() - -const __sharedMemory = new WebAssembly.Memory({ - initial: 4000, - maximum: 65536, - shared: true, -}) - -let __wasmFilePath = __nodePath.join(__dirname, 'hyperjs.wasm32-wasi.wasm') -const __wasmDebugFilePath = __nodePath.join(__dirname, 'hyperjs.wasm32-wasi.debug.wasm') - -if (__nodeFs.existsSync(__wasmDebugFilePath)) { - __wasmFilePath = __wasmDebugFilePath -} else if (!__nodeFs.existsSync(__wasmFilePath)) { - try { - __wasmFilePath = require.resolve('hyperjs-wasm32-wasi/hyperjs.wasm32-wasi.wasm') - } catch { - throw new Error('Cannot find hyperjs.wasm32-wasi.wasm file, and hyperjs-wasm32-wasi package is not installed.') - } -} - -const { instance: __napiInstance, module: __wasiModule, napiModule: __napiModule } = __emnapiInstantiateNapiModuleSync(__nodeFs.readFileSync(__wasmFilePath), { - context: __emnapiContext, - asyncWorkPoolSize: (function() { - const threadsSizeFromEnv = Number(process.env.NAPI_RS_ASYNC_WORK_POOL_SIZE ?? process.env.UV_THREADPOOL_SIZE) - // NaN > 0 is false - if (threadsSizeFromEnv > 0) { - return threadsSizeFromEnv - } else { - return 4 - } - })(), - reuseWorker: true, - wasi: __wasi, - onCreateWorker() { - const worker = new Worker(__nodePath.join(__dirname, 'wasi-worker.mjs'), { - env: process.env, - }) - worker.onmessage = ({ data }) => { - __wasmCreateOnMessageForFsProxy(__nodeFs)(data) - } - - // The main thread of Node.js waits for all the active handles before exiting. - // But Rust threads are never waited without `thread::join`. - // So here we hack the code of Node.js to prevent the workers from being referenced (active). - // According to https://github.com/nodejs/node/blob/19e0d472728c79d418b74bddff588bea70a403d0/lib/internal/worker.js#L415, - // a worker is consist of two handles: kPublicPort and kHandle. - { - const kPublicPort = Object.getOwnPropertySymbols(worker).find(s => - s.toString().includes("kPublicPort") - ); - if (kPublicPort) { - worker[kPublicPort].ref = () => {}; - } - - const kHandle = Object.getOwnPropertySymbols(worker).find(s => - s.toString().includes("kHandle") - ); - if (kHandle) { - worker[kHandle].ref = () => {}; - } - - worker.unref(); - } - return worker - }, - overwriteImports(importObject) { - importObject.env = { - ...importObject.env, - ...importObject.napi, - ...importObject.emnapi, - memory: __sharedMemory, - } - return importObject - }, - beforeInit({ instance }) { - for (const name of Object.keys(instance.exports)) { - if (name.startsWith('__napi_register__')) { - instance.exports[name]() - } - } - }, -}) -module.exports = __napiModule.exports -module.exports.Bytes = __napiModule.exports.Bytes -module.exports.CookieParserMiddleware = __napiModule.exports.CookieParserMiddleware -module.exports.FileStat = __napiModule.exports.FileStat -module.exports.JsonMiddleware = __napiModule.exports.JsonMiddleware -module.exports.RawMiddleware = __napiModule.exports.RawMiddleware -module.exports.Request = __napiModule.exports.Request -module.exports.Response = __napiModule.exports.Response -module.exports.Server = __napiModule.exports.Server -module.exports.StaticMiddleware = __napiModule.exports.StaticMiddleware -module.exports.StatusCode = __napiModule.exports.StatusCode -module.exports.TextMiddleware = __napiModule.exports.TextMiddleware -module.exports.UrlencodedMiddleware = __napiModule.exports.UrlencodedMiddleware -module.exports.Version = __napiModule.exports.Version -module.exports.serializeNapiObject = __napiModule.exports.serializeNapiObject diff --git a/package.json b/package.json index 5279137..3a9b1dc 100644 --- a/package.json +++ b/package.json @@ -49,9 +49,8 @@ }, "scripts": { "artifacts": "napi artifacts", - "bench": "node --import @oxc-node/core/register benchmark/bench.ts", - "build": "napi build --platform --release", - "build:debug": "napi build --platform", + "build": "napi build --output-dir hyperjs-core --platform --release", + "build:debug": "napi build --output-dir hyperjs-core --platform", "format": "run-p format:prettier format:rs format:toml", "format:prettier": "prettier . -w", "format:toml": "taplo format", @@ -59,9 +58,8 @@ "lint": "oxlint .", "prepublishOnly": "napi prepublish -t npm", "test": "ava", - "preversion": "napi build --platform && git add .", - "version": "napi version", - "prepare": "husky" + "preversion": "napi build --output-dir hyperjs-core --platform && git add .", + "version": "napi version" }, "devDependencies": { "@emnapi/core": "^1.5.0", @@ -71,7 +69,7 @@ "@taplo/cli": "^0.7.0", "@tybys/wasm-util": "^0.10.0", "@types/node": "^25.0.3", - "ava": "^6.4.1", + "ava": "^7.0.0", "axios": "^1.13.2", "chalk": "^5.6.2", "husky": "^9.1.7", @@ -82,7 +80,7 @@ "tinybench": "^6.0.0", "tree-kill": "^1.2.2", "tsx": "^4.21.0", - "typescript": "^5.9.2" + "typescript": "^6.0.2" }, "lint-staged": { "*.@(js|ts|tsx)": [ diff --git a/server.ts b/server.ts deleted file mode 100644 index 2f4cf0d..0000000 --- a/server.ts +++ /dev/null @@ -1,317 +0,0 @@ -import { - Server, - Request, - Response, - StatusCode, - TextMiddleware, - JsonMiddleware, - RawMiddleware, - StaticMiddleware, - FileStat, - UrlencodedMiddleware, - CookieParserMiddleware, -} from './index.js' -import path from 'path' -import process from 'process' - -const __dirname = process.cwd() - -// ============================================================================ -// SETUP: Create router and register routes -// ============================================================================ - -// Create app with router -const app = new Server() - -// ============================================================================ -// LETSENCRYPT: How to configure -// ============================================================================ - -// app.acmeConfigMeta({ -// domains: ['"example.com'], -// contactEmail: 'admin@foo.com', -// cacheDir: '/home/tomn/.local..', -// }) - -// ============================================================================ -// ROUTE DEFINITIONS -// ============================================================================ - -// // Middleware that continues the chain -app.use('/health', async (_req: Request, _res: Response) => { - console.log('JS: Logging middleware') - return true // Continue to next middleware -}) - -// Simple synchronous route -app.get('/health', (_req: Request, res: Response) => { - console.log('JS: GET /health callback called.') - res.sendStatus(StatusCode.ok()) -}) - -// Test Response.end -app.get('/end', (_req: Request, res: Response) => { - console.log('JS: GET /end callback called.') - res.status(StatusCode.ok()).end() -}) - -// GET | Support URL parameters -app.get('/users/{user_id}', async (req: Request, res: Response) => { - // Get URL parameters for the request object - console.log('Request:', req) - const params = req.params - console.log('URL parameters:', params) - - res.status(200).json(params) -}) - -// Text middleware -const textMiddleware = new TextMiddleware({ - limit: '100mb', -}) -app.use('/echo', (req: Request, res: Response) => textMiddleware.run(req, res)) - -// POST Echo -app.post('/echo', async (req: Request, res: Response) => { - console.log('JS: POST /echo callback called.') - if (typeof req.body === 'string') res.status(200).send(req.body) - else if (typeof req.body === 'undefined') res.sendStatus(200) - else res.status(500).send(`Expected string, found '${typeof req.body}'`) -}) - -// JSON middleware -const jsonMiddleware = new JsonMiddleware({ - strict: true, -}) -app.use('/json-echo', (req: Request, res: Response) => jsonMiddleware.run(req, res)) - -// POST Echo -app.post('/json-echo', async (req: Request, res: Response) => { - console.log('JS: POST /json-echo callback called.') - if (typeof req.body === 'object') res.status(200).send(req.body) - else if (typeof req.body === 'undefined') res.sendStatus(200) - else res.status(500).send(`Expected object, found '${typeof req.body}'`) -}) - -// RAW middleware -const rawMiddleware = new RawMiddleware() -app.use('/raw-echo', (req: Request, res: Response) => rawMiddleware.run(req, res)) - -// RAW Echo -app.post('/raw-echo', async (req: Request, res: Response) => { - console.log('JS: POST /raw-echo callback called.') - let data = req.body - if (data instanceof Buffer) res.status(200).send(data) - else if (typeof req.body === 'undefined') res.sendStatus(200) - else res.status(500).send(`Expected Buffer, found '${typeof req.body}'`) -}) - -// Urlencoded middleware -const urlencodedMiddleware = new UrlencodedMiddleware({ - extended: true, -}) -app.use('/urlencoded', (req: Request, res: Response) => urlencodedMiddleware.run(req, res)) - -// POST Echo -app.post('/urlencoded', async (req: Request, res: Response) => { - console.log('JS: POST /urlencoded callback called.') - let data = req.body - if (typeof data === 'object') res.status(200).send(data) - else if (typeof req.body === 'undefined') res.sendStatus(200) - else res.status(500).send(`Expected Buffer, found '${typeof req.body}'`) -}) - -// Async route with delay -app.get('/users', async (_req: Request, res: Response) => { - console.log('JS: GET /users callback called.') - // Simulate async database query - await new Promise((resolve) => setTimeout(resolve, 100)) - - const users = [ - { id: 1, name: 'Alice' }, - { id: 2, name: 'Bob' }, - ] - - res.status(200).json(users) -}) - -app.get('/format', async (_req: Request, res: Response) => { - console.log('JS: GET /format callback called.') - res.format({ - 'text/plain'() { - console.log('JS: text/plain handler executed.') - res.send('hey') - }, - - 'text/html'() { - console.log('JS: text/htmp handler executed.') - res.send('

hey

') - }, - - 'application/json'() { - console.log('JS: application/json handler executed.') - res.send({ message: 'hey' }) - }, - - default() { - // log the request and respond with 406 - res.status(200).send('Not Acceptable') - }, - }) -}) - -// POST endpoint -app.post('/users', async (_req: Request, res: Response) => { - console.log('JS: POST /users callback called.') - // In a real app, you'd parse the request body here - const newUser = { id: 3, name: 'Charlie' } - - res.status(201).json(newUser) -}) - -// Route with error handling -app.get('/error', async (_req: Request, _res: Response) => { - console.log('JS: GET /error callback called.') - throw new Error('Intentional error for testing') -}) - -// Request.method() test routes -app.get('/method', async (req: Request, res: Response) => { - console.log('JS: GET /method callback called.') - res.status(200).send(req.method) -}) -app.post('/method', async (req: Request, res: Response) => { - console.log('JS: POST /method callback called.') - res.status(200).send(req.method) -}) -app.put('/method', async (req: Request, res: Response) => { - console.log('JS: PUT /method callback called.') - res.status(200).send(req.method) -}) -app.delete('/method', async (req: Request, res: Response) => { - console.log('JS: DELETE /method callback called.') - res.status(200).send(req.method) -}) - -// Redirection -app.get('/redirect', async (_req: Request, res: Response) => { - console.log('JS: GET /redirect callback called.') - res.redirect('https://example.com') -}) - -// Range -app.get('/range', async (req: Request, res: Response) => { - console.log('JS: GET /range callback called.') - res.json(req.range(1000, { combine: true })) -}) - -// Send a file -app.get('/send-file/{dotfiles}/{name}', async (req: Request, res: Response) => { - const options = { - root: path.join(__dirname, 'public'), - dotfiles: (req.params as any).dotfiles, - extensions: ['html', 'htm'], - } - - console.log(options) - - const fileName = (req.params as any).name - console.log('JS: fileName =', fileName) - await res.sendFile(fileName, options) -}) - -// Respond with index.html -app.get('/folder', async (_req: Request, res: Response) => { - const options = { - root: path.join(__dirname, 'public'), - } - - await res.sendFile('/', options) -}) - -// Download a file -app.get('/download/{dotfiles}/{name}', async (req: Request, res: Response) => { - const options = { - root: path.join(__dirname, 'public'), - dotfiles: (req.params as any).dotfiles, - extensions: ['html', 'htm'], - } - - console.log(options) - - const fileName = (req.params as any).name - console.log('JS: fileName =', fileName) - await res.download(fileName, options) -}) - -// Cookie testing endpoints -// set-cookie -const cookieParserMiddleware = new CookieParserMiddleware(null, {}) -app.use(null, (req: Request, res: Response) => cookieParserMiddleware.run(req, res)) - -app.get('/cookie/show', async (req: Request, res: Response) => { - console.log(req.cookies, typeof req.cookies) - res.json(req.cookies as any) -}) - -// ============================================================================ -// AFTER-ROUTES APPLICATION-WIDE MIDDLEWARE DEFINITIONS -// ============================================================================ - -// Static middleware -const staticMiddleware = new StaticMiddleware('public', { - dotfiles: 'ignore', - etag: false, - extensions: ['htm', 'html'], - index: false, - redirect: false, - fallthrough: true, - setHeaders(res: Response, _path: string, _stat: FileStat) { - res.set('x-timestamp', Date.now().toString()) - }, -}) -app.use(null, (req: Request, res: Response) => staticMiddleware.run(req, res)) - -// ============================================================================ -// SERVER STARTUP -// ============================================================================ - -function startServer() { - try { - // Get port from environment variable or use a random port - const port = process.env.PORT || 8080 - const addr = `127.0.0.1:${port}` - - console.log(`Starting app on ${addr}...`) - // console.log(`Registered routes: ${router.getRoutes().join(', ')}`) - - // This will block and run the app - app.listen(addr) - - // Log this exact message so tests can detect when server is ready - console.log('Server listening') - } catch (error) { - console.error('Server error:', error) - process.exit(1) - } -} - -// ============================================================================ -// GRACEFUL SHUTDOWN -// ============================================================================ - -process.on('SIGINT', () => { - console.log('\nShutting down gracefully...') - process.exit(0) -}) - -process.on('SIGTERM', () => { - console.log('\nShutting down gracefully...') - process.exit(0) -}) - -// ============================================================================ -// START -// ============================================================================ - -startServer() diff --git a/wasi-worker-browser.mjs b/wasi-worker-browser.mjs deleted file mode 100644 index 8b1b172..0000000 --- a/wasi-worker-browser.mjs +++ /dev/null @@ -1,32 +0,0 @@ -import { instantiateNapiModuleSync, MessageHandler, WASI } from '@napi-rs/wasm-runtime' - -const handler = new MessageHandler({ - onLoad({ wasmModule, wasmMemory }) { - const wasi = new WASI({ - print: function () { - // eslint-disable-next-line no-console - console.log.apply(console, arguments) - }, - printErr: function() { - // eslint-disable-next-line no-console - console.error.apply(console, arguments) - }, - }) - return instantiateNapiModuleSync(wasmModule, { - childThread: true, - wasi, - overwriteImports(importObject) { - importObject.env = { - ...importObject.env, - ...importObject.napi, - ...importObject.emnapi, - memory: wasmMemory, - } - }, - }) - }, -}) - -globalThis.onmessage = function (e) { - handler.handle(e) -} diff --git a/wasi-worker.mjs b/wasi-worker.mjs deleted file mode 100644 index 84b448f..0000000 --- a/wasi-worker.mjs +++ /dev/null @@ -1,63 +0,0 @@ -import fs from "node:fs"; -import { createRequire } from "node:module"; -import { parse } from "node:path"; -import { WASI } from "node:wasi"; -import { parentPort, Worker } from "node:worker_threads"; - -const require = createRequire(import.meta.url); - -const { instantiateNapiModuleSync, MessageHandler, getDefaultContext } = require("@napi-rs/wasm-runtime"); - -if (parentPort) { - parentPort.on("message", (data) => { - globalThis.onmessage({ data }); - }); -} - -Object.assign(globalThis, { - self: globalThis, - require, - Worker, - importScripts: function (f) { - ;(0, eval)(fs.readFileSync(f, "utf8") + "//# sourceURL=" + f); - }, - postMessage: function (msg) { - if (parentPort) { - parentPort.postMessage(msg); - } - }, -}); - -const emnapiContext = getDefaultContext(); - -const __rootDir = parse(process.cwd()).root; - -const handler = new MessageHandler({ - onLoad({ wasmModule, wasmMemory }) { - const wasi = new WASI({ - version: 'preview1', - env: process.env, - preopens: { - [__rootDir]: __rootDir, - }, - }); - - return instantiateNapiModuleSync(wasmModule, { - childThread: true, - wasi, - context: emnapiContext, - overwriteImports(importObject) { - importObject.env = { - ...importObject.env, - ...importObject.napi, - ...importObject.emnapi, - memory: wasmMemory - }; - }, - }); - }, -}); - -globalThis.onmessage = function (e) { - handler.handle(e); -}; diff --git a/yarn.lock b/yarn.lock index 3a45399..7b5d40d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -465,20 +465,6 @@ __metadata: languageName: node linkType: hard -"@isaacs/cliui@npm:^8.0.2": - version: 8.0.2 - resolution: "@isaacs/cliui@npm:8.0.2" - dependencies: - string-width: "npm:^5.1.2" - string-width-cjs: "npm:string-width@^4.2.0" - strip-ansi: "npm:^7.0.1" - strip-ansi-cjs: "npm:strip-ansi@^6.0.1" - wrap-ansi: "npm:^8.1.0" - wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" - checksum: 10c0/b1bf42535d49f11dc137f18d5e4e63a28c5569de438a221c369483731e9dac9fb797af554e8bf02b6192d1e5eba6e6402cf93900c3d0ac86391d00d04876789e - languageName: node - linkType: hard - "@isaacs/fs-minipass@npm:^4.0.0": version: 4.0.1 resolution: "@isaacs/fs-minipass@npm:4.0.1" @@ -1569,13 +1555,6 @@ __metadata: languageName: node linkType: hard -"@pkgjs/parseargs@npm:^0.11.0": - version: 0.11.0 - resolution: "@pkgjs/parseargs@npm:0.11.0" - checksum: 10c0/5bd7576bb1b38a47a7fc7b51ac9f38748e772beebc56200450c4a817d712232b8f1d3ef70532c80840243c657d491cf6a6be1e3a214cff907645819fdc34aadd - languageName: node - linkType: hard - "@rollup/pluginutils@npm:^5.1.3": version: 5.3.0 resolution: "@rollup/pluginutils@npm:5.3.0" @@ -1603,7 +1582,7 @@ __metadata: "@taplo/cli": "npm:^0.7.0" "@tybys/wasm-util": "npm:^0.10.0" "@types/node": "npm:^25.0.3" - ava: "npm:^6.4.1" + ava: "npm:^7.0.0" axios: "npm:^1.13.2" chalk: "npm:^5.6.2" husky: "npm:^9.1.7" @@ -1614,14 +1593,14 @@ __metadata: tinybench: "npm:^6.0.0" tree-kill: "npm:^1.2.2" tsx: "npm:^4.21.0" - typescript: "npm:^5.9.2" + typescript: "npm:^6.0.2" languageName: unknown linkType: soft -"@sindresorhus/merge-streams@npm:^2.1.0": - version: 2.3.0 - resolution: "@sindresorhus/merge-streams@npm:2.3.0" - checksum: 10c0/69ee906f3125fb2c6bb6ec5cdd84e8827d93b49b3892bce8b62267116cc7e197b5cccf20c160a1d32c26014ecd14470a72a5e3ee37a58f1d6dadc0db1ccf3894 +"@sindresorhus/merge-streams@npm:^4.0.0": + version: 4.0.0 + resolution: "@sindresorhus/merge-streams@npm:4.0.0" + checksum: 10c0/482ee543629aa1933b332f811a1ae805a213681ecdd98c042b1c1b89387df63e7812248bb4df3910b02b3cc5589d3d73e4393f30e197c9dde18046ccd471fc6b languageName: node linkType: hard @@ -1659,9 +1638,9 @@ __metadata: languageName: node linkType: hard -"@vercel/nft@npm:^0.29.4": - version: 0.29.4 - resolution: "@vercel/nft@npm:0.29.4" +"@vercel/nft@npm:^1.3.2": + version: 1.5.0 + resolution: "@vercel/nft@npm:1.5.0" dependencies: "@mapbox/node-pre-gyp": "npm:^2.0.0" "@rollup/pluginutils": "npm:^5.1.3" @@ -1670,14 +1649,14 @@ __metadata: async-sema: "npm:^3.1.1" bindings: "npm:^1.4.0" estree-walker: "npm:2.0.2" - glob: "npm:^10.4.5" + glob: "npm:^13.0.0" graceful-fs: "npm:^4.2.9" node-gyp-build: "npm:^4.2.2" picomatch: "npm:^4.0.2" resolve-from: "npm:^5.0.0" bin: nft: out/cli.js - checksum: 10c0/84ba32c685f9d7c2c849b1e8c963d3b7eb09d122e666143ed97c3776f5b04a4745605e1d29fd81383f72b1d1c0d7d58e39f06dc92f021b5de079dfa4e8523574 + checksum: 10c0/e44f2bb41908f11ece98aa5c4f33b7ff6575a5102ab2db2675f4ee74bcc62439dce4d363a393f80a677898f63f7cac5741685653ee97a924e61e75c9d5b504d6 languageName: node linkType: hard @@ -1704,7 +1683,7 @@ __metadata: languageName: node linkType: hard -"acorn-walk@npm:^8.3.4": +"acorn-walk@npm:^8.3.5": version: 8.3.5 resolution: "acorn-walk@npm:8.3.5" dependencies: @@ -1713,7 +1692,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.11.0, acorn@npm:^8.15.0, acorn@npm:^8.6.0": +"acorn@npm:^8.11.0, acorn@npm:^8.16.0, acorn@npm:^8.6.0": version: 8.16.0 resolution: "acorn@npm:8.16.0" bin: @@ -1738,13 +1717,6 @@ __metadata: languageName: node linkType: hard -"ansi-regex@npm:^5.0.1": - version: 5.0.1 - resolution: "ansi-regex@npm:5.0.1" - checksum: 10c0/9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 - languageName: node - linkType: hard - "ansi-regex@npm:^6.2.2": version: 6.2.2 resolution: "ansi-regex@npm:6.2.2" @@ -1752,16 +1724,7 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^4.0.0": - version: 4.3.0 - resolution: "ansi-styles@npm:4.3.0" - dependencies: - color-convert: "npm:^2.0.1" - checksum: 10c0/895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 - languageName: node - linkType: hard - -"ansi-styles@npm:^6.0.0, ansi-styles@npm:^6.1.0, ansi-styles@npm:^6.2.1, ansi-styles@npm:^6.2.3": +"ansi-styles@npm:^6.2.1, ansi-styles@npm:^6.2.3": version: 6.2.3 resolution: "ansi-styles@npm:6.2.3" checksum: 10c0/23b8a4ce14e18fb854693b95351e286b771d23d8844057ed2e7d083cd3e708376c3323707ec6a24365f7d7eda3ca00327fe04092e29e551499ec4c8b7bfac868 @@ -1833,50 +1796,49 @@ __metadata: languageName: node linkType: hard -"ava@npm:^6.4.1": - version: 6.4.1 - resolution: "ava@npm:6.4.1" +"ava@npm:^7.0.0": + version: 7.0.0 + resolution: "ava@npm:7.0.0" dependencies: - "@vercel/nft": "npm:^0.29.4" - acorn: "npm:^8.15.0" - acorn-walk: "npm:^8.3.4" - ansi-styles: "npm:^6.2.1" + "@vercel/nft": "npm:^1.3.2" + acorn: "npm:^8.16.0" + acorn-walk: "npm:^8.3.5" + ansi-styles: "npm:^6.2.3" arrgv: "npm:^1.0.2" arrify: "npm:^3.0.0" callsites: "npm:^4.2.0" - cbor: "npm:^10.0.9" - chalk: "npm:^5.4.1" + cbor: "npm:^10.0.11" + chalk: "npm:^5.6.2" chunkd: "npm:^2.0.1" - ci-info: "npm:^4.3.0" + ci-info: "npm:^4.4.0" ci-parallel-vars: "npm:^1.0.1" - cli-truncate: "npm:^4.0.0" + cli-truncate: "npm:^5.1.1" code-excerpt: "npm:^4.0.0" common-path-prefix: "npm:^3.0.0" concordance: "npm:^5.0.4" currently-unhandled: "npm:^0.4.1" - debug: "npm:^4.4.1" + debug: "npm:^4.4.3" emittery: "npm:^1.2.0" figures: "npm:^6.1.0" - globby: "npm:^14.1.0" + globby: "npm:^16.1.1" ignore-by-default: "npm:^2.1.0" indent-string: "npm:^5.0.0" is-plain-object: "npm:^5.0.0" is-promise: "npm:^4.0.0" - matcher: "npm:^5.0.0" - memoize: "npm:^10.1.0" + matcher: "npm:^6.0.0" + memoize: "npm:^10.2.0" ms: "npm:^2.1.3" - p-map: "npm:^7.0.3" + p-map: "npm:^7.0.4" package-config: "npm:^5.0.0" - picomatch: "npm:^4.0.2" - plur: "npm:^5.1.0" - pretty-ms: "npm:^9.2.0" + picomatch: "npm:^4.0.3" + plur: "npm:^6.0.0" + pretty-ms: "npm:^9.3.0" resolve-cwd: "npm:^3.0.0" stack-utils: "npm:^2.0.6" - strip-ansi: "npm:^7.1.0" supertap: "npm:^3.0.1" temp-dir: "npm:^3.0.0" - write-file-atomic: "npm:^6.0.0" - yargs: "npm:^17.7.2" + write-file-atomic: "npm:^7.0.0" + yargs: "npm:^18.0.0" peerDependencies: "@ava/typescript": "*" peerDependenciesMeta: @@ -1884,7 +1846,7 @@ __metadata: optional: true bin: ava: entrypoints/cli.mjs - checksum: 10c0/21972df1031ef46533ea1b7daa132a5fc66841c8a221b6901163d12d2a1cac39bfd8a6d3459da7eb9344fa90fc02f237f2fe2aac8785d04bf5894fa43625be28 + checksum: 10c0/19c5e494bac1cc8140e78cb1440f299f9ae07baea50740c4d66daec3e2af048e71b8b5313e4834e100aeead7182d9bfe1b7acaad3dab7317ef2a00f23d39bc92 languageName: node linkType: hard @@ -1899,13 +1861,6 @@ __metadata: languageName: node linkType: hard -"balanced-match@npm:^1.0.0": - version: 1.0.2 - resolution: "balanced-match@npm:1.0.2" - checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee - languageName: node - linkType: hard - "balanced-match@npm:^4.0.2": version: 4.0.4 resolution: "balanced-match@npm:4.0.4" @@ -1936,15 +1891,6 @@ __metadata: languageName: node linkType: hard -"brace-expansion@npm:^2.0.2": - version: 2.0.2 - resolution: "brace-expansion@npm:2.0.2" - dependencies: - balanced-match: "npm:^1.0.0" - checksum: 10c0/6d117a4c793488af86b83172deb6af143e94c17bc53b0b3cec259733923b4ca84679d506ac261f4ba3c7ed37c46018e2ff442f9ce453af8643ecd64f4a54e6cf - languageName: node - linkType: hard - "brace-expansion@npm:^5.0.2": version: 5.0.4 resolution: "brace-expansion@npm:5.0.4" @@ -1999,7 +1945,7 @@ __metadata: languageName: node linkType: hard -"cbor@npm:^10.0.9": +"cbor@npm:^10.0.11": version: 10.0.12 resolution: "cbor@npm:10.0.12" dependencies: @@ -2008,7 +1954,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^5.4.1, chalk@npm:^5.6.2": +"chalk@npm:^5.6.2": version: 5.6.2 resolution: "chalk@npm:5.6.2" checksum: 10c0/99a4b0f0e7991796b1e7e3f52dceb9137cae2a9dfc8fc0784a550dc4c558e15ab32ed70b14b21b52beb2679b4892b41a0aa44249bcb996f01e125d58477c6976 @@ -2036,7 +1982,7 @@ __metadata: languageName: node linkType: hard -"ci-info@npm:^4.3.0": +"ci-info@npm:^4.4.0": version: 4.4.0 resolution: "ci-info@npm:4.4.0" checksum: 10c0/44156201545b8dde01aa8a09ee2fe9fc7a73b1bef9adbd4606c9f61c8caeeb73fb7a575c88b0443f7b4edb5ee45debaa59ed54ba5f99698339393ca01349eb3a @@ -2059,17 +2005,7 @@ __metadata: languageName: node linkType: hard -"cli-truncate@npm:^4.0.0": - version: 4.0.0 - resolution: "cli-truncate@npm:4.0.0" - dependencies: - slice-ansi: "npm:^5.0.0" - string-width: "npm:^7.0.0" - checksum: 10c0/d7f0b73e3d9b88cb496e6c086df7410b541b56a43d18ade6a573c9c18bd001b1c3fba1ad578f741a4218fdc794d042385f8ac02c25e1c295a2d8b9f3cb86eb4c - languageName: node - linkType: hard - -"cli-truncate@npm:^5.0.0": +"cli-truncate@npm:^5.0.0, cli-truncate@npm:^5.1.1": version: 5.2.0 resolution: "cli-truncate@npm:5.2.0" dependencies: @@ -2097,14 +2033,14 @@ __metadata: languageName: node linkType: hard -"cliui@npm:^8.0.1": - version: 8.0.1 - resolution: "cliui@npm:8.0.1" +"cliui@npm:^9.0.1": + version: 9.0.1 + resolution: "cliui@npm:9.0.1" dependencies: - string-width: "npm:^4.2.0" - strip-ansi: "npm:^6.0.1" - wrap-ansi: "npm:^7.0.0" - checksum: 10c0/4bda0f09c340cbb6dfdc1ed508b3ca080f12992c18d68c6be4d9cf51756033d5266e61ec57529e610dacbf4da1c634423b0c1b11037709cc6b09045cbd815df5 + string-width: "npm:^7.2.0" + strip-ansi: "npm:^7.1.0" + wrap-ansi: "npm:^9.0.0" + checksum: 10c0/13441832e9efe7c7a76bd2b8e683555c478d461a9f249dc5db9b17fe8d4b47fa9277b503914b90bd00e4a151abb6b9b02b2288972ffe2e5e3ca40bcb1c2330d3 languageName: node linkType: hard @@ -2117,22 +2053,6 @@ __metadata: languageName: node linkType: hard -"color-convert@npm:^2.0.1": - version: 2.0.1 - resolution: "color-convert@npm:2.0.1" - dependencies: - color-name: "npm:~1.1.4" - checksum: 10c0/37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 - languageName: node - linkType: hard - -"color-name@npm:~1.1.4": - version: 1.1.4 - resolution: "color-name@npm:1.1.4" - checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 - languageName: node - linkType: hard - "colorette@npm:^2.0.20": version: 2.0.20 resolution: "colorette@npm:2.0.20" @@ -2222,7 +2142,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.3.4, debug@npm:^4.4.1": +"debug@npm:4, debug@npm:^4.3.4, debug@npm:^4.4.1, debug@npm:^4.4.3": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -2259,13 +2179,6 @@ __metadata: languageName: node linkType: hard -"eastasianwidth@npm:^0.2.0": - version: 0.2.0 - resolution: "eastasianwidth@npm:0.2.0" - checksum: 10c0/26f364ebcdb6395f95124fda411f63137a4bfb5d3a06453f7f23dfe52502905bd84e0488172e0f9ec295fdc45f05c23d5d91baf16bd26f0fe9acd777a188dc39 - languageName: node - linkType: hard - "emittery@npm:^1.2.0": version: 1.2.1 resolution: "emittery@npm:1.2.1" @@ -2292,20 +2205,6 @@ __metadata: languageName: node linkType: hard -"emoji-regex@npm:^8.0.0": - version: 8.0.0 - resolution: "emoji-regex@npm:8.0.0" - checksum: 10c0/b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010 - languageName: node - linkType: hard - -"emoji-regex@npm:^9.2.2": - version: 9.2.2 - resolution: "emoji-regex@npm:9.2.2" - checksum: 10c0/af014e759a72064cf66e6e694a7fc6b0ed3d8db680427b021a89727689671cefe9d04151b2cad51dbaf85d5ba790d061cd167f1cf32eb7b281f6368b3c181639 - languageName: node - linkType: hard - "env-paths@npm:^2.2.0": version: 2.2.1 resolution: "env-paths@npm:2.2.1" @@ -2630,16 +2529,6 @@ __metadata: languageName: node linkType: hard -"foreground-child@npm:^3.1.0": - version: 3.3.1 - resolution: "foreground-child@npm:3.3.1" - dependencies: - cross-spawn: "npm:^7.0.6" - signal-exit: "npm:^4.0.1" - checksum: 10c0/8986e4af2430896e65bc2788d6679067294d6aee9545daefc84923a0a4b399ad9c7a3ea7bd8c0b2b80fdf4a92de4c69df3f628233ff3224260e9c1541a9e9ed3 - languageName: node - linkType: hard - "form-data@npm:^4.0.5": version: 4.0.5 resolution: "form-data@npm:4.0.5" @@ -2758,22 +2647,6 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.4.5": - version: 10.5.0 - resolution: "glob@npm:10.5.0" - dependencies: - foreground-child: "npm:^3.1.0" - jackspeak: "npm:^3.1.2" - minimatch: "npm:^9.0.4" - minipass: "npm:^7.1.2" - package-json-from-dist: "npm:^1.0.0" - path-scurry: "npm:^1.11.1" - bin: - glob: dist/esm/bin.mjs - checksum: 10c0/100705eddbde6323e7b35e1d1ac28bcb58322095bd8e63a7d0bef1a2cdafe0d0f7922a981b2b48369a4f8c1b077be5c171804534c3509dfe950dde15fbe6d828 - languageName: node - linkType: hard - "glob@npm:^13.0.0": version: 13.0.6 resolution: "glob@npm:13.0.6" @@ -2785,17 +2658,17 @@ __metadata: languageName: node linkType: hard -"globby@npm:^14.1.0": - version: 14.1.0 - resolution: "globby@npm:14.1.0" +"globby@npm:^16.1.1": + version: 16.1.1 + resolution: "globby@npm:16.1.1" dependencies: - "@sindresorhus/merge-streams": "npm:^2.1.0" + "@sindresorhus/merge-streams": "npm:^4.0.0" fast-glob: "npm:^3.3.3" - ignore: "npm:^7.0.3" - path-type: "npm:^6.0.0" + ignore: "npm:^7.0.5" + is-path-inside: "npm:^4.0.0" slash: "npm:^5.1.0" - unicorn-magic: "npm:^0.3.0" - checksum: 10c0/527a1063c5958255969620c6fa4444a2b2e9278caddd571d46dfbfa307cb15977afb746e84d682ba5b6c94fc081e8997f80ff05dd235441ba1cb16f86153e58e + unicorn-magic: "npm:^0.4.0" + checksum: 10c0/2fbed8e5c59639a98b9b9c700afe5bcedf14742b43c25950cfd34a032db0cce4b440d8436beb4a936d211744e0b7330646f086b95cd8054251162c5d83001600 languageName: node linkType: hard @@ -2890,7 +2763,7 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^7.0.3": +"ignore@npm:^7.0.5": version: 7.0.5 resolution: "ignore@npm:7.0.5" checksum: 10c0/ae00db89fe873064a093b8999fe4cc284b13ef2a178636211842cceb650b9c3e390d3339191acb145d81ed5379d2074840cf0c33a20bdbd6f32821f79eb4ad5d @@ -2918,10 +2791,10 @@ __metadata: languageName: node linkType: hard -"irregular-plurals@npm:^3.3.0": - version: 3.5.0 - resolution: "irregular-plurals@npm:3.5.0" - checksum: 10c0/7c033bbe7325e5a6e0a26949cc6863b6ce273403d4cd5b93bd99b33fecb6605b0884097c4259c23ed0c52c2133bf7d1cdcdd7a0630e8c325161fe269b3447918 +"irregular-plurals@npm:^4.2.0": + version: 4.2.0 + resolution: "irregular-plurals@npm:4.2.0" + checksum: 10c0/f3b86491a2e2879c8bb4f818dede9d1305d02893076e73e6b620fcf570330bcaa0520dc1342d088b35dfea07e79919ff780e21f61c4e1fb6cc50858c1045be81 languageName: node linkType: hard @@ -2932,20 +2805,6 @@ __metadata: languageName: node linkType: hard -"is-fullwidth-code-point@npm:^3.0.0": - version: 3.0.0 - resolution: "is-fullwidth-code-point@npm:3.0.0" - checksum: 10c0/bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc - languageName: node - linkType: hard - -"is-fullwidth-code-point@npm:^4.0.0": - version: 4.0.0 - resolution: "is-fullwidth-code-point@npm:4.0.0" - checksum: 10c0/df2a717e813567db0f659c306d61f2f804d480752526886954a2a3e2246c7745fd07a52b5fecf2b68caf0a6c79dcdace6166fdf29cc76ed9975cc334f0a018b8 - languageName: node - linkType: hard - "is-fullwidth-code-point@npm:^5.0.0, is-fullwidth-code-point@npm:^5.1.0": version: 5.1.0 resolution: "is-fullwidth-code-point@npm:5.1.0" @@ -2971,6 +2830,13 @@ __metadata: languageName: node linkType: hard +"is-path-inside@npm:^4.0.0": + version: 4.0.0 + resolution: "is-path-inside@npm:4.0.0" + checksum: 10c0/51188d7e2b1d907a9a5f7c18d99a90b60870b951ed87cf97595d9aaa429d4c010652c3350bcbf31182e7f4b0eab9a1860b43e16729b13cb1a44baaa6cdb64c46 + languageName: node + linkType: hard + "is-plain-object@npm:^5.0.0": version: 5.0.0 resolution: "is-plain-object@npm:5.0.0" @@ -3013,19 +2879,6 @@ __metadata: languageName: node linkType: hard -"jackspeak@npm:^3.1.2": - version: 3.4.3 - resolution: "jackspeak@npm:3.4.3" - dependencies: - "@isaacs/cliui": "npm:^8.0.2" - "@pkgjs/parseargs": "npm:^0.11.0" - dependenciesMeta: - "@pkgjs/parseargs": - optional: true - checksum: 10c0/6acc10d139eaefdbe04d2f679e6191b3abf073f111edf10b1de5302c97ec93fffeb2fdd8681ed17f16268aa9dd4f8c588ed9d1d3bffbbfa6e8bf897cbb3149b9 - languageName: node - linkType: hard - "js-string-escape@npm:^1.0.1": version: 1.0.1 resolution: "js-string-escape@npm:1.0.1" @@ -3127,13 +2980,6 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^10.2.0": - version: 10.4.3 - resolution: "lru-cache@npm:10.4.3" - checksum: 10c0/ebd04fbca961e6c1d6c0af3799adcc966a1babe798f685bb84e6599266599cd95d94630b10262f5424539bc4640107e8a33aa28585374abf561d30d16f4b39fb - languageName: node - linkType: hard - "lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1": version: 11.2.7 resolution: "lru-cache@npm:11.2.7" @@ -3160,12 +3006,12 @@ __metadata: languageName: node linkType: hard -"matcher@npm:^5.0.0": - version: 5.0.0 - resolution: "matcher@npm:5.0.0" +"matcher@npm:^6.0.0": + version: 6.0.0 + resolution: "matcher@npm:6.0.0" dependencies: escape-string-regexp: "npm:^5.0.0" - checksum: 10c0/eda5471fc9d5b7264d63c81727824adc3585ddb5cfdc5fce5a9b7c86f946ff181610735d330b1c37a84811df872d1290bf4e9401d2be2a414204343701144b18 + checksum: 10c0/b2eb6a23de82be3cbcf8e79034638a44e22ce6fcc6968ea2a7b342b1e059eb15bc18bf32d8fce7390c9b0710b21a8eabdc133190c0d86a91de2491b0afae3ca7 languageName: node linkType: hard @@ -3185,7 +3031,7 @@ __metadata: languageName: node linkType: hard -"memoize@npm:^10.1.0": +"memoize@npm:^10.2.0": version: 10.2.0 resolution: "memoize@npm:10.2.0" dependencies: @@ -3250,15 +3096,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^9.0.4": - version: 9.0.9 - resolution: "minimatch@npm:9.0.9" - dependencies: - brace-expansion: "npm:^2.0.2" - checksum: 10c0/0b6a58530dbb00361745aa6c8cffaba4c90f551afe7c734830bd95fd88ebf469dd7355a027824ea1d09e37181cfeb0a797fb17df60c15ac174303ac110eb7e86 - languageName: node - linkType: hard - "minipass-collect@npm:^2.0.1": version: 2.0.1 resolution: "minipass-collect@npm:2.0.1" @@ -3319,7 +3156,7 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2, minipass@npm:^7.1.3": +"minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2, minipass@npm:^7.1.3": version: 7.1.3 resolution: "minipass@npm:7.1.3" checksum: 10c0/539da88daca16533211ea5a9ee98dc62ff5742f531f54640dd34429e621955e91cc280a91a776026264b7f9f6735947629f920944e9c1558369e8bf22eb33fbb @@ -3547,7 +3384,7 @@ __metadata: languageName: node linkType: hard -"p-map@npm:^7.0.2, p-map@npm:^7.0.3": +"p-map@npm:^7.0.2, p-map@npm:^7.0.4": version: 7.0.4 resolution: "p-map@npm:7.0.4" checksum: 10c0/a5030935d3cb2919d7e89454d1ce82141e6f9955413658b8c9403cfe379283770ed3048146b44cde168aa9e8c716505f196d5689db0ae3ce9a71521a2fef3abd @@ -3564,13 +3401,6 @@ __metadata: languageName: node linkType: hard -"package-json-from-dist@npm:^1.0.0": - version: 1.0.1 - resolution: "package-json-from-dist@npm:1.0.1" - checksum: 10c0/62ba2785eb655fec084a257af34dbe24292ab74516d6aecef97ef72d4897310bc6898f6c85b5cd22770eaa1ce60d55a0230e150fb6a966e3ecd6c511e23d164b - languageName: node - linkType: hard - "parse-ms@npm:^4.0.0": version: 4.0.0 resolution: "parse-ms@npm:4.0.0" @@ -3585,16 +3415,6 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^1.11.1": - version: 1.11.1 - resolution: "path-scurry@npm:1.11.1" - dependencies: - lru-cache: "npm:^10.2.0" - minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" - checksum: 10c0/32a13711a2a505616ae1cc1b5076801e453e7aae6ac40ab55b388bb91b9d0547a52f5aaceff710ea400205f18691120d4431e520afbe4266b836fadede15872d - languageName: node - linkType: hard - "path-scurry@npm:^2.0.2": version: 2.0.2 resolution: "path-scurry@npm:2.0.2" @@ -3605,13 +3425,6 @@ __metadata: languageName: node linkType: hard -"path-type@npm:^6.0.0": - version: 6.0.0 - resolution: "path-type@npm:6.0.0" - checksum: 10c0/55baa8b1187d6dc683d5a9cfcc866168d6adff58e5db91126795376d818eee46391e00b2a4d53e44d844c7524a7d96aa68cc68f4f3e500d3d069a39e6535481c - languageName: node - linkType: hard - "picomatch@npm:^2.3.1": version: 2.3.1 resolution: "picomatch@npm:2.3.1" @@ -3642,12 +3455,12 @@ __metadata: languageName: node linkType: hard -"plur@npm:^5.1.0": - version: 5.1.0 - resolution: "plur@npm:5.1.0" +"plur@npm:^6.0.0": + version: 6.0.0 + resolution: "plur@npm:6.0.0" dependencies: - irregular-plurals: "npm:^3.3.0" - checksum: 10c0/26bb622b8545fcfd47bbf56fbcca66c08693708a232e403fa3589e00003c56c14231ac57c7588ca5db83ef4be1f61383402c4ea954000768f779f8aef6eb6da8 + irregular-plurals: "npm:^4.2.0" + checksum: 10c0/5c54badee70debea38e7153b197c1235847edc10952c3dd959c9610005f75013c2053be1cba87acde4531498d7a9e8b16c6b358f3940f05aa6e1aeedbc991b7a languageName: node linkType: hard @@ -3660,7 +3473,7 @@ __metadata: languageName: node linkType: hard -"pretty-ms@npm:^9.2.0": +"pretty-ms@npm:^9.3.0": version: 9.3.0 resolution: "pretty-ms@npm:9.3.0" dependencies: @@ -3700,13 +3513,6 @@ __metadata: languageName: node linkType: hard -"require-directory@npm:^2.1.1": - version: 2.1.1 - resolution: "require-directory@npm:2.1.1" - checksum: 10c0/83aa76a7bc1531f68d92c75a2ca2f54f1b01463cb566cf3fbc787d0de8be30c9dbc211d1d46be3497dac5785fe296f2dd11d531945ac29730643357978966e99 - languageName: node - linkType: hard - "resolve-cwd@npm:^3.0.0": version: 3.0.0 resolution: "resolve-cwd@npm:3.0.0" @@ -3832,16 +3638,6 @@ __metadata: languageName: node linkType: hard -"slice-ansi@npm:^5.0.0": - version: 5.0.0 - resolution: "slice-ansi@npm:5.0.0" - dependencies: - ansi-styles: "npm:^6.0.0" - is-fullwidth-code-point: "npm:^4.0.0" - checksum: 10c0/2d4d40b2a9d5cf4e8caae3f698fe24ae31a4d778701724f578e984dcb485ec8c49f0c04dab59c401821e80fcdfe89cace9c66693b0244e40ec485d72e543914f - languageName: node - linkType: hard - "slice-ansi@npm:^7.1.0": version: 7.1.2 resolution: "slice-ansi@npm:7.1.2" @@ -3922,29 +3718,7 @@ __metadata: languageName: node linkType: hard -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": - version: 4.2.3 - resolution: "string-width@npm:4.2.3" - dependencies: - emoji-regex: "npm:^8.0.0" - is-fullwidth-code-point: "npm:^3.0.0" - strip-ansi: "npm:^6.0.1" - checksum: 10c0/1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b - languageName: node - linkType: hard - -"string-width@npm:^5.0.1, string-width@npm:^5.1.2": - version: 5.1.2 - resolution: "string-width@npm:5.1.2" - dependencies: - eastasianwidth: "npm:^0.2.0" - emoji-regex: "npm:^9.2.2" - strip-ansi: "npm:^7.0.1" - checksum: 10c0/ab9c4264443d35b8b923cbdd513a089a60de339216d3b0ed3be3ba57d6880e1a192b70ae17225f764d7adbf5994e9bb8df253a944736c15a0240eff553c678ca - languageName: node - linkType: hard - -"string-width@npm:^7.0.0": +"string-width@npm:^7.0.0, string-width@npm:^7.2.0": version: 7.2.0 resolution: "string-width@npm:7.2.0" dependencies: @@ -3965,15 +3739,6 @@ __metadata: languageName: node linkType: hard -"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": - version: 6.0.1 - resolution: "strip-ansi@npm:6.0.1" - dependencies: - ansi-regex: "npm:^5.0.1" - checksum: 10c0/1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952 - languageName: node - linkType: hard - "strip-ansi@npm:^7.0.1, strip-ansi@npm:^7.1.0, strip-ansi@npm:^7.1.2": version: 7.2.0 resolution: "strip-ansi@npm:7.2.0" @@ -4108,23 +3873,23 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.9.2": - version: 5.9.3 - resolution: "typescript@npm:5.9.3" +"typescript@npm:^6.0.2": + version: 6.0.2 + resolution: "typescript@npm:6.0.2" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/6bd7552ce39f97e711db5aa048f6f9995b53f1c52f7d8667c1abdc1700c68a76a308f579cd309ce6b53646deb4e9a1be7c813a93baaf0a28ccd536a30270e1c5 + checksum: 10c0/4b860b0bf87cc0fee0f66d8ef2640b5a8a8a8c74d1129adb82e389e5f97124383823c47946bef8a73ede371461143a3aa8544399d2133c7b2e4f07e81860af7f languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5.9.2#optional!builtin": - version: 5.9.3 - resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" +"typescript@patch:typescript@npm%3A^6.0.2#optional!builtin": + version: 6.0.2 + resolution: "typescript@patch:typescript@npm%3A6.0.2#optional!builtin::version=6.0.2&hash=5786d5" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/ad09fdf7a756814dce65bc60c1657b40d44451346858eea230e10f2e95a289d9183b6e32e5c11e95acc0ccc214b4f36289dcad4bf1886b0adb84d711d336a430 + checksum: 10c0/49f0b84fc6ca55653e77752b8a61beabc09ee3dae5d965c31596225aa6ef213c5727b1d2e895b900416dc603854ba0872ac4a812c2a4ed6793a601f9c675de02 languageName: node linkType: hard @@ -4135,10 +3900,10 @@ __metadata: languageName: node linkType: hard -"unicorn-magic@npm:^0.3.0": - version: 0.3.0 - resolution: "unicorn-magic@npm:0.3.0" - checksum: 10c0/0a32a997d6c15f1c2a077a15b1c4ca6f268d574cf5b8975e778bb98e6f8db4ef4e86dfcae4e158cd4c7e38fb4dd383b93b13eefddc7f178dea13d3ac8a603271 +"unicorn-magic@npm:^0.4.0": + version: 0.4.0 + resolution: "unicorn-magic@npm:0.4.0" + checksum: 10c0/cd6eff90967a5528dfa2016bdb5b38b0cd64c8558f9ba04fb5c2c23f3a232a67dfe2bfa4c45af3685d5f1a40dbac6a36d48e053f80f97ae4da1e0f6a55431685 languageName: node linkType: hard @@ -4224,28 +3989,6 @@ __metadata: languageName: node linkType: hard -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": - version: 7.0.0 - resolution: "wrap-ansi@npm:7.0.0" - dependencies: - ansi-styles: "npm:^4.0.0" - string-width: "npm:^4.1.0" - strip-ansi: "npm:^6.0.0" - checksum: 10c0/d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da - languageName: node - linkType: hard - -"wrap-ansi@npm:^8.1.0": - version: 8.1.0 - resolution: "wrap-ansi@npm:8.1.0" - dependencies: - ansi-styles: "npm:^6.1.0" - string-width: "npm:^5.0.1" - strip-ansi: "npm:^7.0.1" - checksum: 10c0/138ff58a41d2f877eae87e3282c0630fc2789012fc1af4d6bd626eeb9a2f9a65ca92005e6e69a75c7b85a68479fe7443c7dbe1eb8fbaa681a4491364b7c55c60 - languageName: node - linkType: hard - "wrap-ansi@npm:^9.0.0": version: 9.0.2 resolution: "wrap-ansi@npm:9.0.2" @@ -4257,13 +4000,12 @@ __metadata: languageName: node linkType: hard -"write-file-atomic@npm:^6.0.0": - version: 6.0.0 - resolution: "write-file-atomic@npm:6.0.0" +"write-file-atomic@npm:^7.0.0": + version: 7.0.1 + resolution: "write-file-atomic@npm:7.0.1" dependencies: - imurmurhash: "npm:^0.1.4" signal-exit: "npm:^4.0.1" - checksum: 10c0/ae2f1c27474758a9aca92037df6c1dd9cb94c4e4983451210bd686bfe341f142662f6aa5913095e572ab037df66b1bfe661ed4ce4c0369ed0e8219e28e141786 + checksum: 10c0/69cebb64945e22928a24ae7e2a55bf54438c92d6f657c1fa5e96b7c7a50f6c022e7454ab5c259079bb35f60296242f3a21234c79320d64a8ad57675b56a2098d languageName: node linkType: hard @@ -4297,24 +4039,23 @@ __metadata: languageName: node linkType: hard -"yargs-parser@npm:^21.1.1": - version: 21.1.1 - resolution: "yargs-parser@npm:21.1.1" - checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 +"yargs-parser@npm:^22.0.0": + version: 22.0.0 + resolution: "yargs-parser@npm:22.0.0" + checksum: 10c0/cb7ef81759c4271cb1d96b9351dbbc9a9ce35d3e1122d2b739bf6c432603824fa02c67cc12dcef6ea80283379d63495686e8f41cc7b06c6576e792aba4d33e1c languageName: node linkType: hard -"yargs@npm:^17.7.2": - version: 17.7.2 - resolution: "yargs@npm:17.7.2" +"yargs@npm:^18.0.0": + version: 18.0.0 + resolution: "yargs@npm:18.0.0" dependencies: - cliui: "npm:^8.0.1" + cliui: "npm:^9.0.1" escalade: "npm:^3.1.1" get-caller-file: "npm:^2.0.5" - require-directory: "npm:^2.1.1" - string-width: "npm:^4.2.3" + string-width: "npm:^7.2.0" y18n: "npm:^5.0.5" - yargs-parser: "npm:^21.1.1" - checksum: 10c0/ccd7e723e61ad5965fffbb791366db689572b80cca80e0f96aad968dfff4156cd7cd1ad18607afe1046d8241e6fb2d6c08bf7fa7bfb5eaec818735d8feac8f05 + yargs-parser: "npm:^22.0.0" + checksum: 10c0/bf290e4723876ea9c638c786a5c42ac28e03c9ca2325e1424bf43b94e5876456292d3ed905b853ebbba6daf43ed29e772ac2a6b3c5fb1b16533245d6211778f3 languageName: node linkType: hard From cdad5a44335561fba50bfd2b0747df6f1a3038d3 Mon Sep 17 00:00:00 2001 From: Samuel Mwangi Date: Sat, 28 Mar 2026 17:54:23 +0300 Subject: [PATCH 02/15] Refactor server implementation to support both TCP and IPC servers. --- Cargo.toml | 1 + examples/hello.js | 11 +++ hyperjs-core/dts-header.d.ts | 3 + hyperjs-core/index.d.ts | 83 +++++++++++++++- index.js | 27 ++++++ index.ts | 31 ++++++ package.json | 9 +- src/server/_listen_tcp.rs | 110 +++++++++++++++++++++ src/server/create_handler_task.rs | 23 +++++ src/server/listen_ipc.rs | 149 +++++++++++++++++++++++++++++ src/server/listen_tcp.rs | 154 ++++++++++++++++++++++++++++++ src/server/mod.rs | 96 ++++--------------- src/server/register_middleware.rs | 26 +++++ src/server/register_route.rs | 34 +++++++ src/server/systemd_notify.rs | 11 +++ tsconfig.json | 5 +- 16 files changed, 687 insertions(+), 86 deletions(-) create mode 100644 examples/hello.js create mode 100644 hyperjs-core/dts-header.d.ts create mode 100644 index.js create mode 100644 index.ts create mode 100644 src/server/_listen_tcp.rs create mode 100644 src/server/create_handler_task.rs create mode 100644 src/server/listen_ipc.rs create mode 100644 src/server/listen_tcp.rs create mode 100644 src/server/register_middleware.rs create mode 100644 src/server/register_route.rs create mode 100644 src/server/systemd_notify.rs diff --git a/Cargo.toml b/Cargo.toml index 75d2f75..472fed5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ rustls-acme = { version = "0.15.1", features = ["tokio"] } serde_json = "1.0.148" serde_qs = "1.0.0" serde_urlencoded = "0.7.1" +socket2 = "0.6.3" tempfile = "3.24.0" tokio-stream = { version = "0.1.18", features = ["net"] } urlencoding = "2.1.3" diff --git a/examples/hello.js b/examples/hello.js new file mode 100644 index 0000000..ad2b8dc --- /dev/null +++ b/examples/hello.js @@ -0,0 +1,11 @@ +const hyperjs = require('../index') +const app = hyperjs() +const port = 3000 + +app.get('/', (req, res) => { + res.send('Hello World!') +}) + +app.listen(port, () => { + console.log(`Example app listening on port ${port}`) +}) diff --git a/hyperjs-core/dts-header.d.ts b/hyperjs-core/dts-header.d.ts new file mode 100644 index 0000000..9a72cea --- /dev/null +++ b/hyperjs-core/dts-header.d.ts @@ -0,0 +1,3 @@ +export type JsHandlerFn = (req: Request, res: Response) => boolean | void | Promise +export type JsVerifyFn = (req: Request, res: Response, buf: Buffer, encoding: String) => void +export type JsSetHeadersFn = (res: Response, path: string, stat: any) => void diff --git a/hyperjs-core/index.d.ts b/hyperjs-core/index.d.ts index e8c83ad..ba46d82 100644 --- a/hyperjs-core/index.d.ts +++ b/hyperjs-core/index.d.ts @@ -1,5 +1,6 @@ -/* auto-generated by NAPI-RS */ -/* eslint-disable */ +export type JsHandlerFn = (req: Request, res: Response) => boolean | void | Promise +export type JsVerifyFn = (req: Request, res: Response, buf: Buffer, encoding: String) => void +export type JsSetHeadersFn = (res: Response, path: string, stat: any) => void export declare class Bytes { constructor() static fromOwner(owner: Buffer): Bytes @@ -763,6 +764,8 @@ export declare class Response { /** HTTP Server that integrates with JavaScript handlers via Router */ export declare class Server { + listenIpc(options: IpcServerListenOptions, callback: (arg: string) => void): void + listenTcp(options: TcpServerListenOptions, callback: (arg: string) => void): void /** Create a new server with a router */ constructor() delete(route: string, handler: JsHandlerFn): void @@ -981,6 +984,32 @@ export interface DownloadOptions { immutable?: boolean } +export interface IpcServerListenOptions { + /** + * The maximum number of pending connections that can be queued before the OS + * starts refusing new ones. Passed directly to the underlying `listen(2)` syscall. + * + * A value of `0` lets the OS choose a reasonable default. + * + * Default = 0 + */ + backlog: number + /** + * Unix domain socket path the server should listen on. + * + * Ignored if [`port`] is also specified — TCP takes precedence. + * + * Defaults to `None`. + * + * [`port`]: Self::port + */ + path: string + /** Makes the pipe readable for all users. Default: false. */ + readableAll?: boolean + /** Makes the pipe writable for all users. Default: false. */ + writableAll?: boolean +} + export interface JsCookieParserOptions { /** * If `true`, the cookie value will be percent-decoded @@ -1354,3 +1383,53 @@ export interface SendFileOptions { } export declare function serializeNapiObject(obj: object): string + +export interface TcpServerListenOptions { + /** + * The maximum number of pending connections that can be queued before the OS + * starts refusing new ones. Passed directly to the underlying `listen(2)` syscall. + * + * A value of `0` lets the OS choose a reasonable default. + * + * Default = 0 + */ + backlog?: number + /** + * The local address the server will bind to. + * + * Defaults to `"0.0.0.0"` (all IPv4 interfaces) when [`ipv6_only`] is `false`, + * or `"::"` (all IPv6 interfaces) when [`ipv6_only`] is `true`. + * + * [`ipv6_only`]: Self::ipv6_only + */ + host?: string + /** + * When `true`, opens an IPv6-only socket (`AF_INET6` with `IPV6_V6ONLY` enabled), + * rejecting any IPv4 connections. When `false` (the default), a dual-stack socket + * is used that accepts both IPv4 and IPv6 connections. + * + * Defaults to `false`. + */ + ipv6Only?: boolean + /** + * When `true`, sets `SO_REUSEPORT` on the socket, allowing multiple sockets to + * bind to the same address and port. Useful for load-balancing incoming connections + * across several threads or processes. + * + * Not supported on all platforms (e.g. older Windows versions). + * + * Defaults to `false`. + */ + reusePort?: boolean + /** + * The TCP port the server should bind to. When set, takes precedence over [`path`]. + * + * A value of `0` asks the OS to assign an available ephemeral port, which can then + * be retrieved after binding. + * + * Defaults to `0`. + * + * [`path`]: Self::path + */ + port?: number +} diff --git a/index.js b/index.js new file mode 100644 index 0000000..9cf1874 --- /dev/null +++ b/index.js @@ -0,0 +1,27 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const index_1 = require("./hyperjs-core/index"); +// interface ServerListenOptions { +// backlog: number, +// exclusive: boolean, +// host: string, +// ipv6Only: boolean, +// reusePort: boolean, +// path: string, +// port: number, +// readableAll: boolean, +// signal: AbortSignal, +// writableAll: boolean +// } +// class TestServer { +// listen(handle, backlog: number, callback); +// listen(options: ServerListenOptions, callback); +// listen(path: string, backlog: number, callback); +// listen(port: number, host: string, backlog: number, callback); +// listen() {} +// } +function hyperjs() { + return new index_1.Server(); +} +exports.default = hyperjs; +module.exports = Object.assign(hyperjs, { Request: index_1.Request, Response: index_1.Response }); diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..5fd7623 --- /dev/null +++ b/index.ts @@ -0,0 +1,31 @@ +import { Server, Request, Response } from './hyperjs-core/index' + +// interface ServerListenOptions { +// backlog: number, +// exclusive: boolean, +// host: string, +// ipv6Only: boolean, +// reusePort: boolean, +// path: string, +// port: number, +// readableAll: boolean, +// signal: AbortSignal, +// writableAll: boolean +// } + +// class TestServer { +// listen(handle, backlog: number, callback); +// listen(options: ServerListenOptions, callback); +// listen(path: string, backlog: number, callback); +// listen(port: number, host: string, backlog: number, callback); + +// listen() {} + +// } + +function hyperjs() { + return new Server() +} + +export default hyperjs +module.exports = Object.assign(hyperjs, { Request, Response }) diff --git a/package.json b/package.json index 3a9b1dc..97a2ad7 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,7 @@ ], "files": [ "index.d.ts", - "index.js", - "browser.js" + "index.js" ], "napi": { "binaryName": "hyperjs", @@ -38,7 +37,8 @@ "aarch64-unknown-linux-musl", "aarch64-pc-windows-msvc", "armv7-linux-androideabi" - ] + ], + "dtsHeaderFile": "hyperjs-core/dts-header.d.ts" }, "engines": { "node": ">= 6.14.2 < 7 || >= 8.11.2 < 9 || >= 9.11.0 < 10 || >= 10.0.0" @@ -49,7 +49,8 @@ }, "scripts": { "artifacts": "napi artifacts", - "build": "napi build --output-dir hyperjs-core --platform --release", + "build:js": "tsc -p ./tsconfig.json", + "build:core": "napi build --output-dir hyperjs-core --platform --release", "build:debug": "napi build --output-dir hyperjs-core --platform", "format": "run-p format:prettier format:rs format:toml", "format:prettier": "prettier . -w", diff --git a/src/server/_listen_tcp.rs b/src/server/_listen_tcp.rs new file mode 100644 index 0000000..3ff9a9d --- /dev/null +++ b/src/server/_listen_tcp.rs @@ -0,0 +1,110 @@ +use env_logger::Builder as EnvLoggerBuilder; +use futures::prelude::*; + +use hyper::rt::{Read, Write}; +use hyper::{server::conn::http1, service::service_fn}; +use hyper_util::rt::tokio::{TokioIo, TokioTimer}; +use log::LevelFilter; +use matchit::Router; +use napi::bindgen_prelude::*; +use napi::threadsafe_function::ThreadsafeFunctionCallMode; +use rustls_acme::AcmeConfig; +use rustls_acme::caches::DirCache; +use std::sync::Arc; +use tokio::net::TcpListener; +use tokio_stream::wrappers::TcpListenerStream; + +use super::{MiddlewareMeta, Server, ThreadsafeCallbackFn, handle_http_request}; + +impl Server { + pub(super) fn _listen_tcp(&self, create_tcp_listener: F, callback: ThreadsafeCallbackFn) + where + F: FnOnce() -> Fut + Send + 'static, + Fut: Future> + Send, + { + let router = Arc::new(self.router.clone()); + let middlewares = Arc::new(self.middlewares.clone()); + let acme_config_meta = self.acme_config_meta.clone(); + + EnvLoggerBuilder::new() + .filter_level(LevelFilter::max()) + .init(); + + std::thread::spawn(move || { + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async move { + let tcp_listener = create_tcp_listener().await.unwrap(); + let addr = tcp_listener.local_addr().unwrap(); + let server_status_message = format!("Server listening on {}", addr); + log::debug!("{server_status_message}"); + + systemd_notify(&server_status_message); + + callback.call(addr.to_string(), ThreadsafeFunctionCallMode::Blocking); + + match acme_config_meta { + Some(acme) => { + let tcp_stream = TcpListenerStream::new(tcp_listener); + + let mut tls_incoming = AcmeConfig::new(acme.domains) + .contact_push(format!("mailto:{}", acme.contact_email)) + .cache(DirCache::new(acme.cache_dir)) + .tokio_incoming(tcp_stream, Vec::new()); + + while let Some(tls) = tls_incoming.next().await { + let tls = match tls { + Ok(t) => t, + Err(e) => { + log::error!("TLS accept error: {}", e); + continue; + } + }; + + let io = TokioIo::new(tls); + let router = router.clone(); + let middlewares = middlewares.clone(); + + create_handler_task(io, router, middlewares); + } + } + None => loop { + let (socket, _) = tcp_listener.accept().await.unwrap(); + let io = TokioIo::new(socket); + let router = router.clone(); + let middlewares = middlewares.clone(); + + create_handler_task(Box::new(io), router, middlewares); + }, + } + }); + }); + } +} + +fn systemd_notify(server_status_message: &str) { + #[cfg(unix)] + { + use sd_notify::{NotifyState, notify}; + if let Err(e) = notify(&[NotifyState::Ready]) { + log::error!("Failed to notify systemd: {}", e); + } + + let _ = notify(&[NotifyState::Status(server_status_message)]); + } +} + +fn create_handler_task( + io: I, + router: Arc>, + middlewares: Arc>, +) { + tokio::task::spawn(async move { + let _ = http1::Builder::new() + .timer(TokioTimer::new()) + .serve_connection( + io, + service_fn(move |req| handle_http_request(req, router.clone(), middlewares.clone())), + ) + .await; + }); +} diff --git a/src/server/create_handler_task.rs b/src/server/create_handler_task.rs new file mode 100644 index 0000000..4f371d4 --- /dev/null +++ b/src/server/create_handler_task.rs @@ -0,0 +1,23 @@ +use hyper::rt::{Read, Write}; +use hyper::{server::conn::http1, service::service_fn}; +use hyper_util::rt::tokio::TokioTimer; +use matchit::Router; +use std::sync::Arc; + +use super::{MiddlewareMeta, handle_http_request::handle_http_request}; + +pub fn create_handler_task( + io: I, + router: Arc>, + middlewares: Arc>, +) { + tokio::task::spawn(async move { + let _ = http1::Builder::new() + .timer(TokioTimer::new()) + .serve_connection( + io, + service_fn(move |req| handle_http_request(req, router.clone(), middlewares.clone())), + ) + .await; + }); +} diff --git a/src/server/listen_ipc.rs b/src/server/listen_ipc.rs new file mode 100644 index 0000000..962f707 --- /dev/null +++ b/src/server/listen_ipc.rs @@ -0,0 +1,149 @@ +use std::{fs::Permissions, os::unix::fs::PermissionsExt, path::Path, sync::Arc}; + +use env_logger::Builder as EnvLoggerBuilder; +use hyper_util::rt::TokioIo; +use log::LevelFilter; +use napi::{ + bindgen_prelude::*, + threadsafe_function::{ThreadsafeCallContext, ThreadsafeFunctionCallMode}, +}; +use napi_derive::napi; +use tokio::net::UnixListener; + +use super::{Server, create_handler_task::create_handler_task, systemd_notify::systemd_notify}; +use crate::server::ThreadsafeCallbackFn; + +#[napi(object)] +pub struct IpcServerListenOptions { + /// The maximum number of pending connections that can be queued before the OS + /// starts refusing new ones. Passed directly to the underlying `listen(2)` syscall. + /// + /// A value of `0` lets the OS choose a reasonable default. + /// + /// Default = 0 + pub backlog: u32, + + /// Unix domain socket path the server should listen on. + /// + /// Ignored if [`port`] is also specified — TCP takes precedence. + /// + /// Defaults to `None`. + /// + /// [`port`]: Self::port + pub path: String, + + /// Makes the pipe readable for all users. Default: false. + pub readable_all: Option, + + /// Makes the pipe writable for all users. Default: false. + pub writable_all: Option, +} + +struct SetSocketPermissionsParams> { + path: P, + readable_all: bool, + writeable_all: bool, +} + +fn set_socket_permissions>(params: SetSocketPermissionsParams

) -> Result<()> { + let permissions_mode = match (params.readable_all, params.writeable_all) { + // owner read+write only + (false, false) => 0o600, + + // all users read+write — necessary for non-owner clients to connect + (true, true) => 0o666, + + // read without write is insufficient for clients to connect to a Unix socket + (true, false) => { + return Err(Error::from_reason( + "Invalid combination (readable_all = true, writeable_all = false): + connecting to a Unix socket requires both read and write permissions, + so granting read alone to all users does not enable them to connect.", + )); + } + + // write without read is insufficient for clients to connect to a Unix socket + (false, true) => { + return Err(Error::from_reason( + "Invalid combination (readable_all = false, writeable_all = true): + connecting to a Unix socket requires both read and write permissions, + so granting write alone to all users does not enable them to connect.", + )); + } + }; + let perm = Permissions::from_mode(permissions_mode); + std::fs::set_permissions(params.path, perm)?; + Ok(()) +} + +#[napi] +impl Server { + pub fn _listen_ipc(&self, ipc_listener: UnixListener, callback: ThreadsafeCallbackFn) { + let router = Arc::new(self.router.clone()); + let middlewares = Arc::new(self.middlewares.clone()); + + EnvLoggerBuilder::new() + .filter_level(LevelFilter::max()) + .init(); + + std::thread::spawn(move || { + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async move { + let addr = ipc_listener.local_addr().unwrap(); + let addr = addr + .as_pathname() + .and_then(|p| p.to_str()) + .or( + addr + .as_abstract_name() + .and_then(|bytes| str::from_utf8(bytes).ok()), + ) + .unwrap_or_default(); + let server_status_message = format!("Server listening on '{}'", addr); + log::debug!("{server_status_message}"); + + systemd_notify(&server_status_message); + + callback.call(addr.to_string(), ThreadsafeFunctionCallMode::Blocking); + + loop { + let (stream, _) = ipc_listener.accept().await.unwrap(); + let io = TokioIo::new(stream); + let router = router.clone(); + let middlewares = middlewares.clone(); + + create_handler_task(Box::new(io), router, middlewares); + } + }); + }); + } + + #[napi] + pub fn listen_ipc( + &self, + options: IpcServerListenOptions, + callback: Function, + ) -> Result<()> { + let ts_callback = callback.build_threadsafe_function().build_callback( + |ctx: ThreadsafeCallContext| { + #[allow(clippy::unit_arg)] + Ok(ctx.value) + }, + )?; + + let ipc_listener = UnixListener::bind(&options.path) + .map_err(|e| Error::from_reason(format!("Error creating IPC listener. {e}")))?; + + set_socket_permissions(SetSocketPermissionsParams { + path: options.path.to_owned(), + readable_all: options.readable_all.unwrap_or_default(), + writeable_all: options.writable_all.unwrap_or_default(), + })?; + + log::info!("IPC server listening on {}", options.path); + + self._listen_ipc(ipc_listener, ts_callback); + + Ok(()) + } +} diff --git a/src/server/listen_tcp.rs b/src/server/listen_tcp.rs new file mode 100644 index 0000000..b1d6aa6 --- /dev/null +++ b/src/server/listen_tcp.rs @@ -0,0 +1,154 @@ +use std::{ + future, + net::{IpAddr, SocketAddr}, +}; + +use napi::{bindgen_prelude::*, threadsafe_function::ThreadsafeCallContext}; +use napi_derive::napi; +use socket2::{Domain, Protocol, Socket, Type}; +use tokio::net::TcpListener; + +use super::Server; + +#[napi(object)] +pub struct TcpServerListenOptions { + /// The maximum number of pending connections that can be queued before the OS + /// starts refusing new ones. Passed directly to the underlying `listen(2)` syscall. + /// + /// A value of `0` lets the OS choose a reasonable default. + /// + /// Default = 0 + pub backlog: Option, + + /// The local address the server will bind to. + /// + /// Defaults to `"0.0.0.0"` (all IPv4 interfaces) when [`ipv6_only`] is `false`, + /// or `"::"` (all IPv6 interfaces) when [`ipv6_only`] is `true`. + /// + /// [`ipv6_only`]: Self::ipv6_only + pub host: Option, + + /// When `true`, opens an IPv6-only socket (`AF_INET6` with `IPV6_V6ONLY` enabled), + /// rejecting any IPv4 connections. When `false` (the default), a dual-stack socket + /// is used that accepts both IPv4 and IPv6 connections. + /// + /// Defaults to `false`. + pub ipv6_only: Option, + + /// When `true`, sets `SO_REUSEPORT` on the socket, allowing multiple sockets to + /// bind to the same address and port. Useful for load-balancing incoming connections + /// across several threads or processes. + /// + /// Not supported on all platforms (e.g. older Windows versions). + /// + /// Defaults to `false`. + pub reuse_port: Option, + + /// The TCP port the server should bind to. When set, takes precedence over [`path`]. + /// + /// A value of `0` asks the OS to assign an available ephemeral port, which can then + /// be retrieved after binding. + /// + /// Defaults to `0`. + /// + /// [`path`]: Self::path + pub port: Option, +} + +pub struct LibTcpServerListenOptions { + backlog: i32, + host: String, + ipv6_only: bool, + reuse_port: bool, + port: u16, +} + +impl From for LibTcpServerListenOptions { + fn from(options: TcpServerListenOptions) -> Self { + Self { + backlog: options.backlog.unwrap_or(0) as i32, + host: match options.ipv6_only.unwrap_or_default() { + true => "::".to_owned(), + false => "0.0.0.0".to_owned(), + }, + ipv6_only: options.ipv6_only.unwrap_or_default(), + reuse_port: options.ipv6_only.unwrap_or_default(), + port: options.port.unwrap_or(0), + } + } +} + +fn host_to_ip_addr(host: &str) -> Result { + host + .parse() + .map_err(|e| Error::new(Status::InvalidArg, format!("Invalid host: {e}"))) +} + +struct CreateTcpSocketParams { + reuse_port: bool, + ipv6_only: bool, +} + +fn create_tcp_socket(params: CreateTcpSocketParams) -> Result { + let domain = match params.ipv6_only { + true => Domain::IPV6, + false => Domain::IPV4, + }; + let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP)) + .map_err(|e| Error::from_reason(e.to_string()))?; + socket + .set_only_v6(params.ipv6_only) + .map_err(|e| Error::from_reason(e.to_string()))?; + socket + .set_reuse_port(params.reuse_port) + .map_err(|e| Error::from_reason(e.to_string()))?; + Ok(socket) +} + +struct CreateTcpListenerParams { + addr: SocketAddr, + backlog: i32, + socket: CreateTcpSocketParams, +} + +fn create_tcp_listener(params: CreateTcpListenerParams) -> Result { + let socket = create_tcp_socket(params.socket)?; + socket + .bind(¶ms.addr.into()) + .map_err(|e| Error::from_reason(e.to_string()))?; + socket + .listen(params.backlog) + .map_err(|e| Error::from_reason(e.to_string()))?; + TcpListener::from_std(socket.into()).map_err(|e| Error::from_reason(e.to_string())) +} + +#[napi] +impl Server { + #[napi] + pub fn listen_tcp( + &self, + options: TcpServerListenOptions, + callback: Function, + ) -> Result<()> { + let options: LibTcpServerListenOptions = options.into(); + + let create_tcp_listener_params = CreateTcpListenerParams { + addr: SocketAddr::from((host_to_ip_addr(&options.host)?, options.port)), + backlog: options.backlog, + socket: CreateTcpSocketParams { + ipv6_only: options.ipv6_only, + reuse_port: options.reuse_port, + }, + }; + let tcp_listener = create_tcp_listener(create_tcp_listener_params)?; + let create_tcp_listener = move || future::ready(Ok(tcp_listener)); + let ts_callback = callback.build_threadsafe_function().build_callback( + |ctx: ThreadsafeCallContext| { + #[allow(clippy::unit_arg)] + Ok(ctx.value) + }, + )?; + self._listen_tcp(create_tcp_listener, ts_callback); + Ok(()) + } +} diff --git a/src/server/mod.rs b/src/server/mod.rs index bcfefef..4d086e1 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,15 +1,21 @@ +mod _listen_tcp; +mod create_handler_task; mod get_next_id; mod handle_http_request; +mod listen_ipc; +mod listen_tcp; +mod register_middleware; +mod register_route; +mod systemd_notify; use env_logger::Builder as EnvLoggerBuilder; use futures::prelude::*; use hyper::Method as LibMethod; -use hyper::{server::conn::http1, service::service_fn}; -use hyper_util::rt::tokio::{TokioIo, TokioTimer}; +use hyper_util::rt::TokioIo; use log::LevelFilter; -use matchit::{InsertError, Router}; +use matchit::Router; use napi::bindgen_prelude::*; -use napi::threadsafe_function::{ThreadsafeCallContext, ThreadsafeFunction}; +use napi::threadsafe_function::ThreadsafeFunction; use napi_derive::napi; use rustls_acme::AcmeConfig; use rustls_acme::caches::DirCache; @@ -19,14 +25,16 @@ use tokio_stream::wrappers::TcpListenerStream; use crate::request::Request; use crate::response::Response; +use create_handler_task::create_handler_task; use handle_http_request::handle_http_request; +use systemd_notify::systemd_notify; // Global state for pending requests lazy_static::lazy_static! { static ref NEXT_ID: Arc> = Arc::new(std::sync::Mutex::new(0)); } -type JsHandlerFn<'a> = +pub type JsHandlerFn<'a> = Function<'a, FnArgs<(Request, Response)>, Either, Promise>>>; type ThreadsafeMiddlewareFn = ThreadsafeFunction< @@ -39,6 +47,8 @@ type ThreadsafeMiddlewareFn = ThreadsafeFunction< 0, >; +type ThreadsafeCallbackFn = ThreadsafeFunction; + #[derive(Clone)] pub struct MiddlewareMeta { /// The string used to register the middleware in the router. @@ -79,48 +89,6 @@ pub struct Server { acme_config_meta: Option, } -impl Server { - fn register_middleware( - &mut self, - route: Option, - handler: JsHandlerFn, - _env: Env, - ) -> Result<()> { - let tsfn = handler - .build_threadsafe_function() - .build_callback(|ctx: ThreadsafeCallContext>| Ok(ctx.value))?; - self.middlewares.push(MiddlewareMeta { - route, - handler: Arc::new(tsfn), - method: None, - }); - Ok(()) - } - - fn register_route( - &mut self, - route: String, - handler: JsHandlerFn, - method: LibMethod, - ) -> Result<()> { - let tsfn = handler - .build_threadsafe_function() - .build_callback(|ctx: ThreadsafeCallContext>| Ok(ctx.value))?; - if let Err(e) = self.router.insert(route.to_owned(), route.to_owned()) { - match e { - InsertError::Conflict { .. } => {} - _ => return Err(Error::new(Status::GenericFailure, e.to_string())), - } - } - self.middlewares.push(MiddlewareMeta { - route: Some(route), - handler: Arc::new(tsfn), - method: Some(method), - }); - Ok(()) - } -} - #[napi] impl Server { /// Create a new server with a router @@ -180,15 +148,7 @@ impl Server { let server_status_message = format!("Server listening on {}", addr); log::debug!("{server_status_message}"); - #[cfg(unix)] - { - use sd_notify::{NotifyState, notify}; - if let Err(e) = notify(&[NotifyState::Ready]) { - log::error!("Failed to notify systemd: {}", e); - } - - let _ = notify(&[NotifyState::Status(&server_status_message)]); - } + systemd_notify(&server_status_message); match acme_config_meta { Some(acme) => { @@ -212,17 +172,7 @@ impl Server { let router = router.clone(); let middlewares = middlewares.clone(); - tokio::task::spawn(async move { - let _ = http1::Builder::new() - .timer(TokioTimer::new()) - .serve_connection( - io, - service_fn(move |req| { - handle_http_request(req, router.clone(), middlewares.clone()) - }), - ) - .await; - }); + create_handler_task(io, router, middlewares); } } None => loop { @@ -231,17 +181,7 @@ impl Server { let router = router.clone(); let middlewares = middlewares.clone(); - tokio::task::spawn(async move { - let _ = http1::Builder::new() - .timer(TokioTimer::new()) - .serve_connection( - io, - service_fn(move |req| { - handle_http_request(req, router.clone(), middlewares.clone()) - }), - ) - .await; - }); + create_handler_task(Box::new(io), router, middlewares); }, } }); diff --git a/src/server/register_middleware.rs b/src/server/register_middleware.rs new file mode 100644 index 0000000..545d7ac --- /dev/null +++ b/src/server/register_middleware.rs @@ -0,0 +1,26 @@ +use napi::bindgen_prelude::*; +use napi::threadsafe_function::ThreadsafeCallContext; +use std::sync::Arc; + +use super::{JsHandlerFn, MiddlewareMeta, Server}; +use crate::request::Request; +use crate::response::Response; + +impl Server { + pub(super) fn register_middleware( + &mut self, + route: Option, + handler: JsHandlerFn, + _env: Env, + ) -> Result<()> { + let tsfn = handler + .build_threadsafe_function() + .build_callback(|ctx: ThreadsafeCallContext>| Ok(ctx.value))?; + self.middlewares.push(MiddlewareMeta { + route, + handler: Arc::new(tsfn), + method: None, + }); + Ok(()) + } +} diff --git a/src/server/register_route.rs b/src/server/register_route.rs new file mode 100644 index 0000000..7eb6168 --- /dev/null +++ b/src/server/register_route.rs @@ -0,0 +1,34 @@ +use hyper::Method as LibMethod; +use matchit::InsertError; +use napi::bindgen_prelude::*; +use napi::threadsafe_function::ThreadsafeCallContext; +use std::sync::Arc; + +use super::{JsHandlerFn, MiddlewareMeta, Server}; +use crate::request::Request; +use crate::response::Response; + +impl Server { + pub(super) fn register_route( + &mut self, + route: String, + handler: JsHandlerFn, + method: LibMethod, + ) -> Result<()> { + let tsfn = handler + .build_threadsafe_function() + .build_callback(|ctx: ThreadsafeCallContext>| Ok(ctx.value))?; + if let Err(e) = self.router.insert(route.to_owned(), route.to_owned()) { + match e { + InsertError::Conflict { .. } => {} + _ => return Err(Error::new(Status::GenericFailure, e.to_string())), + } + } + self.middlewares.push(MiddlewareMeta { + route: Some(route), + handler: Arc::new(tsfn), + method: Some(method), + }); + Ok(()) + } +} diff --git a/src/server/systemd_notify.rs b/src/server/systemd_notify.rs new file mode 100644 index 0000000..29454c8 --- /dev/null +++ b/src/server/systemd_notify.rs @@ -0,0 +1,11 @@ +pub fn systemd_notify(server_status_message: &str) { + #[cfg(unix)] + { + use sd_notify::{NotifyState, notify}; + if let Err(e) = notify(&[NotifyState::Ready]) { + log::error!("Failed to notify systemd: {}", e); + } + + let _ = notify(&[NotifyState::Status(server_status_message)]); + } +} diff --git a/tsconfig.json b/tsconfig.json index 5056788..b36fc56 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,8 +2,9 @@ "compilerOptions": { "target": "ESNext", "strict": true, - "moduleResolution": "node", - "module": "CommonJS", + "moduleResolution": "NodeNext", + "module": "NodeNext", + "types": ["node"], "noUnusedLocals": true, "noUnusedParameters": true, "esModuleInterop": true, From f031878db7f579df8bbef50a98302051f734598b Mon Sep 17 00:00:00 2001 From: Samuel Mwangi Date: Sat, 28 Mar 2026 18:06:22 +0300 Subject: [PATCH 03/15] Update package version to 0.*.* as API is not reliabel. --- hyperjs-core/index.js | 104 ++++++------- index.js | 28 ++-- index.ts | 29 ++-- package.json | 2 +- yarn.lock | 334 +++++++++++++++++++----------------------- 5 files changed, 235 insertions(+), 262 deletions(-) diff --git a/hyperjs-core/index.js b/hyperjs-core/index.js index be2e07e..e6af579 100644 --- a/hyperjs-core/index.js +++ b/hyperjs-core/index.js @@ -77,8 +77,8 @@ function requireNative() { try { const binding = require('@saltcorn/hyperjs-android-arm64') const bindingPackageVersion = require('@saltcorn/hyperjs-android-arm64/package.json').version - if (bindingPackageVersion !== '1.0.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.0.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -93,8 +93,8 @@ function requireNative() { try { const binding = require('@saltcorn/hyperjs-android-arm-eabi') const bindingPackageVersion = require('@saltcorn/hyperjs-android-arm-eabi/package.json').version - if (bindingPackageVersion !== '1.0.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.0.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -114,8 +114,8 @@ function requireNative() { try { const binding = require('@saltcorn/hyperjs-win32-x64-gnu') const bindingPackageVersion = require('@saltcorn/hyperjs-win32-x64-gnu/package.json').version - if (bindingPackageVersion !== '1.0.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.0.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -130,8 +130,8 @@ function requireNative() { try { const binding = require('@saltcorn/hyperjs-win32-x64-msvc') const bindingPackageVersion = require('@saltcorn/hyperjs-win32-x64-msvc/package.json').version - if (bindingPackageVersion !== '1.0.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.0.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -147,8 +147,8 @@ function requireNative() { try { const binding = require('@saltcorn/hyperjs-win32-ia32-msvc') const bindingPackageVersion = require('@saltcorn/hyperjs-win32-ia32-msvc/package.json').version - if (bindingPackageVersion !== '1.0.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.0.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -163,8 +163,8 @@ function requireNative() { try { const binding = require('@saltcorn/hyperjs-win32-arm64-msvc') const bindingPackageVersion = require('@saltcorn/hyperjs-win32-arm64-msvc/package.json').version - if (bindingPackageVersion !== '1.0.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.0.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -182,8 +182,8 @@ function requireNative() { try { const binding = require('@saltcorn/hyperjs-darwin-universal') const bindingPackageVersion = require('@saltcorn/hyperjs-darwin-universal/package.json').version - if (bindingPackageVersion !== '1.0.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.0.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -198,8 +198,8 @@ function requireNative() { try { const binding = require('@saltcorn/hyperjs-darwin-x64') const bindingPackageVersion = require('@saltcorn/hyperjs-darwin-x64/package.json').version - if (bindingPackageVersion !== '1.0.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.0.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -214,8 +214,8 @@ function requireNative() { try { const binding = require('@saltcorn/hyperjs-darwin-arm64') const bindingPackageVersion = require('@saltcorn/hyperjs-darwin-arm64/package.json').version - if (bindingPackageVersion !== '1.0.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.0.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -234,8 +234,8 @@ function requireNative() { try { const binding = require('@saltcorn/hyperjs-freebsd-x64') const bindingPackageVersion = require('@saltcorn/hyperjs-freebsd-x64/package.json').version - if (bindingPackageVersion !== '1.0.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.0.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -250,8 +250,8 @@ function requireNative() { try { const binding = require('@saltcorn/hyperjs-freebsd-arm64') const bindingPackageVersion = require('@saltcorn/hyperjs-freebsd-arm64/package.json').version - if (bindingPackageVersion !== '1.0.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.0.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -271,8 +271,8 @@ function requireNative() { try { const binding = require('@saltcorn/hyperjs-linux-x64-musl') const bindingPackageVersion = require('@saltcorn/hyperjs-linux-x64-musl/package.json').version - if (bindingPackageVersion !== '1.0.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.0.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -287,8 +287,8 @@ function requireNative() { try { const binding = require('@saltcorn/hyperjs-linux-x64-gnu') const bindingPackageVersion = require('@saltcorn/hyperjs-linux-x64-gnu/package.json').version - if (bindingPackageVersion !== '1.0.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.0.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -305,8 +305,8 @@ function requireNative() { try { const binding = require('@saltcorn/hyperjs-linux-arm64-musl') const bindingPackageVersion = require('@saltcorn/hyperjs-linux-arm64-musl/package.json').version - if (bindingPackageVersion !== '1.0.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.0.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -321,8 +321,8 @@ function requireNative() { try { const binding = require('@saltcorn/hyperjs-linux-arm64-gnu') const bindingPackageVersion = require('@saltcorn/hyperjs-linux-arm64-gnu/package.json').version - if (bindingPackageVersion !== '1.0.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.0.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -339,8 +339,8 @@ function requireNative() { try { const binding = require('@saltcorn/hyperjs-linux-arm-musleabihf') const bindingPackageVersion = require('@saltcorn/hyperjs-linux-arm-musleabihf/package.json').version - if (bindingPackageVersion !== '1.0.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.0.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -355,8 +355,8 @@ function requireNative() { try { const binding = require('@saltcorn/hyperjs-linux-arm-gnueabihf') const bindingPackageVersion = require('@saltcorn/hyperjs-linux-arm-gnueabihf/package.json').version - if (bindingPackageVersion !== '1.0.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.0.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -373,8 +373,8 @@ function requireNative() { try { const binding = require('@saltcorn/hyperjs-linux-loong64-musl') const bindingPackageVersion = require('@saltcorn/hyperjs-linux-loong64-musl/package.json').version - if (bindingPackageVersion !== '1.0.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.0.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -389,8 +389,8 @@ function requireNative() { try { const binding = require('@saltcorn/hyperjs-linux-loong64-gnu') const bindingPackageVersion = require('@saltcorn/hyperjs-linux-loong64-gnu/package.json').version - if (bindingPackageVersion !== '1.0.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.0.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -407,8 +407,8 @@ function requireNative() { try { const binding = require('@saltcorn/hyperjs-linux-riscv64-musl') const bindingPackageVersion = require('@saltcorn/hyperjs-linux-riscv64-musl/package.json').version - if (bindingPackageVersion !== '1.0.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.0.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -423,8 +423,8 @@ function requireNative() { try { const binding = require('@saltcorn/hyperjs-linux-riscv64-gnu') const bindingPackageVersion = require('@saltcorn/hyperjs-linux-riscv64-gnu/package.json').version - if (bindingPackageVersion !== '1.0.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.0.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -440,8 +440,8 @@ function requireNative() { try { const binding = require('@saltcorn/hyperjs-linux-ppc64-gnu') const bindingPackageVersion = require('@saltcorn/hyperjs-linux-ppc64-gnu/package.json').version - if (bindingPackageVersion !== '1.0.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.0.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -456,8 +456,8 @@ function requireNative() { try { const binding = require('@saltcorn/hyperjs-linux-s390x-gnu') const bindingPackageVersion = require('@saltcorn/hyperjs-linux-s390x-gnu/package.json').version - if (bindingPackageVersion !== '1.0.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.0.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -476,8 +476,8 @@ function requireNative() { try { const binding = require('@saltcorn/hyperjs-openharmony-arm64') const bindingPackageVersion = require('@saltcorn/hyperjs-openharmony-arm64/package.json').version - if (bindingPackageVersion !== '1.0.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.0.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -492,8 +492,8 @@ function requireNative() { try { const binding = require('@saltcorn/hyperjs-openharmony-x64') const bindingPackageVersion = require('@saltcorn/hyperjs-openharmony-x64/package.json').version - if (bindingPackageVersion !== '1.0.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.0.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { @@ -508,8 +508,8 @@ function requireNative() { try { const binding = require('@saltcorn/hyperjs-openharmony-arm') const bindingPackageVersion = require('@saltcorn/hyperjs-openharmony-arm/package.json').version - if (bindingPackageVersion !== '1.0.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.0.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.1.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.1.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { diff --git a/index.js b/index.js index 9cf1874..bd0d5ea 100644 --- a/index.js +++ b/index.js @@ -2,22 +2,22 @@ Object.defineProperty(exports, "__esModule", { value: true }); const index_1 = require("./hyperjs-core/index"); // interface ServerListenOptions { -// backlog: number, -// exclusive: boolean, -// host: string, -// ipv6Only: boolean, -// reusePort: boolean, -// path: string, -// port: number, -// readableAll: boolean, -// signal: AbortSignal, -// writableAll: boolean +// backlog: number +// exclusive: boolean +// host: string +// ipv6Only: boolean +// reusePort: boolean +// path: string +// port: number +// readableAll: boolean +// signal: AbortSignal +// writableAll: boolean // } // class TestServer { -// listen(handle, backlog: number, callback); -// listen(options: ServerListenOptions, callback); -// listen(path: string, backlog: number, callback); -// listen(port: number, host: string, backlog: number, callback); +// listen(handle, backlog: number, callback) +// listen(options: ServerListenOptions, callback) +// listen(path: string, backlog: number, callback) +// listen(port: number, host: string, backlog: number, callback) // listen() {} // } function hyperjs() { diff --git a/index.ts b/index.ts index 5fd7623..0d05150 100644 --- a/index.ts +++ b/index.ts @@ -1,26 +1,25 @@ import { Server, Request, Response } from './hyperjs-core/index' // interface ServerListenOptions { -// backlog: number, -// exclusive: boolean, -// host: string, -// ipv6Only: boolean, -// reusePort: boolean, -// path: string, -// port: number, -// readableAll: boolean, -// signal: AbortSignal, -// writableAll: boolean +// backlog: number +// exclusive: boolean +// host: string +// ipv6Only: boolean +// reusePort: boolean +// path: string +// port: number +// readableAll: boolean +// signal: AbortSignal +// writableAll: boolean // } // class TestServer { -// listen(handle, backlog: number, callback); -// listen(options: ServerListenOptions, callback); -// listen(path: string, backlog: number, callback); -// listen(port: number, host: string, backlog: number, callback); +// listen(handle, backlog: number, callback) +// listen(options: ServerListenOptions, callback) +// listen(path: string, backlog: number, callback) +// listen(port: number, host: string, backlog: number, callback) // listen() {} - // } function hyperjs() { diff --git a/package.json b/package.json index 97a2ad7..040e9e3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@saltcorn/hyperjs", - "version": "1.0.4", + "version": "0.1.0", "description": "Template project for writing node package with napi-rs", "main": "index.js", "repository": { diff --git a/yarn.lock b/yarn.lock index 7b5d40d..d248a57 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,22 +5,22 @@ __metadata: version: 8 cacheKey: 10c0 -"@emnapi/core@npm:^1.5.0, @emnapi/core@npm:^1.7.1": - version: 1.9.0 - resolution: "@emnapi/core@npm:1.9.0" +"@emnapi/core@npm:^1.5.0": + version: 1.9.1 + resolution: "@emnapi/core@npm:1.9.1" dependencies: "@emnapi/wasi-threads": "npm:1.2.0" tslib: "npm:^2.4.0" - checksum: 10c0/defbfa5861aa5ff1346dbc6a19df50d727ae76ae276a31a97b178db8eecae0c5179976878087b43ac2441750e40e6c50e465280383256deb16dd2fb167dd515c + checksum: 10c0/00e7a99a2bc3ad908ca8272ba861a934da87dffa8797a41316c4a3b571a1e4d2743e2fa14b1a0f131fa4a3c2018ddb601cd2a8cb7f574fa940af696df3c2fe8d languageName: node linkType: hard -"@emnapi/runtime@npm:^1.5.0, @emnapi/runtime@npm:^1.7.1": - version: 1.9.0 - resolution: "@emnapi/runtime@npm:1.9.0" +"@emnapi/runtime@npm:^1.5.0": + version: 1.9.1 + resolution: "@emnapi/runtime@npm:1.9.1" dependencies: tslib: "npm:^2.4.0" - checksum: 10c0/f825e53b2d3f9d31fd880e669197d006bb5158c3a52ab25f0546f3d52ac58eb539a4bd1dcc378af6c10d202956fa064b28ab7b572a76de58972c0b8656a692ef + checksum: 10c0/750edca117e0363ab2de10622f8ee60e57d8690c2f29c49704813da5cd627c641798d7f3cb0d953c62fdc71688e02e333ddbf2c1204f38b47e3e40657332a6f5 languageName: node linkType: hard @@ -216,11 +216,9 @@ __metadata: linkType: hard "@gar/promise-retry@npm:^1.0.0": - version: 1.0.2 - resolution: "@gar/promise-retry@npm:1.0.2" - dependencies: - retry: "npm:^0.13.1" - checksum: 10c0/748a84fb0ab962f7867966f21dc24d1872c53c1656dd3352320fe69ad3b2043f2dfdb3be024c7636ce4904c5ba1da22d0f3558e489c3de578f5bb520f062d0fd + version: 1.0.3 + resolution: "@gar/promise-retry@npm:1.0.3" + checksum: 10c0/885b02c8b0d75b2d215da25f3b639158c4fbe8fefe0d79163304534b9a6d0710db4b7699f7cd3cc1a730792bff04cbe19f4850a62d3e105a663eaeec88f38332 languageName: node linkType: hard @@ -492,8 +490,8 @@ __metadata: linkType: hard "@napi-rs/cli@npm:^3.2.0": - version: 3.5.1 - resolution: "@napi-rs/cli@npm:3.5.1" + version: 3.6.0 + resolution: "@napi-rs/cli@npm:3.6.0" dependencies: "@inquirer/prompts": "npm:^8.0.0" "@napi-rs/cross-toolchain": "npm:^1.0.3" @@ -501,7 +499,7 @@ __metadata: "@octokit/rest": "npm:^22.0.1" clipanion: "npm:^4.0.0-rc.4" colorette: "npm:^2.0.20" - emnapi: "npm:^1.7.1" + emnapi: "npm:^1.9.1" es-toolkit: "npm:^1.41.0" js-yaml: "npm:^4.1.0" obug: "npm:^2.0.0" @@ -515,7 +513,7 @@ __metadata: bin: napi: dist/cli.js napi-raw: cli.mjs - checksum: 10c0/b67d057a052a28917c90653101dfcff9e7f60342b1120f4613d571533cf8080116d97d9825ad88d672c79a5165a0edf7aec4eb4db702464c0551ed48c1b81adf + checksum: 10c0/9956e4fb95e302545d54a725427521bc411dd5bceb1878e54e2e30ef2f1c959869d235e58755aa0e21896d0c489f80059320688e09a1565942c014bd167fd795 languageName: node linkType: hard @@ -915,13 +913,14 @@ __metadata: linkType: hard "@napi-rs/wasm-runtime@npm:^1.0.3, @napi-rs/wasm-runtime@npm:^1.0.7": - version: 1.1.1 - resolution: "@napi-rs/wasm-runtime@npm:1.1.1" + version: 1.1.2 + resolution: "@napi-rs/wasm-runtime@npm:1.1.2" dependencies: - "@emnapi/core": "npm:^1.7.1" - "@emnapi/runtime": "npm:^1.7.1" "@tybys/wasm-util": "npm:^0.10.1" - checksum: 10c0/04d57b67e80736e41fe44674a011878db0a8ad893f4d44abb9d3608debb7c174224cba2796ed5b0c1d367368159f3ca6be45f1c59222f70e32ddc880f803d447 + peerDependencies: + "@emnapi/core": ^1.7.1 + "@emnapi/runtime": ^1.7.1 + checksum: 10c0/725c30ec9c480a8d0c1a6a4ce31dc6c830365d485e23ad560e143d1cb9db89a0c95fbb5b9d53c07121729817a3683db6f1ab65d7e4f38fa7482a11b15ef6c6fd languageName: node linkType: hard @@ -1115,6 +1114,13 @@ __metadata: languageName: node linkType: hard +"@npmcli/redact@npm:^4.0.0": + version: 4.0.0 + resolution: "@npmcli/redact@npm:4.0.0" + checksum: 10c0/a1e9ba9c70a6b40e175bda2c3dd8cfdaf096e6b7f7a132c855c083c8dfe545c3237cd56702e2e6627a580b1d63373599d49a1192c4078a85bf47bbde824df31c + languageName: node + linkType: hard + "@octokit/auth-token@npm:^6.0.0": version: 6.0.0 resolution: "@octokit/auth-token@npm:6.0.0" @@ -1422,135 +1428,135 @@ __metadata: languageName: node linkType: hard -"@oxlint/binding-android-arm-eabi@npm:1.56.0": - version: 1.56.0 - resolution: "@oxlint/binding-android-arm-eabi@npm:1.56.0" +"@oxlint/binding-android-arm-eabi@npm:1.57.0": + version: 1.57.0 + resolution: "@oxlint/binding-android-arm-eabi@npm:1.57.0" conditions: os=android & cpu=arm languageName: node linkType: hard -"@oxlint/binding-android-arm64@npm:1.56.0": - version: 1.56.0 - resolution: "@oxlint/binding-android-arm64@npm:1.56.0" +"@oxlint/binding-android-arm64@npm:1.57.0": + version: 1.57.0 + resolution: "@oxlint/binding-android-arm64@npm:1.57.0" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@oxlint/binding-darwin-arm64@npm:1.56.0": - version: 1.56.0 - resolution: "@oxlint/binding-darwin-arm64@npm:1.56.0" +"@oxlint/binding-darwin-arm64@npm:1.57.0": + version: 1.57.0 + resolution: "@oxlint/binding-darwin-arm64@npm:1.57.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@oxlint/binding-darwin-x64@npm:1.56.0": - version: 1.56.0 - resolution: "@oxlint/binding-darwin-x64@npm:1.56.0" +"@oxlint/binding-darwin-x64@npm:1.57.0": + version: 1.57.0 + resolution: "@oxlint/binding-darwin-x64@npm:1.57.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@oxlint/binding-freebsd-x64@npm:1.56.0": - version: 1.56.0 - resolution: "@oxlint/binding-freebsd-x64@npm:1.56.0" +"@oxlint/binding-freebsd-x64@npm:1.57.0": + version: 1.57.0 + resolution: "@oxlint/binding-freebsd-x64@npm:1.57.0" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@oxlint/binding-linux-arm-gnueabihf@npm:1.56.0": - version: 1.56.0 - resolution: "@oxlint/binding-linux-arm-gnueabihf@npm:1.56.0" +"@oxlint/binding-linux-arm-gnueabihf@npm:1.57.0": + version: 1.57.0 + resolution: "@oxlint/binding-linux-arm-gnueabihf@npm:1.57.0" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@oxlint/binding-linux-arm-musleabihf@npm:1.56.0": - version: 1.56.0 - resolution: "@oxlint/binding-linux-arm-musleabihf@npm:1.56.0" +"@oxlint/binding-linux-arm-musleabihf@npm:1.57.0": + version: 1.57.0 + resolution: "@oxlint/binding-linux-arm-musleabihf@npm:1.57.0" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@oxlint/binding-linux-arm64-gnu@npm:1.56.0": - version: 1.56.0 - resolution: "@oxlint/binding-linux-arm64-gnu@npm:1.56.0" +"@oxlint/binding-linux-arm64-gnu@npm:1.57.0": + version: 1.57.0 + resolution: "@oxlint/binding-linux-arm64-gnu@npm:1.57.0" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@oxlint/binding-linux-arm64-musl@npm:1.56.0": - version: 1.56.0 - resolution: "@oxlint/binding-linux-arm64-musl@npm:1.56.0" +"@oxlint/binding-linux-arm64-musl@npm:1.57.0": + version: 1.57.0 + resolution: "@oxlint/binding-linux-arm64-musl@npm:1.57.0" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@oxlint/binding-linux-ppc64-gnu@npm:1.56.0": - version: 1.56.0 - resolution: "@oxlint/binding-linux-ppc64-gnu@npm:1.56.0" +"@oxlint/binding-linux-ppc64-gnu@npm:1.57.0": + version: 1.57.0 + resolution: "@oxlint/binding-linux-ppc64-gnu@npm:1.57.0" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@oxlint/binding-linux-riscv64-gnu@npm:1.56.0": - version: 1.56.0 - resolution: "@oxlint/binding-linux-riscv64-gnu@npm:1.56.0" +"@oxlint/binding-linux-riscv64-gnu@npm:1.57.0": + version: 1.57.0 + resolution: "@oxlint/binding-linux-riscv64-gnu@npm:1.57.0" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@oxlint/binding-linux-riscv64-musl@npm:1.56.0": - version: 1.56.0 - resolution: "@oxlint/binding-linux-riscv64-musl@npm:1.56.0" +"@oxlint/binding-linux-riscv64-musl@npm:1.57.0": + version: 1.57.0 + resolution: "@oxlint/binding-linux-riscv64-musl@npm:1.57.0" conditions: os=linux & cpu=riscv64 & libc=musl languageName: node linkType: hard -"@oxlint/binding-linux-s390x-gnu@npm:1.56.0": - version: 1.56.0 - resolution: "@oxlint/binding-linux-s390x-gnu@npm:1.56.0" +"@oxlint/binding-linux-s390x-gnu@npm:1.57.0": + version: 1.57.0 + resolution: "@oxlint/binding-linux-s390x-gnu@npm:1.57.0" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@oxlint/binding-linux-x64-gnu@npm:1.56.0": - version: 1.56.0 - resolution: "@oxlint/binding-linux-x64-gnu@npm:1.56.0" +"@oxlint/binding-linux-x64-gnu@npm:1.57.0": + version: 1.57.0 + resolution: "@oxlint/binding-linux-x64-gnu@npm:1.57.0" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@oxlint/binding-linux-x64-musl@npm:1.56.0": - version: 1.56.0 - resolution: "@oxlint/binding-linux-x64-musl@npm:1.56.0" +"@oxlint/binding-linux-x64-musl@npm:1.57.0": + version: 1.57.0 + resolution: "@oxlint/binding-linux-x64-musl@npm:1.57.0" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@oxlint/binding-openharmony-arm64@npm:1.56.0": - version: 1.56.0 - resolution: "@oxlint/binding-openharmony-arm64@npm:1.56.0" +"@oxlint/binding-openharmony-arm64@npm:1.57.0": + version: 1.57.0 + resolution: "@oxlint/binding-openharmony-arm64@npm:1.57.0" conditions: os=openharmony & cpu=arm64 languageName: node linkType: hard -"@oxlint/binding-win32-arm64-msvc@npm:1.56.0": - version: 1.56.0 - resolution: "@oxlint/binding-win32-arm64-msvc@npm:1.56.0" +"@oxlint/binding-win32-arm64-msvc@npm:1.57.0": + version: 1.57.0 + resolution: "@oxlint/binding-win32-arm64-msvc@npm:1.57.0" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@oxlint/binding-win32-ia32-msvc@npm:1.56.0": - version: 1.56.0 - resolution: "@oxlint/binding-win32-ia32-msvc@npm:1.56.0" +"@oxlint/binding-win32-ia32-msvc@npm:1.57.0": + version: 1.57.0 + resolution: "@oxlint/binding-win32-ia32-msvc@npm:1.57.0" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@oxlint/binding-win32-x64-msvc@npm:1.56.0": - version: 1.56.0 - resolution: "@oxlint/binding-win32-x64-msvc@npm:1.56.0" +"@oxlint/binding-win32-x64-msvc@npm:1.57.0": + version: 1.57.0 + resolution: "@oxlint/binding-win32-x64-msvc@npm:1.57.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -1851,13 +1857,13 @@ __metadata: linkType: hard "axios@npm:^1.13.2": - version: 1.13.6 - resolution: "axios@npm:1.13.6" + version: 1.14.0 + resolution: "axios@npm:1.14.0" dependencies: follow-redirects: "npm:^1.15.11" form-data: "npm:^4.0.5" - proxy-from-env: "npm:^1.1.0" - checksum: 10c0/51fb5af055c3b85662fa97df17d986ae2c37d13bf86d50b6bb36b6b3a2dec6966a1d3a14ab3774b71707b155ae3597ed9b7babdf1a1a863d1a31840cb8e7ec71 + proxy-from-env: "npm:^2.1.0" + checksum: 10c0/2541f4aa215a7d1842429dad006fc682d82bc0e74bd14500823f7d8cce3bbae0e0a8c328c8538946718f366ab8ce5a4c12e9ad40e5a0f3482ff8bff0cd115d45 languageName: node linkType: hard @@ -1892,11 +1898,11 @@ __metadata: linkType: hard "brace-expansion@npm:^5.0.2": - version: 5.0.4 - resolution: "brace-expansion@npm:5.0.4" + version: 5.0.5 + resolution: "brace-expansion@npm:5.0.5" dependencies: balanced-match: "npm:^4.0.2" - checksum: 10c0/359cbcfa80b2eb914ca1f3440e92313fbfe7919ee6b274c35db55bec555aded69dac5ee78f102cec90c35f98c20fa43d10936d0cd9978158823c249257e1643a + checksum: 10c0/4d238e14ed4f5cc9c07285550a41cef23121ca08ba99fa9eb5b55b580dcb6bf868b8210aa10526bdc9f8dc97f33ca2a7259039c4cc131a93042beddb424c48e3 languageName: node linkType: hard @@ -1910,8 +1916,8 @@ __metadata: linkType: hard "cacache@npm:^20.0.1": - version: 20.0.3 - resolution: "cacache@npm:20.0.3" + version: 20.0.4 + resolution: "cacache@npm:20.0.4" dependencies: "@npmcli/fs": "npm:^5.0.0" fs-minipass: "npm:^3.0.0" @@ -1923,8 +1929,7 @@ __metadata: minipass-pipeline: "npm:^1.2.4" p-map: "npm:^7.0.2" ssri: "npm:^13.0.0" - unique-filename: "npm:^5.0.0" - checksum: 10c0/c7da1ca694d20e8f8aedabd21dc11518f809a7d2b59aa76a1fc655db5a9e62379e465c157ddd2afe34b19230808882288effa6911b2de26a088a6d5645123462 + checksum: 10c0/539bf4020e44ba9ca5afc2ec435623ed7e0dd80c020097677e6b4a0545df5cc9d20b473212d01209c8b4aea43c0d095af0bb6da97bcb991642ea6fac0d7c462b languageName: node linkType: hard @@ -2186,15 +2191,15 @@ __metadata: languageName: node linkType: hard -"emnapi@npm:^1.7.1": - version: 1.9.0 - resolution: "emnapi@npm:1.9.0" +"emnapi@npm:^1.9.1": + version: 1.9.1 + resolution: "emnapi@npm:1.9.1" peerDependencies: node-addon-api: ">= 6.1.0" peerDependenciesMeta: node-addon-api: optional: true - checksum: 10c0/e639ec31e3d71418b2b57fc1a340b84aedbd2f2f1b1eb5e24b5b76591a31b1c03c75e77f71e92eb3a00ae86c64613b2a290902f7c541eec5a89106793f6ef93c + checksum: 10c0/d2dfc4f7ab1999a77147a924776e8d4d852776a2d246a88a1aff2090774c43d087ab1045ec83e22b999acd4bbef80298c519b0705938384945f4842a1df38f40 languageName: node linkType: hard @@ -2630,11 +2635,11 @@ __metadata: linkType: hard "get-tsconfig@npm:^4.7.5": - version: 4.13.6 - resolution: "get-tsconfig@npm:4.13.6" + version: 4.13.7 + resolution: "get-tsconfig@npm:4.13.7" dependencies: resolve-pkg-maps: "npm:^1.0.0" - checksum: 10c0/bab6937302f542f97217cbe7cbbdfa7e85a56a377bc7a73e69224c1f0b7c9ae8365918e55752ae8648265903f506c1705f63c0de1d4bab1ec2830fef3e539a1a + checksum: 10c0/1118eb7e9b27bce0b9b6f042e98f0d067e26dfa1ca32bc4b56e892b615b57a5a4af9e6f801c7b0611a4afef2e31c4941be4c6026e0e6a480aaf1ddaf261113d5 languageName: node linkType: hard @@ -2659,8 +2664,8 @@ __metadata: linkType: hard "globby@npm:^16.1.1": - version: 16.1.1 - resolution: "globby@npm:16.1.1" + version: 16.2.0 + resolution: "globby@npm:16.2.0" dependencies: "@sindresorhus/merge-streams": "npm:^4.0.0" fast-glob: "npm:^3.3.3" @@ -2668,7 +2673,7 @@ __metadata: is-path-inside: "npm:^4.0.0" slash: "npm:^5.1.0" unicorn-magic: "npm:^0.4.0" - checksum: 10c0/2fbed8e5c59639a98b9b9c700afe5bcedf14742b43c25950cfd34a032db0cce4b440d8436beb4a936d211744e0b7330646f086b95cd8054251162c5d83001600 + checksum: 10c0/fc0675e01dc1da5095f30dccc46a3047fc38d45ca08c21c1aa871bd79d38682f507d84a159be168019db5fffaa09c5663c3679c29190a2d4f999dc91d7ff6406 languageName: node linkType: hard @@ -2770,13 +2775,6 @@ __metadata: languageName: node linkType: hard -"imurmurhash@npm:^0.1.4": - version: 0.1.4 - resolution: "imurmurhash@npm:0.1.4" - checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 - languageName: node - linkType: hard - "indent-string@npm:^5.0.0": version: 5.0.0 resolution: "indent-string@npm:5.0.0" @@ -2917,9 +2915,9 @@ __metadata: linkType: hard "json-with-bigint@npm:^3.5.3": - version: 3.5.7 - resolution: "json-with-bigint@npm:3.5.7" - checksum: 10c0/69f27b712ac113aec2cac0933d81f37a3d6f5f0947543805cf5c6699c19ed14a79515d9aa2f78fde54ffdd7d9105790443408322f23add2a69096e741e176876 + version: 3.5.8 + resolution: "json-with-bigint@npm:3.5.8" + checksum: 10c0/a0c4e37626d74a9a493539f9f9a94855933fa15ea2f028859a787229a42c5f11803db6f94f1ce7b1d89756c1e80a7c1f11006bac266ec7ce819b75701765ca0a languageName: node linkType: hard @@ -2988,11 +2986,12 @@ __metadata: linkType: hard "make-fetch-happen@npm:^15.0.0": - version: 15.0.4 - resolution: "make-fetch-happen@npm:15.0.4" + version: 15.0.5 + resolution: "make-fetch-happen@npm:15.0.5" dependencies: "@gar/promise-retry": "npm:^1.0.0" "@npmcli/agent": "npm:^4.0.0" + "@npmcli/redact": "npm:^4.0.0" cacache: "npm:^20.0.1" http-cache-semantics: "npm:^4.1.1" minipass: "npm:^7.0.2" @@ -3002,7 +3001,7 @@ __metadata: negotiator: "npm:^1.0.0" proc-log: "npm:^6.0.0" ssri: "npm:^13.0.0" - checksum: 10c0/b874bf6879fc0b8ef3a3cafdddadea4d956acf94790f8dede1a9d3c74c7886b6cd3eb992616b8e5935e6fd550016a465f10ba51bf6723a0c6f4d98883ae2926b + checksum: 10c0/527580eb5e5476e6ad07a4e3bd017d13e935f4be815674b442081ae5a721c13d3af5715006619e6be79a85723067e047f83a0c9e699f41d8cec43609a8de4f7b languageName: node linkType: hard @@ -3121,11 +3120,11 @@ __metadata: linkType: hard "minipass-flush@npm:^1.0.5": - version: 1.0.5 - resolution: "minipass-flush@npm:1.0.5" + version: 1.0.7 + resolution: "minipass-flush@npm:1.0.7" dependencies: minipass: "npm:^3.0.0" - checksum: 10c0/2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd + checksum: 10c0/960915c02aa0991662c37c404517dd93708d17f96533b2ca8c1e776d158715d8107c5ced425ffc61674c167d93607f07f48a83c139ce1057f8781e5dfb4b90c2 languageName: node linkType: hard @@ -3312,28 +3311,28 @@ __metadata: linkType: hard "oxlint@npm:^1.14.0": - version: 1.56.0 - resolution: "oxlint@npm:1.56.0" - dependencies: - "@oxlint/binding-android-arm-eabi": "npm:1.56.0" - "@oxlint/binding-android-arm64": "npm:1.56.0" - "@oxlint/binding-darwin-arm64": "npm:1.56.0" - "@oxlint/binding-darwin-x64": "npm:1.56.0" - "@oxlint/binding-freebsd-x64": "npm:1.56.0" - "@oxlint/binding-linux-arm-gnueabihf": "npm:1.56.0" - "@oxlint/binding-linux-arm-musleabihf": "npm:1.56.0" - "@oxlint/binding-linux-arm64-gnu": "npm:1.56.0" - "@oxlint/binding-linux-arm64-musl": "npm:1.56.0" - "@oxlint/binding-linux-ppc64-gnu": "npm:1.56.0" - "@oxlint/binding-linux-riscv64-gnu": "npm:1.56.0" - "@oxlint/binding-linux-riscv64-musl": "npm:1.56.0" - "@oxlint/binding-linux-s390x-gnu": "npm:1.56.0" - "@oxlint/binding-linux-x64-gnu": "npm:1.56.0" - "@oxlint/binding-linux-x64-musl": "npm:1.56.0" - "@oxlint/binding-openharmony-arm64": "npm:1.56.0" - "@oxlint/binding-win32-arm64-msvc": "npm:1.56.0" - "@oxlint/binding-win32-ia32-msvc": "npm:1.56.0" - "@oxlint/binding-win32-x64-msvc": "npm:1.56.0" + version: 1.57.0 + resolution: "oxlint@npm:1.57.0" + dependencies: + "@oxlint/binding-android-arm-eabi": "npm:1.57.0" + "@oxlint/binding-android-arm64": "npm:1.57.0" + "@oxlint/binding-darwin-arm64": "npm:1.57.0" + "@oxlint/binding-darwin-x64": "npm:1.57.0" + "@oxlint/binding-freebsd-x64": "npm:1.57.0" + "@oxlint/binding-linux-arm-gnueabihf": "npm:1.57.0" + "@oxlint/binding-linux-arm-musleabihf": "npm:1.57.0" + "@oxlint/binding-linux-arm64-gnu": "npm:1.57.0" + "@oxlint/binding-linux-arm64-musl": "npm:1.57.0" + "@oxlint/binding-linux-ppc64-gnu": "npm:1.57.0" + "@oxlint/binding-linux-riscv64-gnu": "npm:1.57.0" + "@oxlint/binding-linux-riscv64-musl": "npm:1.57.0" + "@oxlint/binding-linux-s390x-gnu": "npm:1.57.0" + "@oxlint/binding-linux-x64-gnu": "npm:1.57.0" + "@oxlint/binding-linux-x64-musl": "npm:1.57.0" + "@oxlint/binding-openharmony-arm64": "npm:1.57.0" + "@oxlint/binding-win32-arm64-msvc": "npm:1.57.0" + "@oxlint/binding-win32-ia32-msvc": "npm:1.57.0" + "@oxlint/binding-win32-x64-msvc": "npm:1.57.0" peerDependencies: oxlint-tsgolint: ">=0.15.0" dependenciesMeta: @@ -3380,7 +3379,7 @@ __metadata: optional: true bin: oxlint: bin/oxlint - checksum: 10c0/0b95b03106619574244e0837b94730a988c3188a2d0137c07bcc85ee79cb11a3c64e46910b3bca8befce49459af58a503c7e2c26c6022cc11705cf6db787581c + checksum: 10c0/e2ccd23280615068335c603c7fe9d2590812d7b2f16ee55a3fca1260d600795847e94926026a0d4ef46026c97c3868aee070ecca125eeaac68a8abbbe309d4a1 languageName: node linkType: hard @@ -3426,16 +3425,16 @@ __metadata: linkType: hard "picomatch@npm:^2.3.1": - version: 2.3.1 - resolution: "picomatch@npm:2.3.1" - checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be + version: 2.3.2 + resolution: "picomatch@npm:2.3.2" + checksum: 10c0/a554d1709e59be97d1acb9eaedbbc700a5c03dbd4579807baed95100b00420bc729335440ef15004ae2378984e2487a7c1cebd743cfdb72b6fa9ab69223c0d61 languageName: node linkType: hard "picomatch@npm:^4.0.2, picomatch@npm:^4.0.3": - version: 4.0.3 - resolution: "picomatch@npm:4.0.3" - checksum: 10c0/9582c951e95eebee5434f59e426cddd228a7b97a0161a375aed4be244bd3fe8e3a31b846808ea14ef2c8a2527a6eeab7b3946a67d5979e81694654f939473ae2 + version: 4.0.4 + resolution: "picomatch@npm:4.0.4" + checksum: 10c0/e2c6023372cc7b5764719a5ffb9da0f8e781212fa7ca4bd0562db929df8e117460f00dff3cb7509dacfc06b86de924b247f504d0ce1806a37fac4633081466b0 languageName: node linkType: hard @@ -3489,10 +3488,10 @@ __metadata: languageName: node linkType: hard -"proxy-from-env@npm:^1.1.0": - version: 1.1.0 - resolution: "proxy-from-env@npm:1.1.0" - checksum: 10c0/fe7dd8b1bdbbbea18d1459107729c3e4a2243ca870d26d34c2c1bcd3e4425b7bcc5112362df2d93cc7fb9746f6142b5e272fd1cc5c86ddf8580175186f6ad42b +"proxy-from-env@npm:^2.1.0": + version: 2.1.0 + resolution: "proxy-from-env@npm:2.1.0" + checksum: 10c0/ed01729fd4d094eab619cd7e17ce3698b3413b31eb102c4904f9875e677cd207392795d5b4adee9cec359dfd31c44d5ad7595a3a3ad51c40250e141512281c58 languageName: node linkType: hard @@ -3546,13 +3545,6 @@ __metadata: languageName: node linkType: hard -"retry@npm:^0.13.1": - version: 0.13.1 - resolution: "retry@npm:0.13.1" - checksum: 10c0/9ae822ee19db2163497e074ea919780b1efa00431d197c7afdb950e42bf109196774b92a49fc9821f0b8b328a98eea6017410bfc5e8a0fc19c85c6d11adb3772 - languageName: node - linkType: hard - "reusify@npm:^1.0.4": version: 1.1.0 resolution: "reusify@npm:1.1.0" @@ -3761,15 +3753,15 @@ __metadata: linkType: hard "tar@npm:^7.4.0, tar@npm:^7.5.4": - version: 7.5.11 - resolution: "tar@npm:7.5.11" + version: 7.5.13 + resolution: "tar@npm:7.5.13" dependencies: "@isaacs/fs-minipass": "npm:^4.0.0" chownr: "npm:^3.0.0" minipass: "npm:^7.1.2" minizlib: "npm:^3.1.0" yallist: "npm:^5.0.0" - checksum: 10c0/b6bb420550ef50ef23356018155e956cd83282c97b6128d8d5cfe5740c57582d806a244b2ef0bf686a74ce526babe8b8b9061527623e935e850008d86d838929 + checksum: 10c0/5c65b8084799bde7a791593a1c1a45d3d6ee98182e3700b24c247b7b8f8654df4191642abbdb07ff25043d45dcff35620827c3997b88ae6c12040f64bed5076b languageName: node linkType: hard @@ -3907,24 +3899,6 @@ __metadata: languageName: node linkType: hard -"unique-filename@npm:^5.0.0": - version: 5.0.0 - resolution: "unique-filename@npm:5.0.0" - dependencies: - unique-slug: "npm:^6.0.0" - checksum: 10c0/afb897e9cf4c2fb622ea716f7c2bb462001928fc5f437972213afdf1cc32101a230c0f1e9d96fc91ee5185eca0f2feb34127145874975f347be52eb91d6ccc2c - languageName: node - linkType: hard - -"unique-slug@npm:^6.0.0": - version: 6.0.0 - resolution: "unique-slug@npm:6.0.0" - dependencies: - imurmurhash: "npm:^0.1.4" - checksum: 10c0/da7ade4cb04eb33ad0499861f82fe95ce9c7c878b7139dc54d140ecfb6a6541c18a5c8dac16188b8b379fe62c0c1f1b710814baac910cde5f4fec06212126c6a - languageName: node - linkType: hard - "universal-user-agent@npm:^7.0.0, universal-user-agent@npm:^7.0.2": version: 7.0.3 resolution: "universal-user-agent@npm:7.0.3" @@ -4031,11 +4005,11 @@ __metadata: linkType: hard "yaml@npm:^2.8.2": - version: 2.8.2 - resolution: "yaml@npm:2.8.2" + version: 2.8.3 + resolution: "yaml@npm:2.8.3" bin: yaml: bin.mjs - checksum: 10c0/703e4dc1e34b324aa66876d63618dcacb9ed49f7e7fe9b70f1e703645be8d640f68ab84f12b86df8ac960bac37acf5513e115de7c970940617ce0343c8c9cd96 + checksum: 10c0/ddff0e11c1b467728d7eb4633db61c5f5de3d8e9373cf84d08fb0cdee03e1f58f02b9f1c51a4a8a865751695addbd465a77f73f1079be91fe5493b29c305fd77 languageName: node linkType: hard From 791acdab6539c69aaa9265899be84500290248e4 Mon Sep 17 00:00:00 2001 From: Samuel Mwangi Date: Sat, 28 Mar 2026 18:19:32 +0300 Subject: [PATCH 04/15] CI fix: Update GitHub workflow file to use updated npm scripts. --- .github/workflows/CI.yml | 47 +++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 436b455..0daa981 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -50,40 +50,60 @@ jobs: settings: - host: macos-latest target: x86_64-apple-darwin - build: yarn build --target x86_64-apple-darwin + build: yarn build:core --target x86_64-apple-darwin - host: windows-latest - build: yarn build --target x86_64-pc-windows-msvc + build: yarn build:core --target x86_64-pc-windows-msvc target: x86_64-pc-windows-msvc - host: windows-latest - build: yarn build --target i686-pc-windows-msvc + build: | + yarn build:core --target i686-pc-windows-msvc + yarn build:js target: i686-pc-windows-msvc - host: ubuntu-22.04 target: x86_64-unknown-linux-gnu - build: yarn build --target x86_64-unknown-linux-gnu + build: | + yarn build:core --target x86_64-unknown-linux-gnu + yarn build:js - host: ubuntu-latest target: x86_64-unknown-linux-musl - build: yarn build --target x86_64-unknown-linux-musl -x + build: | + yarn build:core --target x86_64-unknown-linux-musl -x + yarn build:js - host: macos-latest target: aarch64-apple-darwin - build: yarn build --target aarch64-apple-darwin + build: | + yarn build:core --target aarch64-apple-darwin + yarn build:js - host: ubuntu-22.04 target: aarch64-unknown-linux-gnu - build: yarn build --target aarch64-unknown-linux-gnu + build: | + yarn build:core --target aarch64-unknown-linux-gnu + yarn build:js - host: ubuntu-latest target: armv7-unknown-linux-gnueabihf - build: yarn build --target armv7-unknown-linux-gnueabihf --use-napi-cross + build: | + yarn build:core --target armv7-unknown-linux-gnueabihf --use-napi-cross + yarn build:js - host: ubuntu-latest target: aarch64-linux-android - build: yarn build --target aarch64-linux-android + build: | + yarn build:core --target aarch64-linux-android + yarn build:js - host: ubuntu-latest target: armv7-linux-androideabi - build: yarn build --target armv7-linux-androideabi + build: | + yarn build:core --target armv7-linux-androideabi + yarn build:js - host: ubuntu-latest target: aarch64-unknown-linux-musl - build: yarn build --target aarch64-unknown-linux-musl -x + build: | + yarn build:core --target aarch64-unknown-linux-musl -x + yarn build:js - host: windows-latest target: aarch64-pc-windows-msvc - build: yarn build --target aarch64-pc-windows-msvc + build: | + yarn build:core --target aarch64-pc-windows-msvc + yarn build:js name: stable - ${{ matrix.settings.target }} - node@22 runs-on: ${{ matrix.settings.host }} steps: @@ -192,7 +212,8 @@ jobs: env freebsd-version yarn install - yarn build + yarn build:core + yarn build:js rm -rf node_modules rm -rf target rm -rf .yarn/cache From 77768052a9112db196ec5438b37865f0dff21bd1 Mon Sep 17 00:00:00 2001 From: Samuel Mwangi Date: Sat, 28 Mar 2026 18:29:58 +0300 Subject: [PATCH 05/15] Use 'SocketAddr::as_abstract_name' only on linux and android platforms. --- src/server/listen_ipc.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/server/listen_ipc.rs b/src/server/listen_ipc.rs index 962f707..23f0ae9 100644 --- a/src/server/listen_ipc.rs +++ b/src/server/listen_ipc.rs @@ -90,15 +90,15 @@ impl Server { let rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(async move { let addr = ipc_listener.local_addr().unwrap(); - let addr = addr - .as_pathname() - .and_then(|p| p.to_str()) - .or( + let mut addr_str = addr.as_pathname().and_then(|p| p.to_str()); + if cfg!(any(target_os = "linux", target_os = "android")) { + addr_str = addr_str.or( addr .as_abstract_name() .and_then(|bytes| str::from_utf8(bytes).ok()), ) - .unwrap_or_default(); + } + let addr = addr_str.unwrap_or_default(); let server_status_message = format!("Server listening on '{}'", addr); log::debug!("{server_status_message}"); From 2f3838e7a608d0dcb6a1d009c5b01360cda41679 Mon Sep 17 00:00:00 2001 From: Samuel Mwangi Date: Sat, 28 Mar 2026 20:10:19 +0300 Subject: [PATCH 06/15] Add support for IPC servers on Windows. --- Cargo.toml | 96 ++++++++++++++++---------------- src/server/_listen_tcp.rs | 15 +++-- src/server/listen_ipc.rs | 103 +++++++++++++++++++++++++++++------ src/server/listen_tcp.rs | 5 ++ src/server/mod.rs | 2 + src/server/systemd_notify.rs | 2 +- 6 files changed, 149 insertions(+), 74 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 472fed5..8cdbac1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,73 +2,73 @@ [package] authors = ["LongYinan "] edition = "2024" -name = "hyperjs" +name = "hyperjs" version = "0.1.0" [lib] crate-type = ["cdylib"] [dependencies] -ammonia = "4.1.2" -askama_escape = "0.15.2" -byte-unit = "5.2.0" -bytes = "1.11.0" -chrono = "0.4.43" -env_logger = "0.11.9" -futures = "0.3.31" -headers-accept = "0.3.0" -headers-core = "0.3.0" -http-body = "1.0.1" -http-body-util = "0.1.3" +ammonia = "4.1.2" +askama_escape = "0.15.2" +byte-unit = "5.2.0" +bytes = "1.11.0" +chrono = "0.4.43" +env_logger = "0.11.9" +futures = "0.3.31" +headers-accept = "0.3.0" +headers-core = "0.3.0" +http-body = "1.0.1" +http-body-util = "0.1.3" http-range-header = "0.4.2" -httpdate = "1.0.3" -hyper-staticfile = "0.10.1" -lazy_static = "1.5.0" -log = "0.4.29" -matchit = "0.9.1" -mediatype = "0.21.0" -mime_guess = "2.0.5" -napi-derive = "3.0.0" -percent-encoding = "2.3.2" -regex = "1.12.3" -rustls-acme = { version = "0.15.1", features = ["tokio"] } -serde_json = "1.0.148" -serde_qs = "1.0.0" -serde_urlencoded = "0.7.1" +httpdate = "1.0.3" +hyper-staticfile = "0.10.1" +lazy_static = "1.5.0" +log = "0.4.29" +matchit = "0.9.1" +mediatype = "0.21.0" +mime_guess = "2.0.5" +napi-derive = "3.0.0" +percent-encoding = "2.3.2" +regex = "1.12.3" +rustls-acme = {version = "0.15.1", features = ["tokio"]} +serde_json = "1.0.148" +serde_qs = "1.0.0" +serde_urlencoded = "0.7.1" socket2 = "0.6.3" -tempfile = "3.24.0" -tokio-stream = { version = "0.1.18", features = ["net"] } -urlencoding = "2.1.3" +tempfile = "3.24.0" +tokio-stream = {version = "0.1.18", features = ["net"]} +urlencoding = "2.1.3" - [dependencies.cookie] - features = ["percent-encode", "secure"] - version = "0.18.1" +[dependencies.cookie] +features = ["percent-encode", "secure"] +version = "0.18.1" - [dependencies.etag] - features = ["std"] - version = "4.0.0" +[dependencies.etag] +features = ["std"] +version = "4.0.0" - [dependencies.hyper] - features = ["full"] - version = "1.8.1" +[dependencies.hyper] +features = ["full"] +version = "1.8.1" - [dependencies.hyper-util] - features = ["tokio"] - version = "0.1.19" +[dependencies.hyper-util] +features = ["tokio"] +version = "0.1.19" - [dependencies.napi] - features = ["napi6", "async", "serde", "serde-json"] - version = "3.0.0" +[dependencies.napi] +features = ["napi6", "async", "serde", "serde-json"] +version = "3.0.0" - [dependencies.tokio] - features = ["rt", "net", "rt-multi-thread", "macros"] - version = "1.48.0" +[dependencies.tokio] +features = ["rt", "net", "rt-multi-thread", "macros"] +version = "1.48.0" [build-dependencies] napi-build = "2" [profile.release] -lto = true +lto = true strip = "symbols" [target.'cfg(unix)'.dependencies] diff --git a/src/server/_listen_tcp.rs b/src/server/_listen_tcp.rs index 3ff9a9d..3972e66 100644 --- a/src/server/_listen_tcp.rs +++ b/src/server/_listen_tcp.rs @@ -38,6 +38,7 @@ impl Server { let server_status_message = format!("Server listening on {}", addr); log::debug!("{server_status_message}"); + #[cfg(unix)] systemd_notify(&server_status_message); callback.call(addr.to_string(), ThreadsafeFunctionCallMode::Blocking); @@ -81,16 +82,14 @@ impl Server { } } +#[cfg(unix)] fn systemd_notify(server_status_message: &str) { - #[cfg(unix)] - { - use sd_notify::{NotifyState, notify}; - if let Err(e) = notify(&[NotifyState::Ready]) { - log::error!("Failed to notify systemd: {}", e); - } - - let _ = notify(&[NotifyState::Status(server_status_message)]); + use sd_notify::{NotifyState, notify}; + if let Err(e) = notify(&[NotifyState::Ready]) { + log::error!("Failed to notify systemd: {}", e); } + + let _ = notify(&[NotifyState::Status(server_status_message)]); } fn create_handler_task( diff --git a/src/server/listen_ipc.rs b/src/server/listen_ipc.rs index 23f0ae9..bf94a76 100644 --- a/src/server/listen_ipc.rs +++ b/src/server/listen_ipc.rs @@ -1,4 +1,8 @@ -use std::{fs::Permissions, os::unix::fs::PermissionsExt, path::Path, sync::Arc}; +#[cfg(unix)] +use std::path::Path; +use std::sync::Arc; +#[cfg(unix)] +use std::{fs::Permissions, os::unix::fs::PermissionsExt}; use env_logger::Builder as EnvLoggerBuilder; use hyper_util::rt::TokioIo; @@ -8,9 +12,14 @@ use napi::{ threadsafe_function::{ThreadsafeCallContext, ThreadsafeFunctionCallMode}, }; use napi_derive::napi; +#[cfg(unix)] use tokio::net::UnixListener; +#[cfg(windows)] +use tokio::net::windows::named_pipe::ServerOptions; -use super::{Server, create_handler_task::create_handler_task, systemd_notify::systemd_notify}; +#[cfg(unix)] +use super::systemd_notify::systemd_notify; +use super::{Server, create_handler_task::create_handler_task}; use crate::server::ThreadsafeCallbackFn; #[napi(object)] @@ -39,12 +48,14 @@ pub struct IpcServerListenOptions { pub writable_all: Option, } +#[cfg(unix)] struct SetSocketPermissionsParams> { path: P, readable_all: bool, writeable_all: bool, } +#[cfg(unix)] fn set_socket_permissions>(params: SetSocketPermissionsParams

) -> Result<()> { let permissions_mode = match (params.readable_all, params.writeable_all) { // owner read+write only @@ -76,15 +87,35 @@ fn set_socket_permissions>(params: SetSocketPermissionsParams

) Ok(()) } +fn setup_logging() { + EnvLoggerBuilder::new() + .filter_level(LevelFilter::max()) + .init(); +} + #[napi] impl Server { - pub fn _listen_ipc(&self, ipc_listener: UnixListener, callback: ThreadsafeCallbackFn) { + #[cfg(unix)] + pub fn _listen_ipc_unix( + &self, + options: IpcServerListenOptions, + callback: ThreadsafeCallbackFn, + ) -> Result<()> { + let ipc_listener = UnixListener::bind(&options.path) + .map_err(|e| Error::from_reason(format!("Error creating IPC listener. {e}")))?; + + set_socket_permissions(SetSocketPermissionsParams { + path: options.path.to_owned(), + readable_all: options.readable_all.unwrap_or_default(), + writeable_all: options.writable_all.unwrap_or_default(), + })?; + + log::info!("IPC server listening on {}", options.path); + let router = Arc::new(self.router.clone()); let middlewares = Arc::new(self.middlewares.clone()); - EnvLoggerBuilder::new() - .filter_level(LevelFilter::max()) - .init(); + setup_logging(); std::thread::spawn(move || { let rt = tokio::runtime::Runtime::new().unwrap(); @@ -116,6 +147,51 @@ impl Server { } }); }); + + Ok(()) + } + + #[cfg(windows)] + pub fn _listen_ipc_windows( + &self, + options: IpcServerListenOptions, + callback: ThreadsafeCallbackFn, + ) -> Result<()> { + let router = Arc::new(self.router.clone()); + let middlewares = Arc::new(self.middlewares.clone()); + let pipe_path = options.path.clone(); + + setup_logging(); + + let mut server = ServerOptions::new() + .first_pipe_instance(true) + .create(options.path)?; + + std::thread::spawn(move || { + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async move { + callback.call(pipe_path.to_owned(), ThreadsafeFunctionCallMode::Blocking); + + loop { + server.connect().await.unwrap(); + let connected_client = server; + + // Construct the next server to be connected before sending the one + // we already have of onto a task. This ensures that the server + // isn't closed (after it's done in the task) before a new one is + // available. Otherwise the client might error with + // `io::ErrorKind::NotFound`. + server = ServerOptions::new().create(pipe_path.clone()).unwrap(); + let io = TokioIo::new(connected_client); + let router = router.clone(); + let middlewares = middlewares.clone(); + + create_handler_task(io, router, middlewares); + } + }); + }); + + Ok(()) } #[napi] @@ -131,18 +207,11 @@ impl Server { }, )?; - let ipc_listener = UnixListener::bind(&options.path) - .map_err(|e| Error::from_reason(format!("Error creating IPC listener. {e}")))?; - - set_socket_permissions(SetSocketPermissionsParams { - path: options.path.to_owned(), - readable_all: options.readable_all.unwrap_or_default(), - writeable_all: options.writable_all.unwrap_or_default(), - })?; - - log::info!("IPC server listening on {}", options.path); + #[cfg(unix)] + self._listen_ipc_unix(options, ts_callback)?; - self._listen_ipc(ipc_listener, ts_callback); + #[cfg(windows)] + self._listen_ipc_windows(options, ts_callback)?; Ok(()) } diff --git a/src/server/listen_tcp.rs b/src/server/listen_tcp.rs index b1d6aa6..a2a5a72 100644 --- a/src/server/listen_tcp.rs +++ b/src/server/listen_tcp.rs @@ -59,6 +59,7 @@ pub struct LibTcpServerListenOptions { backlog: i32, host: String, ipv6_only: bool, + #[cfg(all(unix, windows))] reuse_port: bool, port: u16, } @@ -72,6 +73,7 @@ impl From for LibTcpServerListenOptions { false => "0.0.0.0".to_owned(), }, ipv6_only: options.ipv6_only.unwrap_or_default(), + #[cfg(all(unix, windows))] reuse_port: options.ipv6_only.unwrap_or_default(), port: options.port.unwrap_or(0), } @@ -85,6 +87,7 @@ fn host_to_ip_addr(host: &str) -> Result { } struct CreateTcpSocketParams { + #[cfg(all(unix, windows))] reuse_port: bool, ipv6_only: bool, } @@ -99,6 +102,7 @@ fn create_tcp_socket(params: CreateTcpSocketParams) -> Result { socket .set_only_v6(params.ipv6_only) .map_err(|e| Error::from_reason(e.to_string()))?; + #[cfg(all(unix, windows))] socket .set_reuse_port(params.reuse_port) .map_err(|e| Error::from_reason(e.to_string()))?; @@ -137,6 +141,7 @@ impl Server { backlog: options.backlog, socket: CreateTcpSocketParams { ipv6_only: options.ipv6_only, + #[cfg(all(unix, windows))] reuse_port: options.reuse_port, }, }; diff --git a/src/server/mod.rs b/src/server/mod.rs index 4d086e1..5aa0c21 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -27,6 +27,7 @@ use crate::request::Request; use crate::response::Response; use create_handler_task::create_handler_task; use handle_http_request::handle_http_request; +#[cfg(unix)] use systemd_notify::systemd_notify; // Global state for pending requests @@ -148,6 +149,7 @@ impl Server { let server_status_message = format!("Server listening on {}", addr); log::debug!("{server_status_message}"); + #[cfg(unix)] systemd_notify(&server_status_message); match acme_config_meta { diff --git a/src/server/systemd_notify.rs b/src/server/systemd_notify.rs index 29454c8..803bd8a 100644 --- a/src/server/systemd_notify.rs +++ b/src/server/systemd_notify.rs @@ -1,5 +1,5 @@ +#[cfg(unix)] pub fn systemd_notify(server_status_message: &str) { - #[cfg(unix)] { use sd_notify::{NotifyState, notify}; if let Err(e) = notify(&[NotifyState::Ready]) { From 017ed5d5d137a6499b65ba38a24bddb7efb004fe Mon Sep 17 00:00:00 2001 From: Samuel Mwangi Date: Sat, 28 Mar 2026 20:17:43 +0300 Subject: [PATCH 07/15] CI fix: Update upload artifact paths to mirror changed napi output directory. --- .github/workflows/CI.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 0daa981..002f967 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -172,8 +172,8 @@ jobs: with: name: bindings-${{ matrix.settings.target }} path: | - ${{ env.APP_NAME }}.*.node - ${{ env.APP_NAME }}.*.wasm + hyperjs-core/${{ env.APP_NAME }}.*.node + hyperjs-core/${{ env.APP_NAME }}.*.wasm if-no-files-found: error build-freebsd: runs-on: ubuntu-latest @@ -221,7 +221,7 @@ jobs: uses: actions/upload-artifact@v6 with: name: bindings-freebsd - path: ${{ env.APP_NAME }}.*.node + path: hyperjs-core/${{ env.APP_NAME }}.*.node if-no-files-found: error test-macOS-windows-binding: name: Test bindings on ${{ matrix.settings.target }} - node@${{ matrix.node }} From 24fe17125afef528b2db3abbee2172c7dfdf9af8 Mon Sep 17 00:00:00 2001 From: Samuel Mwangi Date: Sat, 28 Mar 2026 20:26:42 +0300 Subject: [PATCH 08/15] Add additional OS-specific guards for features not available on varying OSes. --- src/server/listen_ipc.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/server/listen_ipc.rs b/src/server/listen_ipc.rs index bf94a76..00f3f7c 100644 --- a/src/server/listen_ipc.rs +++ b/src/server/listen_ipc.rs @@ -122,12 +122,15 @@ impl Server { rt.block_on(async move { let addr = ipc_listener.local_addr().unwrap(); let mut addr_str = addr.as_pathname().and_then(|p| p.to_str()); - if cfg!(any(target_os = "linux", target_os = "android")) { + if cfg!(all( + unix, + not(any(target_vendor = "apple", target_os = "freebsd")) + )) { addr_str = addr_str.or( addr .as_abstract_name() .and_then(|bytes| str::from_utf8(bytes).ok()), - ) + ); } let addr = addr_str.unwrap_or_default(); let server_status_message = format!("Server listening on '{}'", addr); From 697eb5361fb34d1bcb7097068c43fc7b0df47df7 Mon Sep 17 00:00:00 2001 From: Samuel Mwangi Date: Sat, 28 Mar 2026 20:40:30 +0300 Subject: [PATCH 09/15] Use '#[cfg(...)]' instead of 'if cfg!(...)' for compile-time checking instead of runtime checking of platform-specific code. --- src/server/listen_ipc.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/server/listen_ipc.rs b/src/server/listen_ipc.rs index 00f3f7c..b957c9b 100644 --- a/src/server/listen_ipc.rs +++ b/src/server/listen_ipc.rs @@ -122,14 +122,12 @@ impl Server { rt.block_on(async move { let addr = ipc_listener.local_addr().unwrap(); let mut addr_str = addr.as_pathname().and_then(|p| p.to_str()); - if cfg!(all( - unix, - not(any(target_vendor = "apple", target_os = "freebsd")) - )) { + #[cfg(all(unix, not(any(target_vendor = "apple", target_os = "freebsd"))))] + { addr_str = addr_str.or( addr .as_abstract_name() - .and_then(|bytes| str::from_utf8(bytes).ok()), + .and_then(|bytes| std::str::from_utf8(bytes).ok()), ); } let addr = addr_str.unwrap_or_default(); From e73c5fd9ae8e1ee1c0211b908dbdda08bf4825c6 Mon Sep 17 00:00:00 2001 From: Samuel Mwangi Date: Wed, 1 Apr 2026 12:56:30 +0300 Subject: [PATCH 10/15] CI fix: Update artifact download step path to match updated upload step path. --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 002f967..29333c0 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -319,7 +319,7 @@ jobs: uses: actions/download-artifact@v7 with: name: bindings-${{ matrix.target }} - path: . + path: ./hyperjs-core - name: List packages run: ls -R . shell: bash From 3bbfcbec63439b5365049dfff9c78c54d42a2d58 Mon Sep 17 00:00:00 2001 From: Samuel Mwangi Date: Wed, 1 Apr 2026 13:09:05 +0300 Subject: [PATCH 11/15] CI fix: Update artifact download step path to match updated upload step path. --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 29333c0..2239d99 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -261,7 +261,7 @@ jobs: uses: actions/download-artifact@v7 with: name: bindings-${{ matrix.settings.target }} - path: . + path: ./hyperjs-core - name: List packages run: ls -R . shell: bash From bf84927b03a5730bd0c8120864644ed07e2db253 Mon Sep 17 00:00:00 2001 From: Samuel Mwangi Date: Sat, 4 Apr 2026 09:31:42 +0300 Subject: [PATCH 12/15] Implement server.listen function. --- hyperjs-core/index.d.ts | 9 ----- index.js | 68 ++++++++++++++++++++++---------- index.ts | 84 ++++++++++++++++++++++++++++++---------- src/server/listen_ipc.rs | 8 ---- 4 files changed, 112 insertions(+), 57 deletions(-) diff --git a/hyperjs-core/index.d.ts b/hyperjs-core/index.d.ts index ba46d82..dadd8b9 100644 --- a/hyperjs-core/index.d.ts +++ b/hyperjs-core/index.d.ts @@ -985,15 +985,6 @@ export interface DownloadOptions { } export interface IpcServerListenOptions { - /** - * The maximum number of pending connections that can be queued before the OS - * starts refusing new ones. Passed directly to the underlying `listen(2)` syscall. - * - * A value of `0` lets the OS choose a reasonable default. - * - * Default = 0 - */ - backlog: number /** * Unix domain socket path the server should listen on. * diff --git a/index.js b/index.js index bd0d5ea..92a2231 100644 --- a/index.js +++ b/index.js @@ -1,27 +1,55 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const index_1 = require("./hyperjs-core/index"); -// interface ServerListenOptions { -// backlog: number -// exclusive: boolean -// host: string -// ipv6Only: boolean -// reusePort: boolean -// path: string -// port: number -// readableAll: boolean -// signal: AbortSignal -// writableAll: boolean -// } -// class TestServer { -// listen(handle, backlog: number, callback) -// listen(options: ServerListenOptions, callback) -// listen(path: string, backlog: number, callback) -// listen(port: number, host: string, backlog: number, callback) -// listen() {} -// } +class Server { + listen() { + const args = Array.prototype.slice.call(arguments); + const done = typeof args[args.length - 1] === 'function' ? args[args.length - 1] : null; + let rsServer = new index_1.Server(); + // (port, hostname, backlog[, callback]) + if (typeof args[0] === 'number' && typeof args[1] === 'string' && typeof args[2] === 'number') { + const options = { + port: args[0], + host: args[1], + backlog: args[2], + }; + rsServer.listenTcp(options, done); + } + // (port, hostname[, callback]) + else if (typeof args[0] === 'number' && typeof args[1] === 'string') { + const options = { + port: args[0], + host: args[1], + }; + rsServer.listenTcp(options, done); + } + // (port[, callback]) + else if (typeof args[0] === 'number') { + const options = { + port: args[0], + }; + rsServer.listenTcp(options, done); + } + // (path[, callback]) + else if (typeof args[0] === 'string') { + const options = { + path: args[0], + }; + rsServer.listenIpc(options, done); + } + // ([callback]) + else if (done) { + rsServer.listenTcp({}, done); + } + // (handle, listeningListener) + else { + throw new Error('Listening on handle is not supported in this implementation'); + } + return rsServer; + } +} function hyperjs() { - return new index_1.Server(); + return new Server(); } exports.default = hyperjs; module.exports = Object.assign(hyperjs, { Request: index_1.Request, Response: index_1.Response }); diff --git a/index.ts b/index.ts index 0d05150..09a9a51 100644 --- a/index.ts +++ b/index.ts @@ -1,26 +1,70 @@ -import { Server, Request, Response } from './hyperjs-core/index' +import { + Server as RsServer, + Request, + Response, + TcpServerListenOptions, + IpcServerListenOptions, +} from './hyperjs-core/index' -// interface ServerListenOptions { -// backlog: number -// exclusive: boolean -// host: string -// ipv6Only: boolean -// reusePort: boolean -// path: string -// port: number -// readableAll: boolean -// signal: AbortSignal -// writableAll: boolean -// } +class Server { + /** + * Listen for connections. + */ + listen(port: number, hostname: string, backlog: number, callback?: (error?: Error) => void): RsServer + listen(port: number, hostname: string, callback?: (error?: Error) => void): RsServer + listen(port: number, callback?: (error?: Error) => void): RsServer + listen(callback?: (error?: Error) => void): RsServer + listen(path: string, callback?: (error?: Error) => void): RsServer + listen(handle: any, listeningListener?: (error?: Error) => void): RsServer -// class TestServer { -// listen(handle, backlog: number, callback) -// listen(options: ServerListenOptions, callback) -// listen(path: string, backlog: number, callback) -// listen(port: number, host: string, backlog: number, callback) + listen(): RsServer { + const args = Array.prototype.slice.call(arguments) + const done = typeof args[args.length - 1] === 'function' ? args[args.length - 1] : null + let rsServer: RsServer = new RsServer() -// listen() {} -// } + // (port, hostname, backlog[, callback]) + if (typeof args[0] === 'number' && typeof args[1] === 'string' && typeof args[2] === 'number') { + const options: TcpServerListenOptions = { + port: args[0], + host: args[1], + backlog: args[2], + } + rsServer.listenTcp(options, done) + } + // (port, hostname[, callback]) + else if (typeof args[0] === 'number' && typeof args[1] === 'string') { + const options: TcpServerListenOptions = { + port: args[0], + host: args[1], + } + rsServer.listenTcp(options, done) + } + // (port[, callback]) + else if (typeof args[0] === 'number') { + const options: TcpServerListenOptions = { + port: args[0], + } + rsServer.listenTcp(options, done) + } + // (path[, callback]) + else if (typeof args[0] === 'string') { + const options: IpcServerListenOptions = { + path: args[0], + } + rsServer.listenIpc(options, done) + } + // ([callback]) + else if (done) { + rsServer.listenTcp({}, done) + } + // (handle, listeningListener) + else { + throw new Error('Listening on handle is not supported in this implementation') + } + + return rsServer + } +} function hyperjs() { return new Server() diff --git a/src/server/listen_ipc.rs b/src/server/listen_ipc.rs index b957c9b..fa2cb16 100644 --- a/src/server/listen_ipc.rs +++ b/src/server/listen_ipc.rs @@ -24,14 +24,6 @@ use crate::server::ThreadsafeCallbackFn; #[napi(object)] pub struct IpcServerListenOptions { - /// The maximum number of pending connections that can be queued before the OS - /// starts refusing new ones. Passed directly to the underlying `listen(2)` syscall. - /// - /// A value of `0` lets the OS choose a reasonable default. - /// - /// Default = 0 - pub backlog: u32, - /// Unix domain socket path the server should listen on. /// /// Ignored if [`port`] is also specified — TCP takes precedence. From b3b17400a13f65e1f5ee6ee9e141c95b852fa522 Mon Sep 17 00:00:00 2001 From: Samuel Mwangi Date: Mon, 27 Apr 2026 05:52:16 +0300 Subject: [PATCH 13/15] Separate tests testing the NAPI-generated TypeScript code from tests testing the hand-authored Express API parity TypeScript code. --- __test__/{ => core}/health.spec.ts | 0 __test__/{ => core}/middlewares/cookie_parser.spec.ts | 0 __test__/{ => core}/middlewares/json.spec.ts | 0 __test__/{ => core}/middlewares/raw.spec.ts | 0 __test__/{ => core}/middlewares/static.spec.ts | 4 ++-- __test__/{ => core}/middlewares/text.spec.ts | 0 __test__/{ => core}/middlewares/urlencoded.spec.ts | 0 __test__/{ => core}/request/accepts.spec.ts | 2 +- __test__/{ => core}/request/method.spec.ts | 0 __test__/{ => core}/request/range.spec.ts | 0 __test__/{ => core}/response/append.spec.ts | 2 +- __test__/{ => core}/response/attachment.spec.ts | 2 +- __test__/{ => core}/response/clear_cookie.spec.ts | 2 +- __test__/{ => core}/response/content_type.spec.ts | 2 +- __test__/{ => core}/response/cookie.spec.ts | 2 +- __test__/{ => core}/response/download.spec.ts | 2 +- __test__/{ => core}/response/end.spec.ts | 0 __test__/{ => core}/response/format.spec.ts | 0 __test__/{ => core}/response/links.spec.ts | 2 +- __test__/{ => core}/response/location.spec.ts | 2 +- __test__/{ => core}/response/redirect.spec.ts | 0 __test__/{ => core}/response/send_file.spec.ts | 4 ++-- __test__/{ => core}/response/set.spec.ts | 2 +- __test__/{ => core}/response/vary.spec.ts | 2 +- __test__/{ => core}/server-setup.ts | 2 +- __test__/{ => core}/server.ts | 2 +- __test__/{ => core}/utilities.spec.ts | 2 +- 27 files changed, 18 insertions(+), 18 deletions(-) rename __test__/{ => core}/health.spec.ts (100%) rename __test__/{ => core}/middlewares/cookie_parser.spec.ts (100%) rename __test__/{ => core}/middlewares/json.spec.ts (100%) rename __test__/{ => core}/middlewares/raw.spec.ts (100%) rename __test__/{ => core}/middlewares/static.spec.ts (93%) rename __test__/{ => core}/middlewares/text.spec.ts (100%) rename __test__/{ => core}/middlewares/urlencoded.spec.ts (100%) rename __test__/{ => core}/request/accepts.spec.ts (70%) rename __test__/{ => core}/request/method.spec.ts (100%) rename __test__/{ => core}/request/range.spec.ts (100%) rename __test__/{ => core}/response/append.spec.ts (89%) rename __test__/{ => core}/response/attachment.spec.ts (87%) rename __test__/{ => core}/response/clear_cookie.spec.ts (82%) rename __test__/{ => core}/response/content_type.spec.ts (92%) rename __test__/{ => core}/response/cookie.spec.ts (96%) rename __test__/{ => core}/response/download.spec.ts (94%) rename __test__/{ => core}/response/end.spec.ts (100%) rename __test__/{ => core}/response/format.spec.ts (100%) rename __test__/{ => core}/response/links.spec.ts (92%) rename __test__/{ => core}/response/location.spec.ts (84%) rename __test__/{ => core}/response/redirect.spec.ts (100%) rename __test__/{ => core}/response/send_file.spec.ts (94%) rename __test__/{ => core}/response/set.spec.ts (93%) rename __test__/{ => core}/response/vary.spec.ts (84%) rename __test__/{ => core}/server-setup.ts (95%) rename __test__/{ => core}/server.ts (99%) rename __test__/{ => core}/utilities.spec.ts (73%) diff --git a/__test__/health.spec.ts b/__test__/core/health.spec.ts similarity index 100% rename from __test__/health.spec.ts rename to __test__/core/health.spec.ts diff --git a/__test__/middlewares/cookie_parser.spec.ts b/__test__/core/middlewares/cookie_parser.spec.ts similarity index 100% rename from __test__/middlewares/cookie_parser.spec.ts rename to __test__/core/middlewares/cookie_parser.spec.ts diff --git a/__test__/middlewares/json.spec.ts b/__test__/core/middlewares/json.spec.ts similarity index 100% rename from __test__/middlewares/json.spec.ts rename to __test__/core/middlewares/json.spec.ts diff --git a/__test__/middlewares/raw.spec.ts b/__test__/core/middlewares/raw.spec.ts similarity index 100% rename from __test__/middlewares/raw.spec.ts rename to __test__/core/middlewares/raw.spec.ts diff --git a/__test__/middlewares/static.spec.ts b/__test__/core/middlewares/static.spec.ts similarity index 93% rename from __test__/middlewares/static.spec.ts rename to __test__/core/middlewares/static.spec.ts index 2a2e5fc..1c76ddb 100644 --- a/__test__/middlewares/static.spec.ts +++ b/__test__/core/middlewares/static.spec.ts @@ -28,7 +28,7 @@ test('fetch index.html in the public directory', async (t) => { const res = await axios.get(`http://localhost:${port}/public/index.html`) // Don't store the full response object, just extract the data const data = res.data - let index_file_contents = readFileSync(path.join(__dirname, '../../public/index.html'), { encoding: 'utf-8' }) + let index_file_contents = readFileSync(path.join(__dirname, '../../../public/index.html'), { encoding: 'utf-8' }) t.is(data, index_file_contents) }) @@ -36,7 +36,7 @@ test('fetch alternative_index.html in the public directory', async (t) => { const res = await axios.get(`http://localhost:${port}/public/alternative_index.html`) // Don't store the full response object, just extract the data const data = res.data - let index_file_contents = readFileSync(path.join(__dirname, '../../public/alternative_index.html'), { + let index_file_contents = readFileSync(path.join(__dirname, '../../../public/alternative_index.html'), { encoding: 'utf-8', }) t.is(data, index_file_contents) diff --git a/__test__/middlewares/text.spec.ts b/__test__/core/middlewares/text.spec.ts similarity index 100% rename from __test__/middlewares/text.spec.ts rename to __test__/core/middlewares/text.spec.ts diff --git a/__test__/middlewares/urlencoded.spec.ts b/__test__/core/middlewares/urlencoded.spec.ts similarity index 100% rename from __test__/middlewares/urlencoded.spec.ts rename to __test__/core/middlewares/urlencoded.spec.ts diff --git a/__test__/request/accepts.spec.ts b/__test__/core/request/accepts.spec.ts similarity index 70% rename from __test__/request/accepts.spec.ts rename to __test__/core/request/accepts.spec.ts index aa89b19..5a195fc 100644 --- a/__test__/request/accepts.spec.ts +++ b/__test__/core/request/accepts.spec.ts @@ -1,6 +1,6 @@ import test from 'ava' -import { Request } from '../../hyperjs-core/index' +import { Request } from '../../../hyperjs-core/index' test('accepts', (t) => { const req = new Request() diff --git a/__test__/request/method.spec.ts b/__test__/core/request/method.spec.ts similarity index 100% rename from __test__/request/method.spec.ts rename to __test__/core/request/method.spec.ts diff --git a/__test__/request/range.spec.ts b/__test__/core/request/range.spec.ts similarity index 100% rename from __test__/request/range.spec.ts rename to __test__/core/request/range.spec.ts diff --git a/__test__/response/append.spec.ts b/__test__/core/response/append.spec.ts similarity index 89% rename from __test__/response/append.spec.ts rename to __test__/core/response/append.spec.ts index 3e32266..19b6c4e 100644 --- a/__test__/response/append.spec.ts +++ b/__test__/core/response/append.spec.ts @@ -1,6 +1,6 @@ import test from 'ava' -import { Response } from '../../hyperjs-core/index' +import { Response } from '../../../hyperjs-core/index' test('append', (t) => { const res = new Response() diff --git a/__test__/response/attachment.spec.ts b/__test__/core/response/attachment.spec.ts similarity index 87% rename from __test__/response/attachment.spec.ts rename to __test__/core/response/attachment.spec.ts index 90c6de2..af610eb 100644 --- a/__test__/response/attachment.spec.ts +++ b/__test__/core/response/attachment.spec.ts @@ -1,6 +1,6 @@ import test from 'ava' -import { Response } from '../../hyperjs-core/index' +import { Response } from '../../../hyperjs-core/index' test('attachment - no value', (t) => { const res = new Response() diff --git a/__test__/response/clear_cookie.spec.ts b/__test__/core/response/clear_cookie.spec.ts similarity index 82% rename from __test__/response/clear_cookie.spec.ts rename to __test__/core/response/clear_cookie.spec.ts index 5d85b39..7d1095a 100644 --- a/__test__/response/clear_cookie.spec.ts +++ b/__test__/core/response/clear_cookie.spec.ts @@ -1,6 +1,6 @@ import test from 'ava' -import { Response } from '../../hyperjs-core/index' +import { Response } from '../../../hyperjs-core/index' test('clearCookie', (t) => { let res = new Response() diff --git a/__test__/response/content_type.spec.ts b/__test__/core/response/content_type.spec.ts similarity index 92% rename from __test__/response/content_type.spec.ts rename to __test__/core/response/content_type.spec.ts index b35ff4d..e9f60c5 100644 --- a/__test__/response/content_type.spec.ts +++ b/__test__/core/response/content_type.spec.ts @@ -1,6 +1,6 @@ import test from 'ava' -import { Response } from '../../hyperjs-core/index' +import { Response } from '../../../hyperjs-core/index' test('type', (t) => { const res = new Response() diff --git a/__test__/response/cookie.spec.ts b/__test__/core/response/cookie.spec.ts similarity index 96% rename from __test__/response/cookie.spec.ts rename to __test__/core/response/cookie.spec.ts index ee30626..77d468e 100644 --- a/__test__/response/cookie.spec.ts +++ b/__test__/core/response/cookie.spec.ts @@ -1,6 +1,6 @@ import test from 'ava' -import { Response } from '../../hyperjs-core/index' +import { Response } from '../../../hyperjs-core/index' test('cookie - multiple', (t) => { const res = new Response() diff --git a/__test__/response/download.spec.ts b/__test__/core/response/download.spec.ts similarity index 94% rename from __test__/response/download.spec.ts rename to __test__/core/response/download.spec.ts index 02842b5..26d1501 100644 --- a/__test__/response/download.spec.ts +++ b/__test__/core/response/download.spec.ts @@ -29,6 +29,6 @@ test('/download', async (t) => { const data = res.data const contentDispositionHeader = res.headers['content-disposition'] t.is(contentDispositionHeader, 'attachment; filename=".dotfile.html"') - let dotfile_contents = readFileSync(path.join(__dirname, '../../public/.dotfile.html'), { encoding: 'utf-8' }) + let dotfile_contents = readFileSync(path.join(__dirname, '../../../public/.dotfile.html'), { encoding: 'utf-8' }) t.is(data, dotfile_contents) }) diff --git a/__test__/response/end.spec.ts b/__test__/core/response/end.spec.ts similarity index 100% rename from __test__/response/end.spec.ts rename to __test__/core/response/end.spec.ts diff --git a/__test__/response/format.spec.ts b/__test__/core/response/format.spec.ts similarity index 100% rename from __test__/response/format.spec.ts rename to __test__/core/response/format.spec.ts diff --git a/__test__/response/links.spec.ts b/__test__/core/response/links.spec.ts similarity index 92% rename from __test__/response/links.spec.ts rename to __test__/core/response/links.spec.ts index aba91e9..d8dfd06 100644 --- a/__test__/response/links.spec.ts +++ b/__test__/core/response/links.spec.ts @@ -1,6 +1,6 @@ import test from 'ava' -import { Response } from '../../hyperjs-core/index' +import { Response } from '../../../hyperjs-core/index' test('links', (t) => { let res = new Response() diff --git a/__test__/response/location.spec.ts b/__test__/core/response/location.spec.ts similarity index 84% rename from __test__/response/location.spec.ts rename to __test__/core/response/location.spec.ts index c316248..0824681 100644 --- a/__test__/response/location.spec.ts +++ b/__test__/core/response/location.spec.ts @@ -1,6 +1,6 @@ import test from 'ava' -import { Response } from '../../hyperjs-core/index' +import { Response } from '../../../hyperjs-core/index' test('location', (t) => { let res = new Response() diff --git a/__test__/response/redirect.spec.ts b/__test__/core/response/redirect.spec.ts similarity index 100% rename from __test__/response/redirect.spec.ts rename to __test__/core/response/redirect.spec.ts diff --git a/__test__/response/send_file.spec.ts b/__test__/core/response/send_file.spec.ts similarity index 94% rename from __test__/response/send_file.spec.ts rename to __test__/core/response/send_file.spec.ts index bb1629e..e4d9f32 100644 --- a/__test__/response/send_file.spec.ts +++ b/__test__/core/response/send_file.spec.ts @@ -27,7 +27,7 @@ test.after.always(() => { test('/send-file - dotfiles = allow', async (t) => { const res = await axios.get(`http://localhost:${port}/send-file/allow/.dotfile.html`) const data = res.data - let dotfile_contents = readFileSync(path.join(__dirname, '../../public/.dotfile.html'), { encoding: 'utf-8' }) + let dotfile_contents = readFileSync(path.join(__dirname, '../../../public/.dotfile.html'), { encoding: 'utf-8' }) t.is(data, dotfile_contents) }) @@ -60,6 +60,6 @@ test('/send-file - dotfiles = ignore', async (t) => { test('/folder - dir default index', async (t) => { const res = await axios.get(`http://localhost:${port}/folder`) const data = res.data - let index_file_contents = readFileSync(path.join(__dirname, '../../public/index.html'), { encoding: 'utf-8' }) + let index_file_contents = readFileSync(path.join(__dirname, '../../../public/index.html'), { encoding: 'utf-8' }) t.is(data, index_file_contents) }) diff --git a/__test__/response/set.spec.ts b/__test__/core/response/set.spec.ts similarity index 93% rename from __test__/response/set.spec.ts rename to __test__/core/response/set.spec.ts index a79ae89..3abdd38 100644 --- a/__test__/response/set.spec.ts +++ b/__test__/core/response/set.spec.ts @@ -1,6 +1,6 @@ import test from 'ava' -import { Response } from '../../hyperjs-core/index' +import { Response } from '../../../hyperjs-core/index' test('set - (string, string)', (t) => { let res = new Response() diff --git a/__test__/response/vary.spec.ts b/__test__/core/response/vary.spec.ts similarity index 84% rename from __test__/response/vary.spec.ts rename to __test__/core/response/vary.spec.ts index de3835c..73ba0a5 100644 --- a/__test__/response/vary.spec.ts +++ b/__test__/core/response/vary.spec.ts @@ -1,6 +1,6 @@ import test from 'ava' -import { Response } from '../../hyperjs-core/index' +import { Response } from '../../../hyperjs-core/index' test('vary', (t) => { let res = new Response() diff --git a/__test__/server-setup.ts b/__test__/core/server-setup.ts similarity index 95% rename from __test__/server-setup.ts rename to __test__/core/server-setup.ts index 78b8a7f..536dde1 100644 --- a/__test__/server-setup.ts +++ b/__test__/core/server-setup.ts @@ -7,7 +7,7 @@ const require = createRequire(import.meta.url) async function start(): Promise<{ process: ChildProcess; port: number }> { const port = Math.floor(Math.random() * 10000) + 10000 - const serverPath = join(process.cwd(), '__test__', 'server.ts') + const serverPath = join(process.cwd(), '__test__', 'core', 'server.ts') // Let Node find the correct path to the tsx package automatically const tsxEntry = require.resolve('tsx') diff --git a/__test__/server.ts b/__test__/core/server.ts similarity index 99% rename from __test__/server.ts rename to __test__/core/server.ts index 6956e34..277d249 100644 --- a/__test__/server.ts +++ b/__test__/core/server.ts @@ -10,7 +10,7 @@ import { FileStat, UrlencodedMiddleware, CookieParserMiddleware, -} from '../hyperjs-core/index.js' +} from '../../hyperjs-core/index.js' import path from 'path' import process from 'process' diff --git a/__test__/utilities.spec.ts b/__test__/core/utilities.spec.ts similarity index 73% rename from __test__/utilities.spec.ts rename to __test__/core/utilities.spec.ts index 3237573..4cb4aff 100644 --- a/__test__/utilities.spec.ts +++ b/__test__/core/utilities.spec.ts @@ -1,6 +1,6 @@ import test from 'ava' -import { serializeNapiObject } from '../hyperjs-core/index' +import { serializeNapiObject } from '../../hyperjs-core/index' test('serializeNapiObject', (t) => { t.is(serializeNapiObject({}), '{}') From 30b122bc2edfb398216e24d2b591f541c804b27e Mon Sep 17 00:00:00 2001 From: Samuel Mwangi Date: Fri, 12 Jun 2026 22:29:35 +0300 Subject: [PATCH 14/15] Align HyperJs.listen's implementation to Express'. --- __test__/.yarn/install-state.gz | Bin 0 -> 1392 bytes __test__/core/health.spec.ts | 6 +- .../core/middlewares/cookie_parser.spec.ts | 6 +- __test__/core/middlewares/json.spec.ts | 6 +- __test__/core/middlewares/raw.spec.ts | 6 +- __test__/core/middlewares/static.spec.ts | 6 +- __test__/core/middlewares/text.spec.ts | 6 +- __test__/core/middlewares/urlencoded.spec.ts | 6 +- __test__/core/request/method.spec.ts | 6 +- __test__/core/request/range.spec.ts | 6 +- __test__/core/response/download.spec.ts | 6 +- __test__/core/response/end.spec.ts | 6 +- __test__/core/response/format.spec.ts | 6 +- __test__/core/response/redirect.spec.ts | 6 +- __test__/core/response/send_file.spec.ts | 6 +- __test__/package.json | 5 +- __test__/public/hello.server.ts | 27 +++++ __test__/{core => }/server-setup.ts | 4 +- __test__/tsconfig.json | 11 +- __test__/yarn.lock | 30 ++++++ hyperjs-core/index.d.ts | 7 +- index.js | 50 +-------- index.ts | 71 +----------- package.json | 7 +- src-ts/hyper_js.js | 52 +++++++++ src-ts/hyper_js.ts | 68 ++++++++++++ src/server/_listen_tcp.rs | 2 +- src/server/listen_ipc.rs | 8 +- src/server/listen_tcp.rs | 44 ++++++-- src/server/mod.rs | 9 +- yarn.lock | 101 ++++++++++++++++++ 31 files changed, 404 insertions(+), 176 deletions(-) create mode 100644 __test__/.yarn/install-state.gz create mode 100644 __test__/public/hello.server.ts rename __test__/{core => }/server-setup.ts (89%) create mode 100644 __test__/yarn.lock create mode 100644 src-ts/hyper_js.js create mode 100644 src-ts/hyper_js.ts diff --git a/__test__/.yarn/install-state.gz b/__test__/.yarn/install-state.gz new file mode 100644 index 0000000000000000000000000000000000000000..f0cf28e1c9b7ce51ffacbfcecd666469d8dd8b67 GIT binary patch literal 1392 zcmV-$1&{h4iwFP!000001J#(zZyd)F#zmQwZ0lh;PLN#Fm*gN3)z9i)o+1m154I!N zJ`1Q+$FXt?9u1{nQO~WNzlZ9o4=W^>|^^*XQfpQ}=IMP4jVZv-s2K_NMi?vIBRvlS0GR zI9s(4YDpwOh)H^Epfy)WYMna8QhNx!V(VBuuwo%VuYxfFaW#4*a z?4nYc&6kiHa_7d;8bm|t*+%#A5e@Th(|U9t@q9R#-EsFPS0ox}^sd`Inolyi zM{j9*J|A>Gy!q$J^5yBy>0NiW)1qJtTvSUhgcLaR-o@9DfK;ia3MMJ^nkfoHPK?qy z8Zi3JsZ5*p$fXLW3%-8HS2H6f3|zN3kYcMvGiWbEdcEnu=4aH_l1mu&e!vCpv9FyPw%)VI-iIarq_dy z$K}P!=g0Gfo`*g^Z(Mz07le(g>EIT~JGxKiYQy>~n%=sz9jvKPf^Mbfp0%OHWXRQX z0Zp}I@zB~>yKuoSb!1lhE(#6Vb5jXtIT6+vp()B-~ZAu?vB z+?a%g)pJMF+%-gBlw$H2MTArZRW>cgSowy&6?251t1yuo3$>00F+~ovX*(!BcY$0A zSD_q{oA+J}0aYZ4$DBP17+@+uU6E=DW(*vCi)kZw?lpB#Bd7*B*4A6}QhStcm-@QB z^~utvOO-n#v)3W-mwh-FwIBH(S+`xi_hkJK?)$cpb$0LFg#ON^ zadEsE-?w5y-;NZv-ix=whHM?(?K`2_J@@#cPhOE6(i^)KP4Yq7mK3Q3} zl@0Q~k8$_kU1;`TANIjLT+s05nBLg8o5S5d9u9}`$H9HPx!C@2vNX5*{l8xR;6A-F z%oDhWYD*i`2AN+Fvu!8x`p>hw?#a?@_2PPFvyGv@7Wl^2v { - const result = await server.start() + const result = await server.start(serverPath) serverApp = result.process port = result.port }) diff --git a/__test__/core/middlewares/cookie_parser.spec.ts b/__test__/core/middlewares/cookie_parser.spec.ts index 50b90a4..4bb0db0 100644 --- a/__test__/core/middlewares/cookie_parser.spec.ts +++ b/__test__/core/middlewares/cookie_parser.spec.ts @@ -2,14 +2,16 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios from 'axios' +import { join } from 'node:path' -import * as server from '../server-setup.js' +import * as server from '../../server-setup.js' let serverApp: ChildProcess let port: number +const serverPath = join(process.cwd(), '__test__', 'core', 'server.ts') test.before(async () => { - const result = await server.start() + const result = await server.start(serverPath) serverApp = result.process port = result.port }) diff --git a/__test__/core/middlewares/json.spec.ts b/__test__/core/middlewares/json.spec.ts index f9a4a09..1da2867 100644 --- a/__test__/core/middlewares/json.spec.ts +++ b/__test__/core/middlewares/json.spec.ts @@ -2,14 +2,16 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios from 'axios' +import { join } from 'node:path' -import * as server from '../server-setup.js' +import * as server from '../../server-setup.js' let serverApp: ChildProcess let port: number +const serverPath = join(process.cwd(), '__test__', 'core', 'server.ts') test.before(async () => { - const result = await server.start() + const result = await server.start(serverPath) serverApp = result.process port = result.port }) diff --git a/__test__/core/middlewares/raw.spec.ts b/__test__/core/middlewares/raw.spec.ts index 9fa5522..aaef72f 100644 --- a/__test__/core/middlewares/raw.spec.ts +++ b/__test__/core/middlewares/raw.spec.ts @@ -2,14 +2,16 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios from 'axios' +import { join } from 'node:path' -import * as server from '../server-setup.js' +import * as server from '../../server-setup.js' let serverApp: ChildProcess let port: number +const serverPath = join(process.cwd(), '__test__', 'core', 'server.ts') test.before(async () => { - const result = await server.start() + const result = await server.start(serverPath) serverApp = result.process port = result.port }) diff --git a/__test__/core/middlewares/static.spec.ts b/__test__/core/middlewares/static.spec.ts index 1c76ddb..ad2b540 100644 --- a/__test__/core/middlewares/static.spec.ts +++ b/__test__/core/middlewares/static.spec.ts @@ -2,8 +2,9 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios from 'axios' +import { join } from 'node:path' -import * as server from '../server-setup.js' +import * as server from '../../server-setup.js' import { readFileSync } from 'node:fs' import path, { dirname } from 'node:path' import { fileURLToPath } from 'node:url' @@ -13,9 +14,10 @@ const __dirname = dirname(__filename) let serverApp: ChildProcess let port: number +const serverPath = join(process.cwd(), '__test__', 'core', 'server.ts') test.before(async () => { - const result = await server.start() + const result = await server.start(serverPath) serverApp = result.process port = result.port }) diff --git a/__test__/core/middlewares/text.spec.ts b/__test__/core/middlewares/text.spec.ts index 7fb4634..457c228 100644 --- a/__test__/core/middlewares/text.spec.ts +++ b/__test__/core/middlewares/text.spec.ts @@ -2,14 +2,16 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios from 'axios' +import { join } from 'node:path' -import * as server from '../server-setup.js' +import * as server from '../../server-setup.js' let serverApp: ChildProcess let port: number +const serverPath = join(process.cwd(), '__test__', 'core', 'server.ts') test.before(async () => { - const result = await server.start() + const result = await server.start(serverPath) serverApp = result.process port = result.port }) diff --git a/__test__/core/middlewares/urlencoded.spec.ts b/__test__/core/middlewares/urlencoded.spec.ts index d87d4cd..f8b6f1c 100644 --- a/__test__/core/middlewares/urlencoded.spec.ts +++ b/__test__/core/middlewares/urlencoded.spec.ts @@ -2,14 +2,16 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios from 'axios' +import { join } from 'node:path' -import * as server from '../server-setup.js' +import * as server from '../../server-setup.js' let serverApp: ChildProcess let port: number +const serverPath = join(process.cwd(), '__test__', 'core', 'server.ts') test.before(async () => { - const result = await server.start() + const result = await server.start(serverPath) serverApp = result.process port = result.port }) diff --git a/__test__/core/request/method.spec.ts b/__test__/core/request/method.spec.ts index 33a9b29..a3177eb 100644 --- a/__test__/core/request/method.spec.ts +++ b/__test__/core/request/method.spec.ts @@ -1,14 +1,16 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios from 'axios' +import { join } from 'node:path' -import * as server from '../server-setup.js' +import * as server from '../../server-setup.js' let serverApp: ChildProcess let port: number +const serverPath = join(process.cwd(), '__test__', 'core', 'server.ts') test.before(async () => { - const result = await server.start() + const result = await server.start(serverPath) serverApp = result.process port = result.port }) diff --git a/__test__/core/request/range.spec.ts b/__test__/core/request/range.spec.ts index bb9186e..1bd6163 100644 --- a/__test__/core/request/range.spec.ts +++ b/__test__/core/request/range.spec.ts @@ -1,14 +1,16 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios from 'axios' +import { join } from 'node:path' -import * as server from '../server-setup.js' +import * as server from '../../server-setup.js' let serverApp: ChildProcess let port: number +const serverPath = join(process.cwd(), '__test__', 'core', 'server.ts') test.before(async () => { - const result = await server.start() + const result = await server.start(serverPath) serverApp = result.process port = result.port }) diff --git a/__test__/core/response/download.spec.ts b/__test__/core/response/download.spec.ts index 26d1501..4909414 100644 --- a/__test__/core/response/download.spec.ts +++ b/__test__/core/response/download.spec.ts @@ -1,8 +1,9 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios from 'axios' +import { join } from 'node:path' -import * as server from '../server-setup.js' +import * as server from '../../server-setup.js' import { readFileSync } from 'node:fs' import { fileURLToPath } from 'url' import { dirname } from 'path' @@ -13,9 +14,10 @@ const __dirname = dirname(__filename) let serverApp: ChildProcess let port: number +const serverPath = join(process.cwd(), '__test__', 'core', 'server.ts') test.before(async () => { - const result = await server.start() + const result = await server.start(serverPath) serverApp = result.process port = result.port }) diff --git a/__test__/core/response/end.spec.ts b/__test__/core/response/end.spec.ts index 2d67de1..1e7c6b5 100644 --- a/__test__/core/response/end.spec.ts +++ b/__test__/core/response/end.spec.ts @@ -2,14 +2,16 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios from 'axios' +import { join } from 'node:path' -import * as server from '../server-setup.js' +import * as server from '../../server-setup.js' let serverApp: ChildProcess let port: number +const serverPath = join(process.cwd(), '__test__', 'core', 'server.ts') test.before(async () => { - const result = await server.start() + const result = await server.start(serverPath) serverApp = result.process port = result.port }) diff --git a/__test__/core/response/format.spec.ts b/__test__/core/response/format.spec.ts index 6ef1f7f..b83181f 100644 --- a/__test__/core/response/format.spec.ts +++ b/__test__/core/response/format.spec.ts @@ -1,14 +1,16 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios from 'axios' +import { join } from 'node:path' -import * as server from '../server-setup.js' +import * as server from '../../server-setup.js' let serverApp: ChildProcess let port: number +const serverPath = join(process.cwd(), '__test__', 'core', 'server.ts') test.before(async () => { - const result = await server.start() + const result = await server.start(serverPath) serverApp = result.process port = result.port }) diff --git a/__test__/core/response/redirect.spec.ts b/__test__/core/response/redirect.spec.ts index 53a285d..0418257 100644 --- a/__test__/core/response/redirect.spec.ts +++ b/__test__/core/response/redirect.spec.ts @@ -2,14 +2,16 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios, { isAxiosError } from 'axios' +import { join } from 'node:path' -import * as server from '../server-setup.js' +import * as server from '../../server-setup.js' let serverApp: ChildProcess let port: number +const serverPath = join(process.cwd(), '__test__', 'core', 'server.ts') test.before(async () => { - const result = await server.start() + const result = await server.start(serverPath) serverApp = result.process port = result.port }) diff --git a/__test__/core/response/send_file.spec.ts b/__test__/core/response/send_file.spec.ts index e4d9f32..05f99db 100644 --- a/__test__/core/response/send_file.spec.ts +++ b/__test__/core/response/send_file.spec.ts @@ -1,8 +1,9 @@ import test from 'ava' import { ChildProcess } from 'node:child_process' import axios, { isAxiosError } from 'axios' +import { join } from 'node:path' -import * as server from '../server-setup.js' +import * as server from '../../server-setup.js' import { readFileSync } from 'node:fs' import { fileURLToPath } from 'url' import { dirname } from 'path' @@ -13,9 +14,10 @@ const __dirname = dirname(__filename) let serverApp: ChildProcess let port: number +const serverPath = join(process.cwd(), '__test__', 'core', 'server.ts') test.before(async () => { - const result = await server.start() + const result = await server.start(serverPath) serverApp = result.process port = result.port }) diff --git a/__test__/package.json b/__test__/package.json index 3dbc1ca..cb671d8 100644 --- a/__test__/package.json +++ b/__test__/package.json @@ -1,3 +1,6 @@ { - "type": "module" + "type": "module", + "dependencies": { + "@types/node": "^25.6.0" + } } diff --git a/__test__/public/hello.server.ts b/__test__/public/hello.server.ts new file mode 100644 index 0000000..03756c9 --- /dev/null +++ b/__test__/public/hello.server.ts @@ -0,0 +1,27 @@ +// __test__/health.spec.ts +import test from 'ava' +import { ChildProcess } from 'node:child_process' +import axios from 'axios' +import { join } from 'node:path' + +import * as server from '../server-setup.js' + +let serverApp: ChildProcess +let port: number +const serverPath = join(process.cwd(), '__test__', 'core', 'server.ts') + +test.before(async () => { + const result = await server.start(serverPath) + serverApp = result.process + port = result.port +}) + +test.after.always(() => { + server.stop(serverApp) +}) + +test('/health', async (t) => { + const res = await axios.get(`http://localhost:${port}/health`) + const data = res.data + t.is(data, 'OK') +}) diff --git a/__test__/core/server-setup.ts b/__test__/server-setup.ts similarity index 89% rename from __test__/core/server-setup.ts rename to __test__/server-setup.ts index 536dde1..f5ef57c 100644 --- a/__test__/core/server-setup.ts +++ b/__test__/server-setup.ts @@ -1,13 +1,11 @@ import { ChildProcess, spawn } from 'node:child_process' -import { join } from 'node:path' import { pathToFileURL } from 'node:url' import { createRequire } from 'node:module' const require = createRequire(import.meta.url) -async function start(): Promise<{ process: ChildProcess; port: number }> { +async function start(serverPath: string): Promise<{ process: ChildProcess; port: number }> { const port = Math.floor(Math.random() * 10000) + 10000 - const serverPath = join(process.cwd(), '__test__', 'core', 'server.ts') // Let Node find the correct path to the tsx package automatically const tsxEntry = require.resolve('tsx') diff --git a/__test__/tsconfig.json b/__test__/tsconfig.json index ec0d2a6..42b1815 100644 --- a/__test__/tsconfig.json +++ b/__test__/tsconfig.json @@ -4,14 +4,9 @@ "module": "ESNext", "moduleResolution": "Bundler", "outDir": "lib", - "rootDir": "." + "rootDir": ".", + "types": ["node"] }, - "include": [ - "**/*.spec.ts" - // "response/format.spec.ts", - // "response/redirect.spec.ts", - // "response/end.spec.ts", - // "middlewares/text.spec.ts" - ], + "include": ["**/*.ts"], "exclude": ["lib"] } diff --git a/__test__/yarn.lock b/__test__/yarn.lock new file mode 100644 index 0000000..045efcb --- /dev/null +++ b/__test__/yarn.lock @@ -0,0 +1,30 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@types/node@npm:^25.6.0": + version: 25.6.0 + resolution: "@types/node@npm:25.6.0" + dependencies: + undici-types: "npm:~7.19.0" + checksum: 10c0/d2d2015630ff098a201407f55f5077a20270ae4f465c739b40865cd9933b91b9c5d2b85568eadaf3db0801b91e267333ca7eb39f007428b173d1cdab4b339ac5 + languageName: node + linkType: hard + +"root-workspace-0b6124@workspace:.": + version: 0.0.0-use.local + resolution: "root-workspace-0b6124@workspace:." + dependencies: + "@types/node": "npm:^25.6.0" + languageName: unknown + linkType: soft + +"undici-types@npm:~7.19.0": + version: 7.19.2 + resolution: "undici-types@npm:7.19.2" + checksum: 10c0/7159f10546f9f6c47d36776bb1bbf8671e87c1e587a6fee84ae1f111ae8de4f914efa8ca0dfcd224f4f4a9dfc3f6028f627ccb5ddaccf82d7fd54671b89fac3e + languageName: node + linkType: hard diff --git a/hyperjs-core/index.d.ts b/hyperjs-core/index.d.ts index dadd8b9..4d9bfa8 100644 --- a/hyperjs-core/index.d.ts +++ b/hyperjs-core/index.d.ts @@ -764,8 +764,8 @@ export declare class Response { /** HTTP Server that integrates with JavaScript handlers via Router */ export declare class Server { - listenIpc(options: IpcServerListenOptions, callback: (arg: string) => void): void - listenTcp(options: TcpServerListenOptions, callback: (arg: string) => void): void + listenIpc(options: IpcServerListenOptions, callback: ListenCallbackFn): void + listenTcp(options: TcpServerListenOptions, callback: ListenCallbackFn): void /** Create a new server with a router */ constructor() delete(route: string, handler: JsHandlerFn): void @@ -1345,6 +1345,9 @@ export interface JsUrlencodedOptions { depth?: number } +export type ListenCallbackFn<> = + (arg: Error | undefined | null) => void + /** Represents a single byte range with start and end positions */ export interface Range { start: number diff --git a/index.js b/index.js index 92a2231..c46c558 100644 --- a/index.js +++ b/index.js @@ -1,55 +1,9 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const index_1 = require("./hyperjs-core/index"); -class Server { - listen() { - const args = Array.prototype.slice.call(arguments); - const done = typeof args[args.length - 1] === 'function' ? args[args.length - 1] : null; - let rsServer = new index_1.Server(); - // (port, hostname, backlog[, callback]) - if (typeof args[0] === 'number' && typeof args[1] === 'string' && typeof args[2] === 'number') { - const options = { - port: args[0], - host: args[1], - backlog: args[2], - }; - rsServer.listenTcp(options, done); - } - // (port, hostname[, callback]) - else if (typeof args[0] === 'number' && typeof args[1] === 'string') { - const options = { - port: args[0], - host: args[1], - }; - rsServer.listenTcp(options, done); - } - // (port[, callback]) - else if (typeof args[0] === 'number') { - const options = { - port: args[0], - }; - rsServer.listenTcp(options, done); - } - // (path[, callback]) - else if (typeof args[0] === 'string') { - const options = { - path: args[0], - }; - rsServer.listenIpc(options, done); - } - // ([callback]) - else if (done) { - rsServer.listenTcp({}, done); - } - // (handle, listeningListener) - else { - throw new Error('Listening on handle is not supported in this implementation'); - } - return rsServer; - } -} +const hyper_js_1 = require("./src-ts/hyper_js"); function hyperjs() { - return new Server(); + return new hyper_js_1.HyperJs(); } exports.default = hyperjs; module.exports = Object.assign(hyperjs, { Request: index_1.Request, Response: index_1.Response }); diff --git a/index.ts b/index.ts index 09a9a51..93e6781 100644 --- a/index.ts +++ b/index.ts @@ -1,73 +1,8 @@ -import { - Server as RsServer, - Request, - Response, - TcpServerListenOptions, - IpcServerListenOptions, -} from './hyperjs-core/index' - -class Server { - /** - * Listen for connections. - */ - listen(port: number, hostname: string, backlog: number, callback?: (error?: Error) => void): RsServer - listen(port: number, hostname: string, callback?: (error?: Error) => void): RsServer - listen(port: number, callback?: (error?: Error) => void): RsServer - listen(callback?: (error?: Error) => void): RsServer - listen(path: string, callback?: (error?: Error) => void): RsServer - listen(handle: any, listeningListener?: (error?: Error) => void): RsServer - - listen(): RsServer { - const args = Array.prototype.slice.call(arguments) - const done = typeof args[args.length - 1] === 'function' ? args[args.length - 1] : null - let rsServer: RsServer = new RsServer() - - // (port, hostname, backlog[, callback]) - if (typeof args[0] === 'number' && typeof args[1] === 'string' && typeof args[2] === 'number') { - const options: TcpServerListenOptions = { - port: args[0], - host: args[1], - backlog: args[2], - } - rsServer.listenTcp(options, done) - } - // (port, hostname[, callback]) - else if (typeof args[0] === 'number' && typeof args[1] === 'string') { - const options: TcpServerListenOptions = { - port: args[0], - host: args[1], - } - rsServer.listenTcp(options, done) - } - // (port[, callback]) - else if (typeof args[0] === 'number') { - const options: TcpServerListenOptions = { - port: args[0], - } - rsServer.listenTcp(options, done) - } - // (path[, callback]) - else if (typeof args[0] === 'string') { - const options: IpcServerListenOptions = { - path: args[0], - } - rsServer.listenIpc(options, done) - } - // ([callback]) - else if (done) { - rsServer.listenTcp({}, done) - } - // (handle, listeningListener) - else { - throw new Error('Listening on handle is not supported in this implementation') - } - - return rsServer - } -} +import { Request, Response } from './hyperjs-core/index' +import { HyperJs } from './src-ts/hyper_js' function hyperjs() { - return new Server() + return new HyperJs() } export default hyperjs diff --git a/package.json b/package.json index 040e9e3..afbb7ba 100644 --- a/package.json +++ b/package.json @@ -115,5 +115,10 @@ "singleQuote": true, "arrowParens": "always" }, - "packageManager": "yarn@4.12.0" + "packageManager": "yarn@4.12.0", + "dependencies": { + "@types/express": "^5.0.6", + "@types/express-serve-static-core": "^5.1.1", + "@types/qs": "^6.15.0" + } } diff --git a/src-ts/hyper_js.js b/src-ts/hyper_js.js new file mode 100644 index 0000000..523e6e3 --- /dev/null +++ b/src-ts/hyper_js.js @@ -0,0 +1,52 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.HyperJs = void 0; +const index_1 = require("../hyperjs-core/index"); +class HyperJs { + listen() { + const args = Array.prototype.slice.call(arguments); + const done = typeof args[args.length - 1] === 'function' ? args[args.length - 1] : () => { }; + let rsServer = new index_1.Server(); + // (port, hostname, backlog[, callback]) + if (typeof args[0] === 'number' && typeof args[1] === 'string' && typeof args[2] === 'number') { + const options = { + port: args[0], + host: args[1], + backlog: args[2], + }; + rsServer.listenTcp(options, done); + } + // (port, hostname[, callback]) + else if (typeof args[0] === 'number' && typeof args[1] === 'string') { + const options = { + port: args[0], + host: args[1], + }; + rsServer.listenTcp(options, done); + } + // (port[, callback]) + else if (typeof args[0] === 'number') { + const options = { + port: args[0], + }; + rsServer.listenTcp(options, done); + } + // (path[, callback]) + else if (typeof args[0] === 'string') { + const options = { + path: args[0], + }; + rsServer.listenIpc(options, done); + } + // ([callback]) + else if (done) { + rsServer.listenTcp({}, done); + } + // (handle, listeningListener) + else { + throw new Error('Listening on handle is not supported in this implementation'); + } + return rsServer; + } +} +exports.HyperJs = HyperJs; diff --git a/src-ts/hyper_js.ts b/src-ts/hyper_js.ts new file mode 100644 index 0000000..bdf6228 --- /dev/null +++ b/src-ts/hyper_js.ts @@ -0,0 +1,68 @@ +import { + Server as RsServer, + TcpServerListenOptions, + IpcServerListenOptions, + ListenCallbackFn, +} from '../hyperjs-core/index' + +class HyperJs { + /** + * Listen for connections. + */ + listen(port: number, hostname: string, backlog: number, callback?: ListenCallbackFn): RsServer + listen(port: number, hostname: string, callback?: ListenCallbackFn): RsServer + listen(port: number, callback?: ListenCallbackFn): RsServer + listen(callback?: ListenCallbackFn): RsServer + listen(path: string, callback?: ListenCallbackFn): RsServer + listen(handle: any, listeningListener?: ListenCallbackFn): RsServer + + listen(): RsServer { + const args = Array.prototype.slice.call(arguments) + const done: ListenCallbackFn = typeof args[args.length - 1] === 'function' ? args[args.length - 1] : () => {} + let rsServer: RsServer = new RsServer() + + // (port, hostname, backlog[, callback]) + if (typeof args[0] === 'number' && typeof args[1] === 'string' && typeof args[2] === 'number') { + const options: TcpServerListenOptions = { + port: args[0], + host: args[1], + backlog: args[2], + } + rsServer.listenTcp(options, done) + } + // (port, hostname[, callback]) + else if (typeof args[0] === 'number' && typeof args[1] === 'string') { + const options: TcpServerListenOptions = { + port: args[0], + host: args[1], + } + rsServer.listenTcp(options, done) + } + // (port[, callback]) + else if (typeof args[0] === 'number') { + const options: TcpServerListenOptions = { + port: args[0], + } + rsServer.listenTcp(options, done) + } + // (path[, callback]) + else if (typeof args[0] === 'string') { + const options: IpcServerListenOptions = { + path: args[0], + } + rsServer.listenIpc(options, done) + } + // ([callback]) + else if (done) { + rsServer.listenTcp({}, done) + } + // (handle, listeningListener) + else { + throw new Error('Listening on handle is not supported in this implementation') + } + + return rsServer + } +} + +export { HyperJs } diff --git a/src/server/_listen_tcp.rs b/src/server/_listen_tcp.rs index 3972e66..a90c0c9 100644 --- a/src/server/_listen_tcp.rs +++ b/src/server/_listen_tcp.rs @@ -41,7 +41,7 @@ impl Server { #[cfg(unix)] systemd_notify(&server_status_message); - callback.call(addr.to_string(), ThreadsafeFunctionCallMode::Blocking); + callback.call(None, ThreadsafeFunctionCallMode::Blocking); match acme_config_meta { Some(acme) => { diff --git a/src/server/listen_ipc.rs b/src/server/listen_ipc.rs index fa2cb16..b242027 100644 --- a/src/server/listen_ipc.rs +++ b/src/server/listen_ipc.rs @@ -20,7 +20,7 @@ use tokio::net::windows::named_pipe::ServerOptions; #[cfg(unix)] use super::systemd_notify::systemd_notify; use super::{Server, create_handler_task::create_handler_task}; -use crate::server::ThreadsafeCallbackFn; +use crate::server::{ListenCallbackFn, ThreadsafeCallbackFn}; #[napi(object)] pub struct IpcServerListenOptions { @@ -128,7 +128,7 @@ impl Server { systemd_notify(&server_status_message); - callback.call(addr.to_string(), ThreadsafeFunctionCallMode::Blocking); + callback.call(None, ThreadsafeFunctionCallMode::Blocking); loop { let (stream, _) = ipc_listener.accept().await.unwrap(); @@ -191,10 +191,10 @@ impl Server { pub fn listen_ipc( &self, options: IpcServerListenOptions, - callback: Function, + callback: ListenCallbackFn, ) -> Result<()> { let ts_callback = callback.build_threadsafe_function().build_callback( - |ctx: ThreadsafeCallContext| { + |ctx: ThreadsafeCallContext>| { #[allow(clippy::unit_arg)] Ok(ctx.value) }, diff --git a/src/server/listen_tcp.rs b/src/server/listen_tcp.rs index a2a5a72..f6ed489 100644 --- a/src/server/listen_tcp.rs +++ b/src/server/listen_tcp.rs @@ -3,12 +3,15 @@ use std::{ net::{IpAddr, SocketAddr}, }; -use napi::{bindgen_prelude::*, threadsafe_function::ThreadsafeCallContext}; +use napi::{ + bindgen_prelude::*, + threadsafe_function::{ThreadsafeCallContext, ThreadsafeFunctionCallMode}, +}; use napi_derive::napi; use socket2::{Domain, Protocol, Socket, Type}; use tokio::net::TcpListener; -use super::Server; +use super::{ListenCallbackFn, Server}; #[napi(object)] pub struct TcpServerListenOptions { @@ -132,12 +135,27 @@ impl Server { pub fn listen_tcp( &self, options: TcpServerListenOptions, - callback: Function, + callback: ListenCallbackFn, ) -> Result<()> { let options: LibTcpServerListenOptions = options.into(); + let ts_callback = callback.build_threadsafe_function().build_callback( + |ctx: ThreadsafeCallContext>| { + #[allow(clippy::unit_arg)] + Ok(ctx.value) + }, + )?; + + let ip_addr = match host_to_ip_addr(&options.host) { + Ok(ip_addr) => ip_addr, + Err(e) => { + ts_callback.call(Some(e), ThreadsafeFunctionCallMode::NonBlocking); + return Ok(()); + } + }; + let create_tcp_listener_params = CreateTcpListenerParams { - addr: SocketAddr::from((host_to_ip_addr(&options.host)?, options.port)), + addr: SocketAddr::from((ip_addr, options.port)), backlog: options.backlog, socket: CreateTcpSocketParams { ipv6_only: options.ipv6_only, @@ -145,15 +163,19 @@ impl Server { reuse_port: options.reuse_port, }, }; - let tcp_listener = create_tcp_listener(create_tcp_listener_params)?; + + let tcp_listener = match create_tcp_listener(create_tcp_listener_params) { + Ok(tcp_listener) => tcp_listener, + Err(e) => { + ts_callback.call(Some(e), ThreadsafeFunctionCallMode::NonBlocking); + return Ok(()); + } + }; + let create_tcp_listener = move || future::ready(Ok(tcp_listener)); - let ts_callback = callback.build_threadsafe_function().build_callback( - |ctx: ThreadsafeCallContext| { - #[allow(clippy::unit_arg)] - Ok(ctx.value) - }, - )?; + self._listen_tcp(create_tcp_listener, ts_callback); + Ok(()) } } diff --git a/src/server/mod.rs b/src/server/mod.rs index 5aa0c21..21e5ada 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -19,6 +19,8 @@ use napi::threadsafe_function::ThreadsafeFunction; use napi_derive::napi; use rustls_acme::AcmeConfig; use rustls_acme::caches::DirCache; +use std::net::ToSocketAddrs; +use std::os::unix::net::SocketAddr; use std::sync::Arc; use tokio::net::TcpListener; use tokio_stream::wrappers::TcpListenerStream; @@ -27,6 +29,7 @@ use crate::request::Request; use crate::response::Response; use create_handler_task::create_handler_task; use handle_http_request::handle_http_request; +use listen_tcp::TcpServerListenOptions; #[cfg(unix)] use systemd_notify::systemd_notify; @@ -48,7 +51,11 @@ type ThreadsafeMiddlewareFn = ThreadsafeFunction< 0, >; -type ThreadsafeCallbackFn = ThreadsafeFunction; +type ThreadsafeCallbackFn = + ThreadsafeFunction, (), Option, Status, false, false, 0>; + +#[napi] +pub type ListenCallbackFn<'a> = Function<'a, Option, ()>; #[derive(Clone)] pub struct MiddlewareMeta { diff --git a/yarn.lock b/yarn.lock index d248a57..195a147 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1587,7 +1587,10 @@ __metadata: "@oxc-node/core": "npm:^0.0.35" "@taplo/cli": "npm:^0.7.0" "@tybys/wasm-util": "npm:^0.10.0" + "@types/express": "npm:^5.0.6" + "@types/express-serve-static-core": "npm:^5.1.1" "@types/node": "npm:^25.0.3" + "@types/qs": "npm:^6.15.0" ava: "npm:^7.0.0" axios: "npm:^1.13.2" chalk: "npm:^5.6.2" @@ -1628,6 +1631,25 @@ __metadata: languageName: node linkType: hard +"@types/body-parser@npm:*": + version: 1.19.6 + resolution: "@types/body-parser@npm:1.19.6" + dependencies: + "@types/connect": "npm:*" + "@types/node": "npm:*" + checksum: 10c0/542da05c924dce58ee23f50a8b981fee36921850c82222e384931fda3e106f750f7880c47be665217d72dbe445129049db6eb1f44e7a06b09d62af8f3cca8ea7 + languageName: node + linkType: hard + +"@types/connect@npm:*": + version: 3.4.38 + resolution: "@types/connect@npm:3.4.38" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/2e1cdba2c410f25649e77856505cd60223250fa12dff7a503e492208dbfdd25f62859918f28aba95315251fd1f5e1ffbfca1e25e73037189ab85dd3f8d0a148c + languageName: node + linkType: hard + "@types/estree@npm:^1.0.0": version: 1.0.8 resolution: "@types/estree@npm:1.0.8" @@ -1635,6 +1657,45 @@ __metadata: languageName: node linkType: hard +"@types/express-serve-static-core@npm:^5.0.0, @types/express-serve-static-core@npm:^5.1.1": + version: 5.1.1 + resolution: "@types/express-serve-static-core@npm:5.1.1" + dependencies: + "@types/node": "npm:*" + "@types/qs": "npm:*" + "@types/range-parser": "npm:*" + "@types/send": "npm:*" + checksum: 10c0/ee88216e114368ef06bcafeceb74a7e8671b90900fb0ab1d49ff41542c3a344231ef0d922bf63daa79f0585f3eebe2ce5ec7f83facc581eff8bcdb136a225ef3 + languageName: node + linkType: hard + +"@types/express@npm:^5.0.6": + version: 5.0.6 + resolution: "@types/express@npm:5.0.6" + dependencies: + "@types/body-parser": "npm:*" + "@types/express-serve-static-core": "npm:^5.0.0" + "@types/serve-static": "npm:^2" + checksum: 10c0/f1071e3389a955d4f9a38aae38634121c7cd9b3171ba4201ec9b56bd534aba07866839d278adc0dda05b942b05a901a02fd174201c3b1f70ce22b10b6c68f24b + languageName: node + linkType: hard + +"@types/http-errors@npm:*": + version: 2.0.5 + resolution: "@types/http-errors@npm:2.0.5" + checksum: 10c0/00f8140fbc504f47356512bd88e1910c2f07e04233d99c88c854b3600ce0523c8cd0ba7d1897667243282eb44c59abb9245959e2428b9de004f93937f52f7c15 + languageName: node + linkType: hard + +"@types/node@npm:*": + version: 25.6.0 + resolution: "@types/node@npm:25.6.0" + dependencies: + undici-types: "npm:~7.19.0" + checksum: 10c0/d2d2015630ff098a201407f55f5077a20270ae4f465c739b40865cd9933b91b9c5d2b85568eadaf3db0801b91e267333ca7eb39f007428b173d1cdab4b339ac5 + languageName: node + linkType: hard + "@types/node@npm:^25.0.3": version: 25.5.0 resolution: "@types/node@npm:25.5.0" @@ -1644,6 +1705,39 @@ __metadata: languageName: node linkType: hard +"@types/qs@npm:*, @types/qs@npm:^6.15.0": + version: 6.15.0 + resolution: "@types/qs@npm:6.15.0" + checksum: 10c0/1b104cac50e655fc41d7fc1de2c2aba2908c4cf833a555b6808fb4c96752662b439238f2392a15d2590a7a6ca75dbd40e42d9378ac2be0d548ee484954363688 + languageName: node + linkType: hard + +"@types/range-parser@npm:*": + version: 1.2.7 + resolution: "@types/range-parser@npm:1.2.7" + checksum: 10c0/361bb3e964ec5133fa40644a0b942279ed5df1949f21321d77de79f48b728d39253e5ce0408c9c17e4e0fd95ca7899da36841686393b9f7a1e209916e9381a3c + languageName: node + linkType: hard + +"@types/send@npm:*": + version: 1.2.1 + resolution: "@types/send@npm:1.2.1" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/7673747f8c2d8e67f3b1b3b57e9d4d681801a4f7b526ecf09987bb9a84a61cf94aa411c736183884dc762c1c402a61681eb1ef200d8d45d7e5ec0ab67ea5f6c1 + languageName: node + linkType: hard + +"@types/serve-static@npm:^2": + version: 2.2.0 + resolution: "@types/serve-static@npm:2.2.0" + dependencies: + "@types/http-errors": "npm:*" + "@types/node": "npm:*" + checksum: 10c0/a3c6126bdbf9685e6c7dc03ad34639666eff32754e912adeed9643bf3dd3aa0ff043002a7f69039306e310d233eb8e160c59308f95b0a619f32366bbc48ee094 + languageName: node + linkType: hard + "@vercel/nft@npm:^1.3.2": version: 1.5.0 resolution: "@vercel/nft@npm:1.5.0" @@ -3892,6 +3986,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~7.19.0": + version: 7.19.2 + resolution: "undici-types@npm:7.19.2" + checksum: 10c0/7159f10546f9f6c47d36776bb1bbf8671e87c1e587a6fee84ae1f111ae8de4f914efa8ca0dfcd224f4f4a9dfc3f6028f627ccb5ddaccf82d7fd54671b89fac3e + languageName: node + linkType: hard + "unicorn-magic@npm:^0.4.0": version: 0.4.0 resolution: "unicorn-magic@npm:0.4.0" From e3ee1cfcea518e6db31b82b48192304d6b72efa8 Mon Sep 17 00:00:00 2001 From: Samuel Mwangi Date: Fri, 12 Jun 2026 23:36:02 +0300 Subject: [PATCH 15/15] Document possible signatures of the Application.get method to guide with implementation. --- src-ts/hyper_js.ts | 91 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/src-ts/hyper_js.ts b/src-ts/hyper_js.ts index bdf6228..ce12b9a 100644 --- a/src-ts/hyper_js.ts +++ b/src-ts/hyper_js.ts @@ -4,8 +4,13 @@ import { IpcServerListenOptions, ListenCallbackFn, } from '../hyperjs-core/index' +import type { IRouter, IRouterMatcher } from 'express-serve-static-core' -class HyperJs { +type IRouterPartial = { + get: IRouterMatcher +} + +class HyperJs implements IRouterPartial { /** * Listen for connections. */ @@ -63,6 +68,90 @@ class HyperJs { return rsServer } + + // get + // 1. (string | regex, ...) + // ... = one or more + // - (req, res, next): unknown | RequestHandler + // | Request.params has properties extracted from path + // + // 2. (string | regex, ...) + // ... = one or more + // - (req, res, next): unknown | RequestHandler + // | Request.params has properties extracted from path + // + // - (err, req, res, next) | ErrorRequestHandler + // | Request.params has properties extracted from path + // + // - (RequestHandler | ErrorRequestHandler)[] | An Array containing a mixture of RequestHandlers and ErrorRequestHandlers + // | Request.params has properties extracted from path + // + // 3. (string | regex | (string | regex)[], ...) + // - (req, res, next): unknown | RequestHandler + // | Request.params = { number: string; string: string; string: string[] } + // + // 4. (string | regex | (string | regex)[], ...) + // ... = one or more + // - (req, res, next): unknown | RequestHandler + // | Request.params = { number: string; string: string; string: string[] } + // + // - (err, req, res, next) | ErrorRequestHandler + // | Request.params = { number: string; string: string; string: string[] } + // + // - (RequestHandler | ErrorRequestHandler)[] | An Array containing a mixture of RequestHandlers and ErrorRequestHandlers + // | Request.params = { number: string; string: string; string: string[] } + // + // 5. (string | regex | (string | regex)[], Application) + get(...args: any[]): IRouterPartial { + // Here are three tricky things I need to be mindful of: + // 1. Flattening Arrays: + // In cases 2, 4, and 5, users can pass nested arrays of middleware + // handlers. I need to flatten them into a single-level list of + // functions. + // + // 2. Identifying Error Handlers: + // Normal handlers take 3 arguments (req, res, next). Error handlers + // take 4 arguments (err, req, res, next). JavaScript lets me check + // this at runtime using fn.length. + // + // 3. Handling Sub-Applications: + // Case 5 passes a sub-app instead of callback functions. + const path = args[0] + const rawHandlers = args.slice(1) + + // 1. Flatten any nested arrays from signatures 2 and 4 + const flattenedHandlers = rawHandlers.flat(Infinity) + + // 2. Check if signature 5 was used (mounting a sub-application) + if (flattenedHandlers.length === 1 && typeof flattenedHandlers[0] === 'object') { + const subApp = flattenedHandlers[0] + // Pass the sub-app mounting logic to your Rust backend + // this.rsServer.mountSubApp(path, subApp); + return this + } + + // 3. Separate standard handlers from error handlers using fn.length + const normalHandlers = [] + const errorHandlers = [] + + for (const handler of flattenedHandlers) { + if (typeof handler === 'function') { + if (handler.length === 4) { + // (err, req, res, next) -> ErrorRequestHandler + errorHandlers.push(handler) + } else { + // (req, res, next) -> RequestHandler + normalHandlers.push(handler) + } + } + } + + // 4. Pass the cleaned data to your Rust Napi bindings + // Example: + // this.rsServer.registerGetRoute(path, normalHandlers, errorHandlers); + + return this + } } export { HyperJs }