@krantho/server-node 0.0.0-stage → 1.0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Descoped
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.
@@ -0,0 +1,20 @@
1
+ import type { KranthoHandler, KranthoHandlerOptions } from '@krantho/server';
2
+ export type { ContentLoader, PageRequestOptions, PageRequestResult, StudioRequest, StudioServer, } from '@krantho/server';
3
+ export { createContentLoader, createStudioServer, runPageRequest, verifyPreviewRequest, } from '@krantho/server';
4
+ export { toWebRequest } from './node-request.js';
5
+ export { writeResponseHeaders, writeWebResponse } from './node-response.js';
6
+ export type { CachedPage, CachedPageHeaders, PageHeaders, PageRequestInfo, RenderedPage, } from './page-cache.js';
7
+ export { renderCachedPage } from './page-cache.js';
8
+ export type { KranthoHandlerOptions };
9
+ /**
10
+ * Create a raw handler for use with any Node.js HTTP server.
11
+ *
12
+ * Returns an object with `handle(request: Request): Promise<Response>`.
13
+ * The handler is initialized lazily on first request and reused (singleton).
14
+ *
15
+ * Use this when integrating with Express, Fastify, or a custom Node.js server.
16
+ * For Vite dev middleware, use `@krantho/server-node/vite` instead.
17
+ * For a standalone production server, use `@krantho/server-node/standalone`.
18
+ */
19
+ export declare function createHandler(options: KranthoHandlerOptions): KranthoHandler;
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AAG5E,YAAY,EACV,aAAa,EACb,kBAAkB,EAClB,iBAAiB,EACjB,aAAa,EACb,YAAY,GACb,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,oBAAoB,GACrB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAC3E,YAAY,EACV,UAAU,EACV,iBAAiB,EACjB,WAAW,EACX,eAAe,EACf,YAAY,GACb,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAClD,YAAY,EAAE,qBAAqB,EAAE,CAAA;AAErC;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,qBAAqB,GAAG,cAAc,CAqB5E"}
package/dist/index.js ADDED
@@ -0,0 +1,36 @@
1
+ import { createKranthoHandler } from '@krantho/server';
2
+ export { createContentLoader, createStudioServer, runPageRequest, verifyPreviewRequest, } from '@krantho/server';
3
+ export { toWebRequest } from './node-request.js';
4
+ export { writeResponseHeaders, writeWebResponse } from './node-response.js';
5
+ export { renderCachedPage } from './page-cache.js';
6
+ /**
7
+ * Create a raw handler for use with any Node.js HTTP server.
8
+ *
9
+ * Returns an object with `handle(request: Request): Promise<Response>`.
10
+ * The handler is initialized lazily on first request and reused (singleton).
11
+ *
12
+ * Use this when integrating with Express, Fastify, or a custom Node.js server.
13
+ * For Vite dev middleware, use `@krantho/server-node/vite` instead.
14
+ * For a standalone production server, use `@krantho/server-node/standalone`.
15
+ */
16
+ export function createHandler(options) {
17
+ let handlerPromise;
18
+ function getHandler() {
19
+ if (!handlerPromise) {
20
+ handlerPromise = createKranthoHandler({ ...options, framework: 'node' });
21
+ }
22
+ return handlerPromise;
23
+ }
24
+ return {
25
+ async handle(request) {
26
+ const handler = await getHandler();
27
+ return handler.handle(request);
28
+ },
29
+ close() {
30
+ if (handlerPromise) {
31
+ handlerPromise.then((h) => h.close()).catch(() => { });
32
+ }
33
+ },
34
+ };
35
+ }
36
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAStD,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,oBAAoB,GACrB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAQ3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAGlD;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAAC,OAA8B;IAC1D,IAAI,cAAmD,CAAA;IAEvD,SAAS,UAAU;QACjB,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,cAAc,GAAG,oBAAoB,CAAC,EAAE,GAAG,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAA;QAC1E,CAAC;QACD,OAAO,cAAc,CAAA;IACvB,CAAC;IAED,OAAO;QACL,KAAK,CAAC,MAAM,CAAC,OAAgB;YAC3B,MAAM,OAAO,GAAG,MAAM,UAAU,EAAE,CAAA;YAClC,OAAO,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QAChC,CAAC;QACD,KAAK;YACH,IAAI,cAAc,EAAE,CAAC;gBACnB,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;YACvD,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { IncomingMessage } from 'node:http';
2
+ /** Convert a Node.js IncomingMessage to a Web Request */
3
+ export declare function toWebRequest(req: IncomingMessage): Promise<Request>;
4
+ //# sourceMappingURL=node-request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-request.d.ts","sourceRoot":"","sources":["../src/node-request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAchD,yDAAyD;AACzD,wBAAsB,YAAY,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAsBzE"}
@@ -0,0 +1,31 @@
1
+ const METHODS_WITH_BODY = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
2
+ /** Read the full request body from a Node.js IncomingMessage, byte for byte: an upload is not text */
3
+ function readBody(req) {
4
+ return new Promise((resolve, reject) => {
5
+ const chunks = [];
6
+ req.on('data', (chunk) => chunks.push(chunk));
7
+ req.on('end', () => resolve(new Uint8Array(Buffer.concat(chunks))));
8
+ req.on('error', reject);
9
+ });
10
+ }
11
+ /** Convert a Node.js IncomingMessage to a Web Request */
12
+ export async function toWebRequest(req) {
13
+ const protocol = req.headers['x-forwarded-proto'] ?? 'http';
14
+ const host = req.headers.host ?? 'localhost';
15
+ const fullUrl = `${protocol}://${host}${req.url ?? '/'}`;
16
+ const headers = new Headers();
17
+ for (const [key, value] of Object.entries(req.headers)) {
18
+ if (typeof value === 'string') {
19
+ headers.set(key, value);
20
+ }
21
+ }
22
+ const init = { method: req.method ?? 'GET', headers };
23
+ if (req.method && METHODS_WITH_BODY.has(req.method)) {
24
+ const body = await readBody(req);
25
+ if (body.byteLength > 0) {
26
+ init.body = body;
27
+ }
28
+ }
29
+ return new Request(fullUrl, init);
30
+ }
31
+ //# sourceMappingURL=node-request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-request.js","sourceRoot":"","sources":["../src/node-request.ts"],"names":[],"mappings":"AAEA,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAA;AAErE,sGAAsG;AACtG,SAAS,QAAQ,CAAC,GAAoB;IACpC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,MAAM,GAAa,EAAE,CAAA;QAC3B,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;QACrD,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;QACnE,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;IACzB,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,yDAAyD;AACzD,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAoB;IACrD,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,mBAAmB,CAAC,IAAI,MAAM,CAAA;IAC3D,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,WAAW,CAAA;IAC5C,MAAM,OAAO,GAAG,GAAG,QAAQ,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,CAAA;IAExD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAA;IAC7B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QACvD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;QACzB,CAAC;IACH,CAAC;IAED,MAAM,IAAI,GAAgB,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,KAAK,EAAE,OAAO,EAAE,CAAA;IAElE,IAAI,GAAG,CAAC,MAAM,IAAI,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAA;QAChC,IAAI,IAAI,CAAC,UAAU,GAAG,CAAC,EAAE,CAAC;YACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAClB,CAAC;IACH,CAAC;IAED,OAAO,IAAI,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;AACnC,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { ServerResponse } from 'node:http';
2
+ /**
3
+ * Copy a Web Response's headers onto a Node ServerResponse.
4
+ *
5
+ * Set-Cookie is emitted as one header line per cookie: iterating a Web `Headers`
6
+ * object folds same-name headers (including Set-Cookie) into a single comma-joined
7
+ * value, which RFC 6265 forbids. `getSetCookie()` recovers the distinct values, and
8
+ * `res.setHeader(name, string[])` writes one line per array element.
9
+ */
10
+ export declare function writeResponseHeaders(res: ServerResponse, response: Response): void;
11
+ /**
12
+ * Write a Web Response to a Node response: headers (one Set-Cookie line per
13
+ * cookie), then the body chunk by chunk as it arrives, honouring backpressure.
14
+ * When the client goes away first, the body stream is cancelled; when the body
15
+ * fails after the headers were sent, the response is destroyed.
16
+ */
17
+ export declare function writeWebResponse(res: ServerResponse, response: Response): Promise<void>;
18
+ //# sourceMappingURL=node-response.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-response.d.ts","sourceRoot":"","sources":["../src/node-response.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAE/C;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,cAAc,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CASlF;AAgBD;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CA4B7F"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Copy a Web Response's headers onto a Node ServerResponse.
3
+ *
4
+ * Set-Cookie is emitted as one header line per cookie: iterating a Web `Headers`
5
+ * object folds same-name headers (including Set-Cookie) into a single comma-joined
6
+ * value, which RFC 6265 forbids. `getSetCookie()` recovers the distinct values, and
7
+ * `res.setHeader(name, string[])` writes one line per array element.
8
+ */
9
+ export function writeResponseHeaders(res, response) {
10
+ for (const [key, value] of response.headers) {
11
+ if (key.toLowerCase() === 'set-cookie')
12
+ continue;
13
+ res.setHeader(key, value);
14
+ }
15
+ const cookies = response.headers.getSetCookie();
16
+ if (cookies.length > 0) {
17
+ res.setHeader('set-cookie', cookies);
18
+ }
19
+ }
20
+ /** Write one chunk, waiting for `drain` when the socket's buffer is full */
21
+ function writeChunk(res, chunk) {
22
+ if (res.write(chunk))
23
+ return Promise.resolve();
24
+ return new Promise((resolve) => {
25
+ const done = () => {
26
+ res.off('drain', done);
27
+ res.off('close', done);
28
+ resolve();
29
+ };
30
+ res.once('drain', done);
31
+ res.once('close', done);
32
+ });
33
+ }
34
+ /**
35
+ * Write a Web Response to a Node response: headers (one Set-Cookie line per
36
+ * cookie), then the body chunk by chunk as it arrives, honouring backpressure.
37
+ * When the client goes away first, the body stream is cancelled; when the body
38
+ * fails after the headers were sent, the response is destroyed.
39
+ */
40
+ export async function writeWebResponse(res, response) {
41
+ res.statusCode = response.status;
42
+ writeResponseHeaders(res, response);
43
+ res.flushHeaders();
44
+ if (!response.body) {
45
+ res.end();
46
+ return;
47
+ }
48
+ const reader = response.body.getReader();
49
+ const onClose = () => {
50
+ if (!res.writableFinished)
51
+ reader.cancel().catch(() => { });
52
+ };
53
+ res.once('close', onClose);
54
+ try {
55
+ for (;;) {
56
+ const { done, value } = await reader.read();
57
+ if (done)
58
+ break;
59
+ if (res.destroyed)
60
+ break;
61
+ await writeChunk(res, value);
62
+ }
63
+ if (!res.destroyed)
64
+ res.end();
65
+ }
66
+ catch (err) {
67
+ res.destroy(err instanceof Error ? err : new Error(String(err)));
68
+ }
69
+ finally {
70
+ res.off('close', onClose);
71
+ reader.releaseLock();
72
+ }
73
+ }
74
+ //# sourceMappingURL=node-response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-response.js","sourceRoot":"","sources":["../src/node-response.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAmB,EAAE,QAAkB;IAC1E,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;QAC5C,IAAI,GAAG,CAAC,WAAW,EAAE,KAAK,YAAY;YAAE,SAAQ;QAChD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;IAC3B,CAAC;IACD,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,YAAY,EAAE,CAAA;IAC/C,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,GAAG,CAAC,SAAS,CAAC,YAAY,EAAE,OAAO,CAAC,CAAA;IACtC,CAAC;AACH,CAAC;AAED,4EAA4E;AAC5E,SAAS,UAAU,CAAC,GAAmB,EAAE,KAAiB;IACxD,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAA;IAC9C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,IAAI,GAAG,GAAS,EAAE;YACtB,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;YACtB,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;YACtB,OAAO,EAAE,CAAA;QACX,CAAC,CAAA;QACD,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;QACvB,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;IACzB,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,GAAmB,EAAE,QAAkB;IAC5E,GAAG,CAAC,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAA;IAChC,oBAAoB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;IACnC,GAAG,CAAC,YAAY,EAAE,CAAA;IAClB,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnB,GAAG,CAAC,GAAG,EAAE,CAAA;QACT,OAAM;IACR,CAAC;IAED,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,CAAA;IACxC,MAAM,OAAO,GAAG,GAAS,EAAE;QACzB,IAAI,CAAC,GAAG,CAAC,gBAAgB;YAAE,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;IAC5D,CAAC,CAAA;IACD,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;IAC1B,IAAI,CAAC;QACH,SAAS,CAAC;YACR,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAA;YAC3C,IAAI,IAAI;gBAAE,MAAK;YACf,IAAI,GAAG,CAAC,SAAS;gBAAE,MAAK;YACxB,MAAM,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;QAC9B,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,SAAS;YAAE,GAAG,CAAC,GAAG,EAAE,CAAA;IAC/B,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,GAAG,CAAC,OAAO,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;IAClE,CAAC;YAAS,CAAC;QACT,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QACzB,MAAM,CAAC,WAAW,EAAE,CAAA;IACtB,CAAC;AACH,CAAC"}
@@ -0,0 +1,58 @@
1
+ import type { KranthoHandlerOptions } from '@krantho/server';
2
+ /** A page request as Express (or any Node server) sees it */
3
+ export interface PageRequestInfo {
4
+ /** The full request path with its query string (Express: `req.originalUrl`), or an absolute URL */
5
+ url: string;
6
+ method: string;
7
+ }
8
+ /**
9
+ * Response headers in Node's shape: what Express's `res.getHeaders()` returns,
10
+ * and what `res.setHeader(name, value)` takes (an `undefined` value is skipped).
11
+ */
12
+ export type PageHeaders = Record<string, string | number | readonly string[] | undefined>;
13
+ /** The headers to send with a page, each ready for `res.setHeader(name, value)` */
14
+ export type CachedPageHeaders = Record<string, string | number | readonly string[]>;
15
+ /** What a server-side render produced */
16
+ export interface RenderedPage {
17
+ status: number;
18
+ html: string;
19
+ /**
20
+ * The page's own response headers — a `Set-Cookie` among them, or a
21
+ * `Cache-Control` saying `private`, `no-store` or `no-cache`, keeps the page
22
+ * off the CDN
23
+ */
24
+ headers?: PageHeaders | undefined;
25
+ }
26
+ /** A rendered page and every header to send with it */
27
+ export interface CachedPage {
28
+ status: number;
29
+ html: string;
30
+ headers: CachedPageHeaders;
31
+ }
32
+ /**
33
+ * Render one page under the page cache rules.
34
+ *
35
+ * A `GET` or `HEAD` outside `/api` and `/studio` renders inside a page request
36
+ * scope, so the content loader records its tags. A verified signed preview
37
+ * link reads drafts and answers `private, no-store`, `noindex`, with no
38
+ * referrer. Under `cache.pages: 'cdn'` a published `200` or `404` whose
39
+ * headers set no cookie is cached at the CDN, tagged with every read that
40
+ * rendered it — unless its own `Cache-Control` says `private`, `no-store` or
41
+ * `no-cache`, which is then left as it is.
42
+ *
43
+ * The returned `headers` are the page's own merged with the cache headers,
44
+ * the cache headers winning on a name both set; a `Set-Cookie` is always
45
+ * passed through. Send every one:
46
+ *
47
+ * ```ts
48
+ * const page = await renderCachedPage(req, config, render)
49
+ * for (const [name, value] of Object.entries(page.headers)) res.setHeader(name, value)
50
+ * res.status(page.status).send(page.html)
51
+ * ```
52
+ *
53
+ * Start every content read inside `render`: a read made before it, or one that
54
+ * outlives it, records no tags. Never read `loader.preview` outside `render` —
55
+ * that puts a draft into a page the CDN caches.
56
+ */
57
+ export declare function renderCachedPage(req: PageRequestInfo, config: KranthoHandlerOptions['config'], render: () => Promise<RenderedPage>): Promise<CachedPage>;
58
+ //# sourceMappingURL=page-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-cache.d.ts","sourceRoot":"","sources":["../src/page-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AAG5D,6DAA6D;AAC7D,MAAM,WAAW,eAAe;IAC9B,mGAAmG;IACnG,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC,CAAA;AAEzF,mFAAmF;AACnF,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC,CAAA;AAEnF,yCAAyC;AACzC,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,OAAO,CAAC,EAAE,WAAW,GAAG,SAAS,CAAA;CAClC;AAED,uDAAuD;AACvD,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,iBAAiB,CAAA;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,eAAe,EACpB,MAAM,EAAE,qBAAqB,CAAC,QAAQ,CAAC,EACvC,MAAM,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,GAClC,OAAO,CAAC,UAAU,CAAC,CAUrB"}
@@ -0,0 +1,63 @@
1
+ import { servePageRequest } from '@krantho/server';
2
+ /**
3
+ * Render one page under the page cache rules.
4
+ *
5
+ * A `GET` or `HEAD` outside `/api` and `/studio` renders inside a page request
6
+ * scope, so the content loader records its tags. A verified signed preview
7
+ * link reads drafts and answers `private, no-store`, `noindex`, with no
8
+ * referrer. Under `cache.pages: 'cdn'` a published `200` or `404` whose
9
+ * headers set no cookie is cached at the CDN, tagged with every read that
10
+ * rendered it — unless its own `Cache-Control` says `private`, `no-store` or
11
+ * `no-cache`, which is then left as it is.
12
+ *
13
+ * The returned `headers` are the page's own merged with the cache headers,
14
+ * the cache headers winning on a name both set; a `Set-Cookie` is always
15
+ * passed through. Send every one:
16
+ *
17
+ * ```ts
18
+ * const page = await renderCachedPage(req, config, render)
19
+ * for (const [name, value] of Object.entries(page.headers)) res.setHeader(name, value)
20
+ * res.status(page.status).send(page.html)
21
+ * ```
22
+ *
23
+ * Start every content read inside `render`: a read made before it, or one that
24
+ * outlives it, records no tags. Never read `loader.preview` outside `render` —
25
+ * that puts a draft into a page the CDN caches.
26
+ */
27
+ export async function renderCachedPage(req, config, render) {
28
+ const { value, headers } = await servePageRequest(req, config, render, (page) => ({
29
+ status: page.status,
30
+ header: (name) => headerValue(page.headers ?? {}, name),
31
+ }));
32
+ return {
33
+ status: value.status,
34
+ html: value.html,
35
+ headers: mergeHeaders(value.headers ?? {}, headers),
36
+ };
37
+ }
38
+ /** A header by name, in any case, several values joined with `, ` — null when absent */
39
+ function headerValue(headers, name) {
40
+ const wanted = name.toLowerCase();
41
+ const values = [];
42
+ for (const [key, value] of Object.entries(headers)) {
43
+ if (value === undefined || key.toLowerCase() !== wanted)
44
+ continue;
45
+ if (typeof value === 'object')
46
+ values.push(...value);
47
+ else
48
+ values.push(String(value));
49
+ }
50
+ return values.length > 0 ? values.join(', ') : null;
51
+ }
52
+ /** The page's headers, less any the cache headers replace (by name, in any case), plus those */
53
+ function mergeHeaders(page, cache) {
54
+ const replaced = new Set(Object.keys(cache).map((name) => name.toLowerCase()));
55
+ const merged = {};
56
+ for (const [name, value] of Object.entries(page)) {
57
+ if (value === undefined || replaced.has(name.toLowerCase()))
58
+ continue;
59
+ merged[name] = value;
60
+ }
61
+ return { ...merged, ...cache };
62
+ }
63
+ //# sourceMappingURL=page-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-cache.js","sourceRoot":"","sources":["../src/page-cache.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAqClD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,GAAoB,EACpB,MAAuC,EACvC,MAAmC;IAEnC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,MAAM,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAChF,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,EAAE,IAAI,CAAC;KACxD,CAAC,CAAC,CAAA;IACH,OAAO;QACL,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,EAAE,OAAO,CAAC;KACpD,CAAA;AACH,CAAC;AAED,wFAAwF;AACxF,SAAS,WAAW,CAAC,OAAoB,EAAE,IAAY;IACrD,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,CAAA;IACjC,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACnD,IAAI,KAAK,KAAK,SAAS,IAAI,GAAG,CAAC,WAAW,EAAE,KAAK,MAAM;YAAE,SAAQ;QACjE,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAA;;YAC/C,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;IACjC,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AACrD,CAAC;AAED,gGAAgG;AAChG,SAAS,YAAY,CAAC,IAAiB,EAAE,KAA6B;IACpE,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAA;IAC9E,MAAM,MAAM,GAAsB,EAAE,CAAA;IACpC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACjD,IAAI,KAAK,KAAK,SAAS,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YAAE,SAAQ;QACrE,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAA;IACtB,CAAC;IACD,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,KAAK,EAAE,CAAA;AAChC,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { KranthoHandlerOptions } from '@krantho/server';
2
+ /** Options for the standalone production server */
3
+ export interface StandaloneOptions extends KranthoHandlerOptions {
4
+ /** Directory containing static files to serve (e.g., 'dist') */
5
+ staticDir?: string | undefined;
6
+ /** Port to listen on (default: 3000) */
7
+ port?: number | undefined;
8
+ }
9
+ /**
10
+ * Start a standalone HTTP server with API + optional static file serving.
11
+ *
12
+ * Requests to `/api/*` are routed to the krantho handler.
13
+ * All other requests serve static files from `staticDir` (if configured).
14
+ * Falls back to `index.html` for SPA client-side routing.
15
+ */
16
+ export declare function startServer(options: StandaloneOptions): Promise<void>;
17
+ //# sourceMappingURL=standalone.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standalone.d.ts","sourceRoot":"","sources":["../src/standalone.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AAK5D,mDAAmD;AACnD,MAAM,WAAW,iBAAkB,SAAQ,qBAAqB;IAC9D,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC9B,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC1B;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+C3E"}
@@ -0,0 +1,83 @@
1
+ import { readFile, stat } from 'node:fs/promises';
2
+ import { createServer } from 'node:http';
3
+ import { extname, join, resolve, sep } from 'node:path';
4
+ import { createKranthoHandler, MIME_TYPES } from '@krantho/server';
5
+ import { toWebRequest } from './node-request.js';
6
+ import { writeWebResponse } from './node-response.js';
7
+ /**
8
+ * Start a standalone HTTP server with API + optional static file serving.
9
+ *
10
+ * Requests to `/api/*` are routed to the krantho handler.
11
+ * All other requests serve static files from `staticDir` (if configured).
12
+ * Falls back to `index.html` for SPA client-side routing.
13
+ */
14
+ export async function startServer(options) {
15
+ const port = options.port ?? 3000;
16
+ const handler = await createKranthoHandler({ ...options, framework: 'node' });
17
+ const server = createServer(async (req, res) => {
18
+ const url = req.url ?? '/';
19
+ // API routes
20
+ if (url.startsWith('/api/')) {
21
+ try {
22
+ const webRequest = await toWebRequest(req);
23
+ const response = await handler.handle(webRequest);
24
+ await writeWebResponse(res, response);
25
+ }
26
+ catch {
27
+ if (res.headersSent) {
28
+ res.destroy();
29
+ return;
30
+ }
31
+ res.statusCode = 500;
32
+ res.setHeader('content-type', 'application/json');
33
+ res.end(JSON.stringify({ error: 'Internal server error' }));
34
+ }
35
+ return;
36
+ }
37
+ // Static files
38
+ if (options.staticDir) {
39
+ const served = await serveStatic(res, options.staticDir, url);
40
+ if (served)
41
+ return;
42
+ // SPA fallback — serve index.html for non-file paths
43
+ const indexServed = await serveStatic(res, options.staticDir, '/index.html');
44
+ if (indexServed)
45
+ return;
46
+ }
47
+ res.statusCode = 404;
48
+ res.setHeader('content-type', 'text/plain');
49
+ res.end('Not Found');
50
+ });
51
+ server.listen(port, () => {
52
+ console.log(`krantho server listening on http://localhost:${port}`);
53
+ if (options.staticDir) {
54
+ console.log(` Static files: ${options.staticDir}`);
55
+ }
56
+ console.log(` API: http://localhost:${port}/api/`);
57
+ });
58
+ }
59
+ async function serveStatic(res, staticDir, urlPath) {
60
+ const resolvedDir = resolve(staticDir);
61
+ const safeRoot = resolvedDir.endsWith(sep) ? resolvedDir : resolvedDir + sep;
62
+ const filePath = resolve(join(staticDir, urlPath));
63
+ // Prevent directory traversal — trailing separator prevents sibling-directory bypass
64
+ if (!filePath.startsWith(safeRoot) && filePath !== resolvedDir) {
65
+ return false;
66
+ }
67
+ try {
68
+ const stats = await stat(filePath);
69
+ if (!stats.isFile())
70
+ return false;
71
+ const content = await readFile(filePath);
72
+ const ext = extname(filePath);
73
+ const contentType = MIME_TYPES[ext] ?? 'application/octet-stream';
74
+ res.statusCode = 200;
75
+ res.setHeader('content-type', contentType);
76
+ res.end(content);
77
+ return true;
78
+ }
79
+ catch {
80
+ return false;
81
+ }
82
+ }
83
+ //# sourceMappingURL=standalone.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standalone.js","sourceRoot":"","sources":["../src/standalone.ts"],"names":[],"mappings":"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"}
package/dist/vite.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import type { KranthoHandlerOptions } from '@krantho/server';
2
+ import type { Plugin } from 'vite';
3
+ export type { KranthoHandlerOptions };
4
+ /**
5
+ * Vite plugin that adds krantho API middleware to the dev server.
6
+ *
7
+ * Intercepts requests matching `/api/*` and routes them through the
8
+ * krantho handler. All other requests pass through to Vite normally.
9
+ */
10
+ export declare function kranthoPlugin(options: KranthoHandlerOptions): Plugin;
11
+ //# sourceMappingURL=vite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AAE5D,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAA;AAIjD,YAAY,EAAE,qBAAqB,EAAE,CAAA;AAErC;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,qBAAqB,GAAG,MAAM,CAgEpE"}
package/dist/vite.js ADDED
@@ -0,0 +1,72 @@
1
+ import { createLazyHandler, createStudioServer } from '@krantho/server';
2
+ import { toWebRequest } from './node-request.js';
3
+ import { writeWebResponse } from './node-response.js';
4
+ /**
5
+ * Vite plugin that adds krantho API middleware to the dev server.
6
+ *
7
+ * Intercepts requests matching `/api/*` and routes them through the
8
+ * krantho handler. All other requests pass through to Vite normally.
9
+ */
10
+ export function kranthoPlugin(options) {
11
+ const getHandler = createLazyHandler({ ...options, framework: 'node' });
12
+ const serveStudio = createStudioServer(options.config);
13
+ return {
14
+ name: 'krantho-api',
15
+ configureServer(server) {
16
+ // Serve Studio SPA
17
+ server.middlewares.use((req, res, next) => {
18
+ const url = req.url ?? '';
19
+ if (!url.startsWith('/studio')) {
20
+ next();
21
+ return;
22
+ }
23
+ let response;
24
+ try {
25
+ response = serveStudio({
26
+ url: new URL(url, 'http://localhost'),
27
+ cookie: req.headers.cookie,
28
+ });
29
+ }
30
+ catch (err) {
31
+ next(err);
32
+ return;
33
+ }
34
+ res.statusCode = response.status;
35
+ for (const [key, value] of response.headers) {
36
+ res.setHeader(key, value);
37
+ }
38
+ response
39
+ .arrayBuffer()
40
+ .then((buf) => res.end(Buffer.from(buf)))
41
+ .catch(() => {
42
+ res.statusCode = 500;
43
+ res.end('Failed to read Studio asset');
44
+ });
45
+ });
46
+ // Serve krantho API
47
+ server.middlewares.use(async (req, res, next) => {
48
+ const url = req.url ?? '';
49
+ if (!url.startsWith('/api/')) {
50
+ next();
51
+ return;
52
+ }
53
+ try {
54
+ const handler = await getHandler();
55
+ const webRequest = await toWebRequest(req);
56
+ const response = await handler.handle(webRequest);
57
+ await writeWebResponse(res, response);
58
+ }
59
+ catch {
60
+ if (res.headersSent) {
61
+ res.destroy();
62
+ return;
63
+ }
64
+ res.statusCode = 500;
65
+ res.setHeader('content-type', 'application/json');
66
+ res.end(JSON.stringify({ error: 'Internal server error' }));
67
+ }
68
+ });
69
+ },
70
+ };
71
+ }
72
+ //# sourceMappingURL=vite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite.js","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,56 @@
1
1
  {
2
2
  "name": "@krantho/server-node",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "1.0.1",
4
+ "description": "The krantho adapter for Node.js, Express and Vite: a request handler, a Vite plugin and a standalone server.",
5
+ "license": "MIT",
6
+ "homepage": "https://krantho.com",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/descoped/krantho.git",
10
+ "directory": "packages/server-node"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/descoped/krantho/issues"
14
+ },
15
+ "type": "module",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js"
20
+ },
21
+ "./vite": {
22
+ "types": "./dist/vite.d.ts",
23
+ "import": "./dist/vite.js"
24
+ },
25
+ "./standalone": {
26
+ "types": "./dist/standalone.d.ts",
27
+ "import": "./dist/standalone.js"
28
+ }
29
+ },
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "dependencies": {
34
+ "@krantho/server": "1.0.1"
35
+ },
36
+ "peerDependencies": {
37
+ "vite": ">=8.0.0"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "vite": {
41
+ "optional": true
42
+ }
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^25.9.9",
46
+ "typescript": "^6.0.3",
47
+ "vite": "^8.3.8",
48
+ "zod": "^4.6.7",
49
+ "@krantho/schema": "1.0.1"
50
+ },
51
+ "scripts": {
52
+ "build": "tsc",
53
+ "typecheck": "tsc --noEmit",
54
+ "clean": "rm -rf dist"
55
+ }
6
56
  }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.