@nifrajs/node 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +62 -0
- package/dist/index.d.ts +110 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +696 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
- package/src/index.ts +954 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nifra Authors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# @nifrajs/node
|
|
2
|
+
|
|
3
|
+
Run a [nifra](../../README.md) app on Node's `http` server — nifra's ergonomics, deployed
|
|
4
|
+
on Node instead of Bun.
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
bun add @nifrajs/node # or: npm add @nifrajs/node
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { server } from "@nifrajs/core"
|
|
12
|
+
import { serve } from "@nifrajs/node"
|
|
13
|
+
|
|
14
|
+
const app = server().get("/users/:id", (c) => ({ id: c.params.id }))
|
|
15
|
+
|
|
16
|
+
const node = await serve(app, { port: 3000 })
|
|
17
|
+
// node.port → the bound port (resolved; useful with `port: 0`)
|
|
18
|
+
// await node.stop() → drain in-flight, then force-close (graceful shutdown)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`serve(app, { port })` bridges Node's stream-based `(req, res)` to/from the Web
|
|
22
|
+
`Request`/`Response` that `app.fetch` speaks — that's the only Bun-specific seam, since
|
|
23
|
+
nifra's lifecycle is already Web-standard. It also gives you a graceful `stop()`, the
|
|
24
|
+
Node equivalent of Bun's `app.listen()`.
|
|
25
|
+
|
|
26
|
+
## Public URL protocol
|
|
27
|
+
|
|
28
|
+
The adapter creates a plain Node `http` server, so `Request.url` uses `http://` by
|
|
29
|
+
default. If TLS terminates in front of Node, set the public protocol explicitly:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
await serve(app, { port: 3000, protocol: "https" })
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Forwarded protocol headers are not trusted automatically; pass a function only when your
|
|
36
|
+
own trusted infrastructure has validated the request:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
await serve(app, {
|
|
40
|
+
port: 3000,
|
|
41
|
+
protocol: (req) => req.headers["x-forwarded-proto"] === "https" ? "https" : "http",
|
|
42
|
+
})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Graceful shutdown on signals
|
|
46
|
+
|
|
47
|
+
Opt in to SIGTERM/SIGINT handling so a `docker stop` / Ctrl-C drains in-flight requests
|
|
48
|
+
before exit (mirrors Bun's `listen({ gracefulSignals })` — off by default, since owning
|
|
49
|
+
process signals should be a deliberate choice):
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
await serve(app, { port: 3000, signals: true })
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## What you get for free
|
|
56
|
+
|
|
57
|
+
The request timeout (`server({ requestTimeoutMs })` → `503 request_timeout`) and body-size
|
|
58
|
+
cap live **inside `app.fetch`**, not Bun's `listen()` — so they apply through this adapter
|
|
59
|
+
with no extra wiring. Slow-client / slow-loris protection is Node's built-in
|
|
60
|
+
`requestTimeout` (300s) and `headersTimeout` (60s) defaults.
|
|
61
|
+
|
|
62
|
+
Works with any `{ fetch(req): Promise<Response> }` handler, not just nifra. ESM-only. MIT.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { type IncomingMessage } from "node:http";
|
|
2
|
+
/** Anything exposing a Web `fetch` handler — a nifra `app`, for instance. */
|
|
3
|
+
export interface FetchHandler {
|
|
4
|
+
fetch(request: Request): Response | Promise<Response>;
|
|
5
|
+
/** Nifra apps also expose this WS-upgrade seam; present → this adapter serves `app.ws()` routes via
|
|
6
|
+
* the optional `ws` package (lazy-imported on the first upgrade). Absent (a plain `{ fetch }`
|
|
7
|
+
* handler) → HTTP only, and an upgrade request gets a 404. */
|
|
8
|
+
resolveWebSocketUpgrade?(request: Request): WsUpgradeOutcome | Promise<WsUpgradeOutcome>;
|
|
9
|
+
}
|
|
10
|
+
/** A received frame, normalized: text → `string`, binary → `Uint8Array`. */
|
|
11
|
+
type NifraWsData = string | Uint8Array;
|
|
12
|
+
/** The portable socket a nifra WS handler sees (mirror of core's `NifraWebSocket`). */
|
|
13
|
+
interface NifraWs {
|
|
14
|
+
send(data: string | ArrayBufferView | ArrayBuffer): void;
|
|
15
|
+
close(code?: number, reason?: string): void;
|
|
16
|
+
readonly readyState: number;
|
|
17
|
+
subscribe(topic: string): void;
|
|
18
|
+
unsubscribe(topic: string): void;
|
|
19
|
+
data: unknown;
|
|
20
|
+
readonly raw: unknown;
|
|
21
|
+
}
|
|
22
|
+
/** Mirror of core's `TopicRegistry` surface — the app's pub/sub the adapter wires `ws.subscribe` to. */
|
|
23
|
+
interface WsPubSub {
|
|
24
|
+
subscribe(topic: string, ws: NifraWs): void;
|
|
25
|
+
unsubscribe(topic: string, ws: NifraWs): void;
|
|
26
|
+
unsubscribeAll(ws: NifraWs): void;
|
|
27
|
+
}
|
|
28
|
+
/** A nifra WS route's lifecycle (mirror of core's `WebSocketHandler` — the post-upgrade callbacks). */
|
|
29
|
+
interface NifraWsHandler {
|
|
30
|
+
open?(ws: NifraWs): void | Promise<void>;
|
|
31
|
+
message?(ws: NifraWs, data: NifraWsData): void | Promise<void>;
|
|
32
|
+
close?(ws: NifraWs, code: number, reason: string): void | Promise<void>;
|
|
33
|
+
error?(ws: NifraWs, error: unknown): void | Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
/** Mirror of core's `WebSocketUpgradeOutcome` — what `resolveWebSocketUpgrade` returns. */
|
|
36
|
+
type WsUpgradeOutcome = {
|
|
37
|
+
readonly kind: "pass";
|
|
38
|
+
} | {
|
|
39
|
+
readonly kind: "reject";
|
|
40
|
+
readonly response: Response;
|
|
41
|
+
} | {
|
|
42
|
+
readonly kind: "upgrade";
|
|
43
|
+
readonly handler: NifraWsHandler;
|
|
44
|
+
readonly data: unknown;
|
|
45
|
+
readonly pubsub: WsPubSub;
|
|
46
|
+
};
|
|
47
|
+
export type RequestProtocol = "http" | "https";
|
|
48
|
+
export type RequestProtocolOption = RequestProtocol | ((request: IncomingMessage) => RequestProtocol);
|
|
49
|
+
/**
|
|
50
|
+
* Serve static files from a directory (e.g. the client build) under a URL prefix — so a self-hosted
|
|
51
|
+
* Node deploy doesn't need a CDN or a hand-rolled `/assets/*` handler. (On Cloudflare/Vercel the
|
|
52
|
+
* platform serves assets; this is for `node server.js`.)
|
|
53
|
+
*/
|
|
54
|
+
export interface ServeStaticOptions {
|
|
55
|
+
/** Directory to read files from — an absolute path or a `file://` URL (`new URL("./assets/", import.meta.url)`). */
|
|
56
|
+
readonly dir: string | URL;
|
|
57
|
+
/** URL prefix these files are served under. Default `"/assets"`. Use `"/"` to serve the whole dir. */
|
|
58
|
+
readonly prefix?: string;
|
|
59
|
+
/** Emit `cache-control: public, max-age=31536000, immutable` — correct for content-hashed files. Default `true`. */
|
|
60
|
+
readonly immutable?: boolean;
|
|
61
|
+
/** Extra headers merged onto every served file. */
|
|
62
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
63
|
+
}
|
|
64
|
+
export interface ServeOptions {
|
|
65
|
+
readonly port: number;
|
|
66
|
+
readonly hostname?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Protocol used when the adapter constructs `Request.url`.
|
|
69
|
+
*
|
|
70
|
+
* `@nifrajs/node` creates a plain Node `http` server, so the safe default is `"http"`. Deployments behind
|
|
71
|
+
* TLS termination can set `"https"` (or a trusted infra-aware function) so app code that reads
|
|
72
|
+
* `request.url` sees the public scheme. Forwarded headers are not trusted implicitly.
|
|
73
|
+
*/
|
|
74
|
+
readonly protocol?: RequestProtocolOption;
|
|
75
|
+
/**
|
|
76
|
+
* Install SIGTERM/SIGINT handlers that call `stop()` for a graceful drain on
|
|
77
|
+
* `docker stop` / Ctrl-C. Off by default — taking over process signals is opt-in,
|
|
78
|
+
* mirroring nifra's Bun `listen({ gracefulSignals })`.
|
|
79
|
+
*
|
|
80
|
+
* The app-level request timeout (`server({ requestTimeoutMs })` → 503) and body cap
|
|
81
|
+
* are *not* set here — they live inside `app.fetch`, so they already apply through
|
|
82
|
+
* this adapter. Slow-client protection is Node's built-in `requestTimeout` (300s) /
|
|
83
|
+
* `headersTimeout` (60s) defaults.
|
|
84
|
+
*/
|
|
85
|
+
readonly signals?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Serve static files from disk for matching GET/HEAD requests *before* the app runs — non-matching
|
|
88
|
+
* requests fall through to `app.fetch` with the node-direct fast path intact (no perf regression on
|
|
89
|
+
* SSR/API routes). Replaces the hand-rolled `/assets/*` reader in self-hosted entries.
|
|
90
|
+
*/
|
|
91
|
+
readonly static?: ServeStaticOptions;
|
|
92
|
+
}
|
|
93
|
+
export interface NodeServer {
|
|
94
|
+
/** The bound port (resolved when `port: 0` is requested). */
|
|
95
|
+
readonly port: number;
|
|
96
|
+
/**
|
|
97
|
+
* Stop accepting connections, let in-flight requests drain (up to `drainMs`), then
|
|
98
|
+
* force-close stragglers + idle keep-alives. Mirrors nifra's Bun `stop()`.
|
|
99
|
+
*/
|
|
100
|
+
stop(options?: {
|
|
101
|
+
drainMs?: number;
|
|
102
|
+
}): Promise<void>;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Serve a Web-`fetch` app on a Node `http` server. Resolves once bound — Node binds
|
|
106
|
+
* the port asynchronously, so awaiting gives you the real port (matters for `port: 0`).
|
|
107
|
+
*/
|
|
108
|
+
export declare function serve(app: FetchHandler, options: ServeOptions): Promise<NodeServer>;
|
|
109
|
+
export {};
|
|
110
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|