@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 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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAA+B,MAAM,gBAAgB,CAAC;AAIlF;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,OAAO,UAAU,WAAW,CACjC,OAAO,CAAC,EAAE,kBAAkB,GAC3B,cAAc,CA0BhB"}
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 ?? 3000;
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, { port, host });
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;AAI5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAY3D;;;;;;;;;;;;;;;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;IAExC,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,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAEnE,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
+ {"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"}
@@ -2,6 +2,8 @@ import type { AppManifest } from "catmint/config";
2
2
  interface ServerOptions {
3
3
  port: number;
4
4
  host: string;
5
+ maxBodySize: number;
6
+ headerPreset: "baseline" | "none";
5
7
  }
6
8
  /**
7
9
  * Generate the content of the standalone Node.js server entry point.
@@ -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;CACd;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,WAAW,EACtB,OAAO,EAAE,aAAa,GACrB,MAAM,CAoWR"}
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"}
@@ -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
- req.on("data", (chunk) => chunks.push(chunk));
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 static assets from dist/static/ (no cache)
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
- // 2. Serve client assets from dist/client/ (immutable for hashed assets)
212
- {
213
- const filePath = join(CLIENT_DIR, pathname);
214
- const served = await serveStaticFile(res, filePath, pathname.includes("/assets/"));
215
- if (served) return;
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
- // 3. Handle server function RPC calls (/__catmint/fn/*)
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
- const message = err instanceof Error ? err.message : String(err);
237
- sendJson(res, 500, { error: message });
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
- // 4. RSC flight stream for client-side navigation (/__catmint/rsc?path=...)
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
- // 5. API endpoint handling
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
- res.writeHead(result.response.status, Object.fromEntries(result.response.headers));
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
- // 6. RSC SSR page rendering pipeline
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
- // 7. 404
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;AAShE;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CACjC,SAAsB,EACtB,OAAsB;IAEtB,OAAO;;;;;;;;;;mEAU0D,OAAO,CAAC,IAAI;mCAC5C,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuV9D,CAAC;AACF,CAAC"}
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.1",
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.1"
17
+ "catmint": "0.0.0-prealpha.10"
18
18
  },
19
19
  "devDependencies": {
20
20
  "typescript": "^5.7.0"