@catmint/adapter-node 0.0.0-prealpha.1 → 0.0.0-prealpha.10
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/README.md +45 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/server-template.d.ts +2 -0
- package/dist/server-template.d.ts.map +1 -1
- package/dist/server-template.js +231 -15
- package/dist/server-template.js.map +1 -1
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# @catmint/adapter-node
|
|
2
|
+
|
|
3
|
+
Node.js production server adapter for [Catmint](https://github.com/darylcecile/catmint).
|
|
4
|
+
|
|
5
|
+
Generates a standalone Node.js server entry point that can be started with `node dist/server/index.js`.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @catmint/adapter-node
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
// catmint.config.ts
|
|
17
|
+
import { defineConfig } from "catmint/config";
|
|
18
|
+
import nodeAdapter from "@catmint/adapter-node";
|
|
19
|
+
|
|
20
|
+
export default defineConfig({
|
|
21
|
+
mode: "fullstack",
|
|
22
|
+
adapter: nodeAdapter({
|
|
23
|
+
port: 6468,
|
|
24
|
+
host: "0.0.0.0",
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Options
|
|
30
|
+
|
|
31
|
+
| Option | Type | Default | Description |
|
|
32
|
+
| ------ | -------- | ----------- | ----------------- |
|
|
33
|
+
| `port` | `number` | `3000` | Port to listen on |
|
|
34
|
+
| `host` | `string` | `"0.0.0.0"` | Host to bind to |
|
|
35
|
+
|
|
36
|
+
## Deploy
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
catmint build
|
|
40
|
+
node dist/server/index.js
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## License
|
|
44
|
+
|
|
45
|
+
GNU General Public License v2
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,13 @@ export interface NodeAdapterOptions {
|
|
|
7
7
|
port?: number;
|
|
8
8
|
/** Host to bind to (default: '0.0.0.0') */
|
|
9
9
|
host?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Maximum allowed request body size in bytes.
|
|
12
|
+
* Applies to server function RPC calls and API endpoints.
|
|
13
|
+
*
|
|
14
|
+
* @default 1_048_576 (1 MB)
|
|
15
|
+
*/
|
|
16
|
+
maxBodySize?: number;
|
|
10
17
|
}
|
|
11
18
|
/**
|
|
12
19
|
* Create a Node.js adapter for Catmint.
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,cAAc,EAGf,MAAM,gBAAgB,CAAC;AAIxB;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,OAAO,UAAU,WAAW,CACjC,OAAO,CAAC,EAAE,kBAAkB,GAC3B,cAAc,CAgChB"}
|
package/dist/index.js
CHANGED
|
@@ -17,14 +17,20 @@ import { generateServerEntry } from "./server-template.js";
|
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
19
|
export default function nodeAdapter(options) {
|
|
20
|
-
const port = options?.port ??
|
|
20
|
+
const port = options?.port ?? 6468;
|
|
21
21
|
const host = options?.host ?? "0.0.0.0";
|
|
22
|
+
const maxBodySize = options?.maxBodySize ?? 1_048_576;
|
|
22
23
|
return {
|
|
23
24
|
name: "@catmint/adapter-node",
|
|
24
25
|
async adapt(context) {
|
|
25
26
|
context.log("@catmint/adapter-node: Generating standalone Node.js server...");
|
|
26
27
|
const manifest = context.manifest;
|
|
27
|
-
const entryContent = generateServerEntry(manifest, {
|
|
28
|
+
const entryContent = generateServerEntry(manifest, {
|
|
29
|
+
port,
|
|
30
|
+
host,
|
|
31
|
+
maxBodySize,
|
|
32
|
+
headerPreset: manifest.config.security.headerPreset,
|
|
33
|
+
});
|
|
28
34
|
// Write to dist/server/index.js (separate from the SSR bundle at dist/ssr/)
|
|
29
35
|
// so `catmint start` can still load the SSR entry for its built-in server.
|
|
30
36
|
await context.writeFile("server/index.js", entryContent);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4DAA4D;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAQ5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAmB3D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,OAAO,UAAU,WAAW,CACjC,OAA4B;IAE5B,MAAM,IAAI,GAAG,OAAO,EAAE,IAAI,IAAI,IAAI,CAAC;IACnC,MAAM,IAAI,GAAG,OAAO,EAAE,IAAI,IAAI,SAAS,CAAC;IACxC,MAAM,WAAW,GAAG,OAAO,EAAE,WAAW,IAAI,SAAS,CAAC;IAEtD,OAAO;QACL,IAAI,EAAE,uBAAuB;QAC7B,KAAK,CAAC,KAAK,CAAC,OAAuB;YACjC,OAAO,CAAC,GAAG,CACT,gEAAgE,CACjE,CAAC;YAEF,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAuB,CAAC;YACjD,MAAM,YAAY,GAAG,mBAAmB,CAAC,QAAQ,EAAE;gBACjD,IAAI;gBACJ,IAAI;gBACJ,WAAW;gBACX,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY;aACpD,CAAC,CAAC;YAEH,4EAA4E;YAC5E,2EAA2E;YAC3E,MAAM,OAAO,CAAC,SAAS,CAAC,iBAAiB,EAAE,YAAY,CAAC,CAAC;YAEzD,OAAO,CAAC,GAAG,CACT,oEAAoE,CACrE,CAAC;YACF,OAAO,CAAC,GAAG,CACT,iFAAiF,IAAI,IAAI,IAAI,GAAG,CACjG,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-template.d.ts","sourceRoot":"","sources":["../src/server-template.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"server-template.d.ts","sourceRoot":"","sources":["../src/server-template.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,UAAU,GAAG,MAAM,CAAC;CACnC;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,WAAW,EACtB,OAAO,EAAE,aAAa,GACrB,MAAM,CA8jBR"}
|
package/dist/server-template.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
export function generateServerEntry(_manifest, options) {
|
|
13
13
|
return `// Auto-generated by @catmint/adapter-node — do not edit
|
|
14
14
|
import { createServer } from "node:http";
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
15
16
|
import { readFile, stat } from "node:fs/promises";
|
|
16
17
|
import { join, extname, resolve } from "node:path";
|
|
17
18
|
import { fileURLToPath } from "node:url";
|
|
@@ -19,6 +20,7 @@ import { fileURLToPath } from "node:url";
|
|
|
19
20
|
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
20
21
|
const CLIENT_DIR = resolve(__dirname, "../client");
|
|
21
22
|
const STATIC_DIR = resolve(__dirname, "../static");
|
|
23
|
+
const PRERENDERED_DIR = resolve(__dirname, "../prerendered");
|
|
22
24
|
|
|
23
25
|
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : ${options.port};
|
|
24
26
|
const HOST = process.env.HOST ?? ${JSON.stringify(options.host)};
|
|
@@ -161,14 +163,58 @@ function __catmintErrorPage(statusCode, detail) {
|
|
|
161
163
|
}
|
|
162
164
|
|
|
163
165
|
function collectBody(req) {
|
|
166
|
+
const MAX_BODY_SIZE = ${options.maxBodySize};
|
|
164
167
|
return new Promise((resolve, reject) => {
|
|
165
168
|
const chunks = [];
|
|
166
|
-
|
|
169
|
+
let totalLength = 0;
|
|
170
|
+
req.on("data", (chunk) => {
|
|
171
|
+
totalLength += chunk.length;
|
|
172
|
+
if (totalLength > MAX_BODY_SIZE) {
|
|
173
|
+
req.destroy();
|
|
174
|
+
reject(new Error("Request body too large"));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
chunks.push(chunk);
|
|
178
|
+
});
|
|
167
179
|
req.on("end", () => resolve(Buffer.concat(chunks)));
|
|
168
180
|
req.on("error", reject);
|
|
169
181
|
});
|
|
170
182
|
}
|
|
171
183
|
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
// Server function error detection helpers (duck-typing)
|
|
186
|
+
//
|
|
187
|
+
// The adapter cannot import from \`catmint\` at runtime. Instead we detect
|
|
188
|
+
// error types by checking for characteristic properties.
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
|
|
191
|
+
function __isClientSafeErrorLike(value) {
|
|
192
|
+
if (!value || typeof value !== "object") return false;
|
|
193
|
+
if (value instanceof Error && typeof value.statusCode === "number") {
|
|
194
|
+
var proto = Object.getPrototypeOf(value);
|
|
195
|
+
while (proto && proto !== Error.prototype) {
|
|
196
|
+
if (proto.constructor && proto.constructor.name === "ClientSafeError") return true;
|
|
197
|
+
proto = Object.getPrototypeOf(proto);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function __isRedirectErrorLike(value) {
|
|
204
|
+
if (!value || typeof value !== "object") return false;
|
|
205
|
+
return (
|
|
206
|
+
value instanceof Error &&
|
|
207
|
+
value.name === "RedirectError" &&
|
|
208
|
+
typeof value.url === "string" &&
|
|
209
|
+
typeof value.status === "number"
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function __errorRef(err) {
|
|
214
|
+
var content = String(Date.now()) + ":" + (err instanceof Error ? err.message : String(err));
|
|
215
|
+
return createHash("sha256").update(content).digest("hex").slice(0, 8);
|
|
216
|
+
}
|
|
217
|
+
|
|
172
218
|
// Import the RSC and SSR entries for the rendering pipeline
|
|
173
219
|
const rscEntry = await import("../rsc/index.js");
|
|
174
220
|
const ssrEntry = await import("../ssr/index.js");
|
|
@@ -189,14 +235,52 @@ async function pipeWebStreamToResponse(webStream, res) {
|
|
|
189
235
|
}
|
|
190
236
|
}
|
|
191
237
|
|
|
238
|
+
function nodeReqToWebRequest(req) {
|
|
239
|
+
const protocol = "http";
|
|
240
|
+
const host = req.headers.host || \`\${HOST}:\${PORT}\`;
|
|
241
|
+
const reqUrl = new URL(req.url || "/", \`\${protocol}://\${host}\`);
|
|
242
|
+
const headers = new Headers();
|
|
243
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
244
|
+
if (value) {
|
|
245
|
+
if (Array.isArray(value)) {
|
|
246
|
+
for (const v of value) {
|
|
247
|
+
headers.append(key, v);
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
headers.set(key, value);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return new Request(reqUrl.href, {
|
|
255
|
+
method: req.method || "GET",
|
|
256
|
+
headers,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
192
260
|
const server = createServer(async (req, res) => {
|
|
261
|
+
${options.headerPreset === "baseline"
|
|
262
|
+
? ` // Baseline security headers
|
|
263
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
264
|
+
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
265
|
+
res.setHeader("X-Frame-Options", "SAMEORIGIN");
|
|
266
|
+
`
|
|
267
|
+
: ""}
|
|
193
268
|
const url = new URL(req.url || "/", \`http://\${HOST}:\${PORT}\`);
|
|
194
269
|
const pathname = url.pathname;
|
|
195
270
|
const method = (req.method || "GET").toUpperCase();
|
|
196
271
|
|
|
197
272
|
try {
|
|
198
|
-
// 1. Serve
|
|
273
|
+
// 1. Serve client assets from dist/client/ (immutable for hashed assets)
|
|
274
|
+
// These have hashed filenames and never conflict with app routes.
|
|
199
275
|
{
|
|
276
|
+
const filePath = join(CLIENT_DIR, pathname);
|
|
277
|
+
const served = await serveStaticFile(res, filePath, pathname.includes("/assets/"));
|
|
278
|
+
if (served) return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// 2. Pre-rendered static pages WITHOUT middleware: serve directly from
|
|
282
|
+
// dist/static/ as a fast path — no middleware execution needed.
|
|
283
|
+
if (method === "GET") {
|
|
200
284
|
let staticPath = pathname;
|
|
201
285
|
if (!extname(staticPath)) {
|
|
202
286
|
staticPath = staticPath.endsWith("/")
|
|
@@ -208,16 +292,79 @@ const server = createServer(async (req, res) => {
|
|
|
208
292
|
if (served) return;
|
|
209
293
|
}
|
|
210
294
|
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
295
|
+
// 3. Middleware execution — runs for all remaining routes (server functions,
|
|
296
|
+
// endpoints, prerendered pages with middleware, RSC-rendered pages).
|
|
297
|
+
// For RSC flight requests, run middleware against the TARGET page path
|
|
298
|
+
// (from ?path= query param), not the literal /__catmint/rsc pathname.
|
|
299
|
+
var middlewareHeaders = null;
|
|
300
|
+
if (ssrEntry.executeMiddleware) {
|
|
301
|
+
try {
|
|
302
|
+
var middlewarePath = pathname;
|
|
303
|
+
if (pathname === "/__catmint/rsc") {
|
|
304
|
+
var rscTargetPath = url.searchParams.get("path");
|
|
305
|
+
if (rscTargetPath) middlewarePath = rscTargetPath;
|
|
306
|
+
}
|
|
307
|
+
var webRequest = nodeReqToWebRequest(req);
|
|
308
|
+
var mwResult = await ssrEntry.executeMiddleware(middlewarePath, webRequest);
|
|
309
|
+
|
|
310
|
+
if (mwResult.shortCircuit) {
|
|
311
|
+
// Middleware short-circuited — return its response directly
|
|
312
|
+
res.writeHead(mwResult.shortCircuit.status,
|
|
313
|
+
Object.fromEntries(mwResult.shortCircuit.headers));
|
|
314
|
+
var scBody = await mwResult.shortCircuit.text();
|
|
315
|
+
res.end(scBody);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Middleware passed — capture headers to merge into final response
|
|
320
|
+
if (mwResult.headers && typeof mwResult.headers.forEach === "function") {
|
|
321
|
+
middlewareHeaders = mwResult.headers;
|
|
322
|
+
}
|
|
323
|
+
} catch (err) {
|
|
324
|
+
console.error("Middleware error:", err);
|
|
325
|
+
if (!res.headersSent) {
|
|
326
|
+
res.writeHead(500);
|
|
327
|
+
res.end("Internal Server Error");
|
|
328
|
+
}
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
216
331
|
}
|
|
217
332
|
|
|
218
|
-
//
|
|
333
|
+
// Helper: merge middleware headers into the response before sending.
|
|
334
|
+
function applyMiddlewareHeaders() {
|
|
335
|
+
if (middlewareHeaders) {
|
|
336
|
+
middlewareHeaders.forEach(function(value, key) {
|
|
337
|
+
res.setHeader(key, value);
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// 4. Handle server function RPC calls (/__catmint/fn/*)
|
|
219
343
|
if (pathname.startsWith("/__catmint/fn/") && ssrEntry.handleServerFn) {
|
|
220
344
|
try {
|
|
345
|
+
// Enforce POST method for server function calls
|
|
346
|
+
if (method !== "POST") {
|
|
347
|
+
sendJson(res, 405, { error: "Method " + method + " not allowed, expected POST" });
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Enforce Content-Type: application/json
|
|
352
|
+
const ct = (req.headers["content-type"] || "").toLowerCase();
|
|
353
|
+
if (ct && !ct.startsWith("application/json")) {
|
|
354
|
+
sendJson(res, 415, { error: "Unsupported Content-Type, expected application/json" });
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Validate Origin header to mitigate cross-site invocation
|
|
359
|
+
const origin = req.headers["origin"];
|
|
360
|
+
if (origin) {
|
|
361
|
+
const expected = \`http://\${HOST}:\${PORT}\`;
|
|
362
|
+
if (origin !== expected && origin !== \`https://\${HOST}:\${PORT}\`) {
|
|
363
|
+
sendJson(res, 403, { error: "Origin not allowed" });
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
221
368
|
const body = await collectBody(req);
|
|
222
369
|
let parsed;
|
|
223
370
|
try {
|
|
@@ -228,18 +375,47 @@ const server = createServer(async (req, res) => {
|
|
|
228
375
|
}
|
|
229
376
|
const result = await ssrEntry.handleServerFn(pathname, parsed);
|
|
230
377
|
if (result) {
|
|
378
|
+
// Check if the result IS a ClientSafeError (returned, not thrown)
|
|
379
|
+
if (__isClientSafeErrorLike(result.result)) {
|
|
380
|
+
applyMiddlewareHeaders();
|
|
381
|
+
sendJson(res, 200, {
|
|
382
|
+
__clientSafeError: true,
|
|
383
|
+
error: result.result.message,
|
|
384
|
+
statusCode: result.result.statusCode,
|
|
385
|
+
data: result.result.data,
|
|
386
|
+
});
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
applyMiddlewareHeaders();
|
|
231
390
|
sendJson(res, 200, result.result);
|
|
232
391
|
return;
|
|
233
392
|
}
|
|
234
393
|
sendJson(res, 404, { error: "Server function not found" });
|
|
235
394
|
} catch (err) {
|
|
236
|
-
|
|
237
|
-
|
|
395
|
+
// Always log full error server-side
|
|
396
|
+
console.error("[catmint] Server function error:", err);
|
|
397
|
+
|
|
398
|
+
// RedirectError — send redirect envelope
|
|
399
|
+
if (__isRedirectErrorLike(err)) {
|
|
400
|
+
sendJson(res, 200, { __redirect: true, url: err.url, status: err.status });
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// ClientSafeError — developer opted in, expose to client
|
|
405
|
+
if (__isClientSafeErrorLike(err)) {
|
|
406
|
+
sendJson(res, err.statusCode, { error: err.message, data: err.data });
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Default: sanitize — generic message with correlation hash
|
|
411
|
+
const ref = __errorRef(err);
|
|
412
|
+
console.error("[catmint] Error reference [ref: " + ref + "] — see above for full error");
|
|
413
|
+
sendJson(res, 500, { error: "Internal Server Error [ref: " + ref + "]" });
|
|
238
414
|
}
|
|
239
415
|
return;
|
|
240
416
|
}
|
|
241
417
|
|
|
242
|
-
//
|
|
418
|
+
// 5. RSC flight stream for client-side navigation (/__catmint/rsc?path=...)
|
|
243
419
|
if (pathname === "/__catmint/rsc" && method === "GET" && rscEntry.render) {
|
|
244
420
|
const targetPath = url.searchParams.get("path");
|
|
245
421
|
if (!targetPath) {
|
|
@@ -251,6 +427,7 @@ const server = createServer(async (req, res) => {
|
|
|
251
427
|
try {
|
|
252
428
|
const rscResult = await rscEntry.render(targetPath);
|
|
253
429
|
if (rscResult) {
|
|
430
|
+
applyMiddlewareHeaders();
|
|
254
431
|
res.writeHead(200, {
|
|
255
432
|
"Content-Type": "text/x-component; charset=utf-8",
|
|
256
433
|
"Cache-Control": "no-cache, no-store, must-revalidate",
|
|
@@ -272,7 +449,7 @@ const server = createServer(async (req, res) => {
|
|
|
272
449
|
return;
|
|
273
450
|
}
|
|
274
451
|
|
|
275
|
-
//
|
|
452
|
+
// 6. API endpoint handling
|
|
276
453
|
if (ssrEntry.hasEndpoint && ssrEntry.hasEndpoint(pathname)) {
|
|
277
454
|
try {
|
|
278
455
|
const protocol = "http";
|
|
@@ -295,7 +472,10 @@ const server = createServer(async (req, res) => {
|
|
|
295
472
|
const result = await ssrEntry.handleEndpoint(pathname, method, webRequest);
|
|
296
473
|
|
|
297
474
|
if (result) {
|
|
298
|
-
|
|
475
|
+
// Copy response headers then merge middleware headers
|
|
476
|
+
var epHeaders = Object.fromEntries(result.response.headers);
|
|
477
|
+
res.writeHead(result.response.status, epHeaders);
|
|
478
|
+
applyMiddlewareHeaders();
|
|
299
479
|
if (result.response.body) {
|
|
300
480
|
await pipeWebStreamToResponse(result.response.body, res);
|
|
301
481
|
} else {
|
|
@@ -317,12 +497,41 @@ const server = createServer(async (req, res) => {
|
|
|
317
497
|
}
|
|
318
498
|
}
|
|
319
499
|
|
|
320
|
-
//
|
|
500
|
+
// 7. Pre-rendered pages WITH middleware: serve from dist/prerendered/
|
|
501
|
+
// These are static routes that have middleware ancestors — their HTML
|
|
502
|
+
// was pre-rendered but they must go through the middleware pipeline.
|
|
503
|
+
if (method === "GET") {
|
|
504
|
+
let prerenderedPath = pathname;
|
|
505
|
+
if (!extname(prerenderedPath)) {
|
|
506
|
+
prerenderedPath = prerenderedPath.endsWith("/")
|
|
507
|
+
? prerenderedPath + "index.html"
|
|
508
|
+
: prerenderedPath + ".html";
|
|
509
|
+
}
|
|
510
|
+
try {
|
|
511
|
+
const prFilePath = join(PRERENDERED_DIR, prerenderedPath);
|
|
512
|
+
const prStats = await stat(prFilePath);
|
|
513
|
+
if (prStats.isFile()) {
|
|
514
|
+
const content = await readFile(prFilePath);
|
|
515
|
+
applyMiddlewareHeaders();
|
|
516
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
517
|
+
res.setHeader("Content-Length", content.byteLength);
|
|
518
|
+
res.setHeader("Cache-Control", "public, max-age=0, must-revalidate");
|
|
519
|
+
res.writeHead(200);
|
|
520
|
+
res.end(content);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
} catch {
|
|
524
|
+
// No prerendered file — continue to RSC rendering
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// 8. RSC → SSR page rendering pipeline
|
|
321
529
|
if (method === "GET" && rscEntry.render) {
|
|
322
530
|
try {
|
|
323
531
|
const rscResult = await rscEntry.render(pathname);
|
|
324
532
|
if (rscResult) {
|
|
325
533
|
const htmlStream = await ssrEntry.renderToHtml(rscResult.stream, rscResult.headConfig);
|
|
534
|
+
applyMiddlewareHeaders();
|
|
326
535
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
327
536
|
await pipeWebStreamToResponse(htmlStream, res);
|
|
328
537
|
return;
|
|
@@ -336,7 +545,14 @@ const server = createServer(async (req, res) => {
|
|
|
336
545
|
}
|
|
337
546
|
}
|
|
338
547
|
|
|
339
|
-
//
|
|
548
|
+
// 9. Static assets fallback (dist/static/ for public files like favicon, etc.)
|
|
549
|
+
{
|
|
550
|
+
const filePath = join(STATIC_DIR, pathname);
|
|
551
|
+
const served = await serveStaticFile(res, filePath, false);
|
|
552
|
+
if (served) return;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// 10. 404
|
|
340
556
|
await sendStatusPage(res, 404, pathname);
|
|
341
557
|
} catch (err) {
|
|
342
558
|
console.error("Request error:", err);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-template.js","sourceRoot":"","sources":["../src/server-template.ts"],"names":[],"mappings":"AAAA,gEAAgE;
|
|
1
|
+
{"version":3,"file":"server-template.js","sourceRoot":"","sources":["../src/server-template.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAWhE;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CACjC,SAAsB,EACtB,OAAsB;IAEtB,OAAO;;;;;;;;;;;;mEAY0D,OAAO,CAAC,IAAI;mCAC5C,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BA4IrC,OAAO,CAAC,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgG3C,OAAO,CAAC,YAAY,KAAK,UAAU;QACjC,CAAC,CAAC;;;;CAIL;QACG,CAAC,CAAC,EACN;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4TC,CAAC;AACF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@catmint/adapter-node",
|
|
3
|
-
"version": "0.0.0-prealpha.
|
|
3
|
+
"version": "0.0.0-prealpha.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"catmint": "0.0.0-prealpha.
|
|
17
|
+
"catmint": "0.0.0-prealpha.10"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"typescript": "^5.7.0"
|