@plitzi/sdk-server 0.30.19

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.
Files changed (93) hide show
  1. package/CHANGELOG.md +192 -0
  2. package/README.md +870 -0
  3. package/dist/.gitkeep +0 -0
  4. package/dist/adapters/jsonAdapters.js +46 -0
  5. package/dist/core/createServer.js +47 -0
  6. package/dist/core/mimeTypes.js +41 -0
  7. package/dist/core/requestHandler.js +108 -0
  8. package/dist/core/requestParser.js +34 -0
  9. package/dist/core/staticFiles.js +42 -0
  10. package/dist/core/transports.js +51 -0
  11. package/dist/helpers/buildResponseHelpers.js +43 -0
  12. package/dist/helpers/buildServerInfo.js +55 -0
  13. package/dist/helpers/cache/TtlCache.js +55 -0
  14. package/dist/helpers/cache/cacheManager.js +25 -0
  15. package/dist/helpers/cache/defaults.js +9 -0
  16. package/dist/helpers/cache/keys.js +6 -0
  17. package/dist/helpers/cache/serverCaches.js +15 -0
  18. package/dist/helpers/compress.js +17 -0
  19. package/dist/helpers/escapeJson.js +4 -0
  20. package/dist/helpers/metrics.js +36 -0
  21. package/dist/helpers/normalizePlugins.js +16 -0
  22. package/dist/helpers/runMiddlewares.js +21 -0
  23. package/dist/index.d.ts +2 -0
  24. package/dist/index.js +3 -0
  25. package/dist/middlewares/auth.js +10 -0
  26. package/dist/middlewares/basicAuth.js +67 -0
  27. package/dist/middlewares/spaceDeployment.js +16 -0
  28. package/dist/modules/mcp/handler.js +34 -0
  29. package/dist/modules/mcp/server.js +120 -0
  30. package/dist/modules/rsc/handler.js +69 -0
  31. package/dist/modules/ssr/Component.js +20 -0
  32. package/dist/modules/ssr/buildBody.js +20 -0
  33. package/dist/modules/ssr/handler.js +36 -0
  34. package/dist/modules/ssr/loadPluginComponents.js +45 -0
  35. package/dist/modules/ssr/prepareRender.js +64 -0
  36. package/dist/modules/ssr/registerExternalPlugins.js +54 -0
  37. package/dist/modules/ssr/streamBody.js +54 -0
  38. package/dist/modules/ssr/template.js +10 -0
  39. package/dist/modules/ssr/views/template.ejs +71 -0
  40. package/dist/plugins/compile.js +29 -0
  41. package/dist/plugins/copy.js +13 -0
  42. package/dist/plugins/detect.js +9 -0
  43. package/dist/plugins/manager.js +251 -0
  44. package/dist/public/.gitkeep +0 -0
  45. package/dist/server.d.ts +1 -0
  46. package/dist/server.js +82 -0
  47. package/dist/src/adapters/jsonAdapters.d.ts +7 -0
  48. package/dist/src/core/createServer.d.ts +2 -0
  49. package/dist/src/core/mimeTypes.d.ts +4 -0
  50. package/dist/src/core/requestHandler.d.ts +5 -0
  51. package/dist/src/core/requestParser.d.ts +3 -0
  52. package/dist/src/core/staticFiles.d.ts +2 -0
  53. package/dist/src/core/transports.d.ts +18 -0
  54. package/dist/src/helpers/buildResponseHelpers.d.ts +10 -0
  55. package/dist/src/helpers/buildServerInfo.d.ts +2 -0
  56. package/dist/src/helpers/cache/TtlCache.d.ts +15 -0
  57. package/dist/src/helpers/cache/cacheManager.d.ts +3 -0
  58. package/dist/src/helpers/cache/defaults.d.ts +1 -0
  59. package/dist/src/helpers/cache/index.d.ts +6 -0
  60. package/dist/src/helpers/cache/keys.d.ts +7 -0
  61. package/dist/src/helpers/cache/serverCaches.d.ts +9 -0
  62. package/dist/src/helpers/compress.d.ts +3 -0
  63. package/dist/src/helpers/escapeJson.d.ts +1 -0
  64. package/dist/src/helpers/metrics.d.ts +11 -0
  65. package/dist/src/helpers/normalizePlugins.d.ts +4 -0
  66. package/dist/src/helpers/runMiddlewares.d.ts +2 -0
  67. package/dist/src/index.d.ts +4 -0
  68. package/dist/src/middlewares/auth.d.ts +2 -0
  69. package/dist/src/middlewares/basicAuth.d.ts +6 -0
  70. package/dist/src/middlewares/spaceDeployment.d.ts +2 -0
  71. package/dist/src/modules/mcp/handler.d.ts +3 -0
  72. package/dist/src/modules/mcp/server.d.ts +3 -0
  73. package/dist/src/modules/rsc/handler.d.ts +17 -0
  74. package/dist/src/modules/ssr/Component.d.ts +11 -0
  75. package/dist/src/modules/ssr/buildBody.d.ts +5 -0
  76. package/dist/src/modules/ssr/handler.d.ts +4 -0
  77. package/dist/src/modules/ssr/loadPluginComponents.d.ts +13 -0
  78. package/dist/src/modules/ssr/prepareRender.d.ts +13 -0
  79. package/dist/src/modules/ssr/registerExternalPlugins.d.ts +7 -0
  80. package/dist/src/modules/ssr/streamBody.d.ts +5 -0
  81. package/dist/src/modules/ssr/template.d.ts +6 -0
  82. package/dist/src/plugins/compile.d.ts +3 -0
  83. package/dist/src/plugins/copy.d.ts +1 -0
  84. package/dist/src/plugins/detect.d.ts +3 -0
  85. package/dist/src/plugins/manager.d.ts +40 -0
  86. package/dist/src/standalone/alias-loader.d.mts +1 -0
  87. package/dist/src/standalone/plugins/ClientInfo.d.ts +2 -0
  88. package/dist/src/standalone/plugins/ServerInfo.d.ts +2 -0
  89. package/dist/src/standalone/plugins/SharedInfo.d.ts +2 -0
  90. package/dist/src/standalone/plugins/styles.d.ts +5 -0
  91. package/dist/src/standalone/register-alias.d.mts +1 -0
  92. package/dist/src/standalone/server.d.ts +1 -0
  93. package/package.json +60 -0
package/dist/.gitkeep ADDED
File without changes
@@ -0,0 +1,46 @@
1
+ import { readFileSync } from "node:fs";
2
+ //#region src/adapters/jsonAdapters.ts
3
+ var isDeploymentObject = (v) => typeof v === "object" && ("spaceId" in v || "environment" in v || "error" in v);
4
+ var readJson = (filePath) => JSON.parse(readFileSync(filePath, "utf-8"));
5
+ var createJsonAdapters = (config) => {
6
+ const getOfflineData = (spaceId, environment, revision) => {
7
+ try {
8
+ const filePath = typeof config.offlineData === "function" ? config.offlineData(spaceId, environment, revision) : config.offlineData;
9
+ return Promise.resolve(readJson(filePath));
10
+ } catch (err) {
11
+ console.error("[JsonAdapters] Failed to read offlineData:", err.message);
12
+ return Promise.resolve(void 0);
13
+ }
14
+ };
15
+ const getSpaceDeployment = (req) => {
16
+ const { deployment } = config;
17
+ if (!deployment) return Promise.resolve({
18
+ spaceId: 1,
19
+ environment: "main",
20
+ revision: 0
21
+ });
22
+ if (typeof deployment === "string") try {
23
+ return Promise.resolve(readJson(deployment));
24
+ } catch (err) {
25
+ console.error("[JsonAdapters] Failed to read deployment file:", err.message);
26
+ return Promise.resolve({ error: {
27
+ code: 500,
28
+ message: "Deployment config unreadable"
29
+ } });
30
+ }
31
+ if (isDeploymentObject(deployment)) return Promise.resolve(deployment);
32
+ const byHostname = deployment;
33
+ return Promise.resolve(byHostname[req.hostname] ?? byHostname["*"] ?? {
34
+ spaceId: 1,
35
+ environment: "main",
36
+ revision: 0
37
+ });
38
+ };
39
+ return {
40
+ getOfflineData,
41
+ getSpaceDeployment,
42
+ getUser: config.user ? (req) => Promise.resolve(typeof config.user === "function" ? config.user(req) : config.user) : void 0
43
+ };
44
+ };
45
+ //#endregion
46
+ export { createJsonAdapters };
@@ -0,0 +1,47 @@
1
+ import { DEFAULT_TTL_MS } from "../helpers/cache/defaults.js";
2
+ import { buildCacheManager } from "../helpers/cache/cacheManager.js";
3
+ import { createServerCaches, destroyServerCaches } from "../helpers/cache/serverCaches.js";
4
+ import { makeHandler } from "./requestHandler.js";
5
+ import { buildTransport, protoLabel } from "./transports.js";
6
+ import normalizePlugins, { normalizePluginSource } from "../helpers/normalizePlugins.js";
7
+ import { compileTemplate } from "../modules/ssr/template.js";
8
+ import { PluginManager } from "../plugins/manager.js";
9
+ //#region src/core/createServer.ts
10
+ var createSSRServer = (config) => {
11
+ const { httpVersion: version = 2, cacheTtlMs: htmlTtlMs = DEFAULT_TTL_MS.html } = config;
12
+ if (version >= 3 && !config.tls) throw new Error(`[SSR] httpVersion: ${version} requires a tls config with key and cert`);
13
+ const caches = createServerCaches(htmlTtlMs, config.rsc?.cacheTtlMs ?? DEFAULT_TTL_MS.rsc);
14
+ const cache = caches.html ? buildCacheManager(caches.html) : null;
15
+ const renderFn = config.templateFn ?? compileTemplate();
16
+ const pluginManager = new PluginManager(normalizePlugins(config.plugins ?? {}), config.pluginsCacheDir, config.pluginsTtlMs, config.devMode);
17
+ const plugins = {
18
+ register: (name, source) => {
19
+ const normalized = normalizePluginSource(source);
20
+ pluginManager.register(`${name}@${normalized.version}`, normalized);
21
+ },
22
+ invalidate: (name, version) => pluginManager.invalidate(name, version)
23
+ };
24
+ let primary;
25
+ let h3;
26
+ return {
27
+ cache,
28
+ plugins,
29
+ listen(port, host = "0.0.0.0") {
30
+ const handler = makeHandler(config, port, renderFn, caches, pluginManager);
31
+ ({primary, h3} = buildTransport(config, handler, port));
32
+ primary.listen(port, host, () => {
33
+ console.log(`[SSR] ${protoLabel(version, !!config.tls)} - listening on ${host}:${port}`);
34
+ });
35
+ },
36
+ async close() {
37
+ destroyServerCaches(caches);
38
+ pluginManager.destroy();
39
+ const closeOne = (srv) => new Promise((resolve, reject) => {
40
+ srv.close((err) => err ? reject(err) : resolve());
41
+ });
42
+ await Promise.all([closeOne(primary), ...h3 ? [closeOne(h3)] : []]);
43
+ }
44
+ };
45
+ };
46
+ //#endregion
47
+ export { createSSRServer };
@@ -0,0 +1,41 @@
1
+ //#region src/core/mimeTypes.ts
2
+ var MIME_TYPES = {
3
+ ".html": "text/html; charset=utf-8",
4
+ ".js": "application/javascript; charset=utf-8",
5
+ ".mjs": "application/javascript; charset=utf-8",
6
+ ".cjs": "application/javascript; charset=utf-8",
7
+ ".css": "text/css; charset=utf-8",
8
+ ".json": "application/json; charset=utf-8",
9
+ ".png": "image/png",
10
+ ".jpg": "image/jpeg",
11
+ ".jpeg": "image/jpeg",
12
+ ".gif": "image/gif",
13
+ ".svg": "image/svg+xml",
14
+ ".webp": "image/webp",
15
+ ".ico": "image/x-icon",
16
+ ".woff": "font/woff",
17
+ ".woff2": "font/woff2",
18
+ ".ttf": "font/ttf",
19
+ ".eot": "application/vnd.ms-fontobject",
20
+ ".txt": "text/plain; charset=utf-8",
21
+ ".xml": "application/xml"
22
+ };
23
+ var IMMUTABLE_EXTS = new Set([
24
+ ".js",
25
+ ".mjs",
26
+ ".cjs",
27
+ ".css",
28
+ ".woff",
29
+ ".woff2",
30
+ ".ttf",
31
+ ".eot"
32
+ ]);
33
+ var getMimeType = (filePath) => {
34
+ return MIME_TYPES[filePath.slice(filePath.lastIndexOf(".")).toLowerCase()] ?? "application/octet-stream";
35
+ };
36
+ var getCacheControl = (filePath) => {
37
+ const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
38
+ return IMMUTABLE_EXTS.has(ext) ? "public, max-age=31536000, immutable" : "public, max-age=3600";
39
+ };
40
+ //#endregion
41
+ export { getCacheControl, getMimeType };
@@ -0,0 +1,108 @@
1
+ import { parseRequest } from "./requestParser.js";
2
+ import { serveStatic } from "./staticFiles.js";
3
+ import { buildResponseHelpers } from "../helpers/buildResponseHelpers.js";
4
+ import { runMiddlewares } from "../helpers/runMiddlewares.js";
5
+ import { authMiddleware } from "../middlewares/auth.js";
6
+ import { basicAuthMiddleware } from "../middlewares/basicAuth.js";
7
+ import { spaceDeploymentMiddleware } from "../middlewares/spaceDeployment.js";
8
+ import { handleMcp } from "../modules/mcp/handler.js";
9
+ import { handleRsc } from "../modules/rsc/handler.js";
10
+ import { renderSSR } from "../modules/ssr/handler.js";
11
+ import path from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+ //#region src/core/requestHandler.ts
14
+ var __dirname = path.dirname(fileURLToPath(import.meta.url));
15
+ var BUILTIN_PUBLIC_DIR = path.resolve(__dirname, "../public");
16
+ var handleRequest = async (raw, rawRes, config, port, renderFn, caches, pluginManager) => {
17
+ const req = parseRequest(raw);
18
+ const res = buildResponseHelpers(rawRes, req.headers["accept-encoding"]);
19
+ if (req.path === "\0") {
20
+ res.setStatus(400);
21
+ res.end();
22
+ return;
23
+ }
24
+ res.setHeader("X-Content-Type-Options", "nosniff");
25
+ res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
26
+ res.setHeader("X-DNS-Prefetch-Control", "off");
27
+ const frameOptions = config.frameOptions === void 0 ? "DENY" : config.frameOptions;
28
+ if (frameOptions) if (Array.isArray(frameOptions)) res.setHeader("Content-Security-Policy", `frame-ancestors ${frameOptions.join(" ")}`);
29
+ else {
30
+ res.setHeader("X-Frame-Options", frameOptions);
31
+ res.setHeader("Content-Security-Policy", `frame-ancestors '${frameOptions === "DENY" ? "none" : "self"}'`);
32
+ }
33
+ if ((config.httpVersion ?? 2) >= 3) res.setHeader("Alt-Svc", `h3=":${port}"; ma=86400`);
34
+ if (serveStatic(req, res, BUILTIN_PUBLIC_DIR)) return;
35
+ if (config.publicDir && serveStatic(req, res, config.publicDir)) return;
36
+ if (req.path.startsWith("/.well-known/")) {
37
+ res.setStatus(404);
38
+ res.end();
39
+ return;
40
+ }
41
+ if (req.path.startsWith(pluginManager.urlPrefix + "/")) {
42
+ const relative = req.path.slice(pluginManager.urlPrefix.length);
43
+ const pluginName = relative.split("/")[1];
44
+ if (pluginName && pluginManager.hasPlugin(pluginName)) {
45
+ await pluginManager.prepare(pluginName);
46
+ if (serveStatic({
47
+ ...req,
48
+ path: relative
49
+ }, res, pluginManager.outputDir)) return;
50
+ }
51
+ res.setStatus(404);
52
+ res.send("Not found");
53
+ return;
54
+ }
55
+ const loginPath = config.loginPath === false ? null : config.loginPath ?? "/auth/login";
56
+ if (loginPath && req.method === "POST" && req.path === loginPath) {
57
+ if (await config.adapters.onLogin?.(req)) res.setStatus(200);
58
+ else res.setStatus(401);
59
+ res.end();
60
+ return;
61
+ }
62
+ const logoutPath = config.logoutPath === false ? null : config.logoutPath ?? "/auth/logout";
63
+ if (logoutPath && req.method === "POST" && req.path === logoutPath) {
64
+ await config.adapters.onLogout?.(req);
65
+ res.setStatus(204);
66
+ res.end();
67
+ return;
68
+ }
69
+ if (config.static) for (const [prefix, rootDir] of Object.entries(config.static)) {
70
+ const normalizedPrefix = prefix.endsWith("/") ? prefix : `${prefix}/`;
71
+ if (req.path === prefix || req.path.startsWith(normalizedPrefix)) {
72
+ if (serveStatic({
73
+ ...req,
74
+ path: req.path.slice(prefix.length) || "/"
75
+ }, res, rootDir)) return;
76
+ }
77
+ }
78
+ const mcpPath = config.mcp?.path ?? "/mcp";
79
+ if (config.mcp && (config.mcp.enabled ?? true) && req.path.startsWith(mcpPath)) {
80
+ await handleMcp(raw, rawRes, config.mcp);
81
+ return;
82
+ }
83
+ if (await runMiddlewares([
84
+ spaceDeploymentMiddleware(config.adapters),
85
+ basicAuthMiddleware({ cacheTtlMs: config.cacheTtlMs }),
86
+ authMiddleware(config.adapters),
87
+ ...config.middlewares || []
88
+ ], req, res) || res.status !== 200) return;
89
+ const rscPath = config.rsc?.path ?? "/_rsc";
90
+ if ((config.rsc?.enabled ?? !!config.adapters.getRscData) && req.method === "GET" && req.path === rscPath) {
91
+ await handleRsc(req, res, config, pluginManager, caches.rsc);
92
+ return;
93
+ }
94
+ await renderSSR(req, res, config, renderFn, pluginManager, caches);
95
+ };
96
+ var makeHandler = (config, port, renderFn, caches, pluginManager) => {
97
+ return (raw, rawRes) => {
98
+ handleRequest(raw, rawRes, config, port, renderFn, caches, pluginManager).catch((err) => {
99
+ console.error("[SSR] Unhandled error:", err);
100
+ try {
101
+ if (!rawRes.headersSent) rawRes.writeHead(500, { "Content-Type": "text/plain" });
102
+ rawRes.end("Internal Server Error");
103
+ } catch {}
104
+ });
105
+ };
106
+ };
107
+ //#endregion
108
+ export { makeHandler };
@@ -0,0 +1,34 @@
1
+ //#region src/core/requestParser.ts
2
+ var HOSTNAME_RE = /^[a-zA-Z0-9.-]{1,253}$/u;
3
+ var parseRequest = (raw) => {
4
+ const headers = raw.headers;
5
+ const rawHostname = (headers[":authority"] ?? headers["host"] ?? "").split(":")[0] ?? "";
6
+ const hostname = HOSTNAME_RE.test(rawHostname) ? rawHostname : "";
7
+ const scheme = headers[":scheme"];
8
+ const forwarded = headers["x-forwarded-proto"];
9
+ const encrypted = !scheme && !forwarded && "encrypted" in raw.socket && raw.socket.encrypted === true;
10
+ const protocol = scheme === "https" || forwarded === "https" || encrypted ? "https" : "http";
11
+ const rawUrl = headers[":path"] ?? raw.url ?? "/";
12
+ const qIndex = rawUrl.indexOf("?");
13
+ const rawPath = qIndex === -1 ? rawUrl : rawUrl.slice(0, qIndex);
14
+ const path = rawPath.includes("\0") ? "\0" : rawPath;
15
+ const search = qIndex === -1 ? "" : rawUrl.slice(qIndex);
16
+ const query = {};
17
+ if (search) {
18
+ const sp = new URLSearchParams(search.slice(1));
19
+ for (const [k, v] of sp.entries()) query[k] = v;
20
+ }
21
+ return {
22
+ method: (headers[":method"] ?? raw.method ?? "GET").toUpperCase(),
23
+ path,
24
+ search,
25
+ url: rawUrl,
26
+ hostname,
27
+ protocol,
28
+ headers,
29
+ query,
30
+ ctx: {}
31
+ };
32
+ };
33
+ //#endregion
34
+ export { parseRequest };
@@ -0,0 +1,42 @@
1
+ import { getCacheControl, getMimeType } from "./mimeTypes.js";
2
+ import path from "node:path";
3
+ import fs from "node:fs";
4
+ //#region src/core/staticFiles.ts
5
+ var buildEtag = (stat) => `"${stat.mtimeMs.toString(36)}-${stat.size.toString(36)}"`;
6
+ var serveFile = (req, res, filePath, stat) => {
7
+ const etag = buildEtag(stat);
8
+ const ifNoneMatch = req.headers["if-none-match"];
9
+ res.setHeader("ETag", etag);
10
+ res.setHeader("Cache-Control", getCacheControl(filePath));
11
+ res.setHeader("Last-Modified", stat.mtime.toUTCString());
12
+ if (ifNoneMatch === etag) {
13
+ res.setStatus(304);
14
+ res.end();
15
+ return true;
16
+ }
17
+ const content = fs.readFileSync(filePath);
18
+ res.setHeader("Content-Type", getMimeType(filePath));
19
+ res.setHeader("Content-Length", content.byteLength.toString());
20
+ res.send(content.toString("utf-8"));
21
+ return true;
22
+ };
23
+ var serveStatic = (req, res, rootDir) => {
24
+ const relative = req.path.replace(/^\/+/, "");
25
+ const filePath = path.resolve(rootDir, relative);
26
+ const resolvedRoot = path.resolve(rootDir);
27
+ if (filePath !== resolvedRoot && !filePath.startsWith(resolvedRoot + path.sep)) {
28
+ res.setStatus(403);
29
+ res.send("Forbidden");
30
+ return true;
31
+ }
32
+ if (!fs.existsSync(filePath)) return false;
33
+ const stat = fs.statSync(filePath);
34
+ if (stat.isDirectory()) {
35
+ const indexPath = path.join(filePath, "index.html");
36
+ if (!fs.existsSync(indexPath)) return false;
37
+ return serveFile(req, res, indexPath, fs.statSync(indexPath));
38
+ }
39
+ return serveFile(req, res, filePath, stat);
40
+ };
41
+ //#endregion
42
+ export { serveStatic };
@@ -0,0 +1,51 @@
1
+ import http from "node:http";
2
+ import http2 from "node:http2";
3
+ import https from "node:https";
4
+ //#region src/core/transports.ts
5
+ var tlsOptions = (config) => {
6
+ const { tls } = config;
7
+ if (!tls) throw new Error("[SSR] TLS config required");
8
+ return {
9
+ key: tls.key,
10
+ cert: tls.cert,
11
+ minVersion: tls.minVersion ?? "TLSv1.3"
12
+ };
13
+ };
14
+ var protoLabel = (version, hasTls) => {
15
+ if (version >= 3) return "HTTP/2+3 (TLS)";
16
+ if (version >= 2) return hasTls ? "HTTP/2 (TLS)" : "HTTP/1.1 - TLS Missing";
17
+ return hasTls ? "HTTPS/1.1" : "HTTP/1.1";
18
+ };
19
+ var buildTransport = (config, handler, port) => {
20
+ const version = config.httpVersion ?? 2;
21
+ let primary;
22
+ let h3;
23
+ if (version >= 3) {
24
+ primary = http2.createSecureServer({
25
+ ...tlsOptions(config),
26
+ allowHTTP1: true
27
+ }, handler);
28
+ (async () => {
29
+ try {
30
+ h3 = (await import("node:http3")).createServer(tlsOptions(config), handler);
31
+ h3.listen(port, "0.0.0.0", () => {
32
+ console.log(`[SSR] HTTP/3 (QUIC) listening on port ${port}`);
33
+ });
34
+ } catch {
35
+ console.warn("[SSR] HTTP/3 unavailable — start Node.js with --experimental-quic (requires Node ≥ 23). Falling back to HTTP/2.");
36
+ }
37
+ })();
38
+ } else if (version >= 2) if (config.tls) primary = http2.createSecureServer({
39
+ ...tlsOptions(config),
40
+ allowHTTP1: true
41
+ }, handler);
42
+ else primary = http.createServer(handler);
43
+ else if (config.tls) primary = https.createServer(tlsOptions(config), handler);
44
+ else primary = http.createServer(handler);
45
+ return {
46
+ primary,
47
+ h3
48
+ };
49
+ };
50
+ //#endregion
51
+ export { buildTransport, protoLabel };
@@ -0,0 +1,43 @@
1
+ import { compressBody, selectEncoding } from "./compress.js";
2
+ //#region src/helpers/buildResponseHelpers.ts
3
+ var buildResponseHelpers = (raw, acceptEncoding) => {
4
+ let statusCode = 200;
5
+ const encoding = selectEncoding(acceptEncoding);
6
+ const writeSend = (body) => {
7
+ const compressed = compressBody(body, encoding);
8
+ if (compressed !== body) {
9
+ raw.setHeader("Content-Encoding", encoding);
10
+ raw.setHeader("Vary", "Accept-Encoding");
11
+ }
12
+ raw.setHeader("Content-Length", Buffer.byteLength(compressed).toString());
13
+ if (!raw.headersSent) raw.writeHead(statusCode);
14
+ raw.end(compressed);
15
+ };
16
+ return {
17
+ get status() {
18
+ return statusCode;
19
+ },
20
+ get headers() {
21
+ return raw.getHeaders();
22
+ },
23
+ setHeader(name, value) {
24
+ raw.setHeader(name, value);
25
+ },
26
+ setStatus(code) {
27
+ statusCode = code;
28
+ },
29
+ send(body) {
30
+ writeSend(body);
31
+ },
32
+ write(chunk) {
33
+ if (!raw.headersSent) raw.writeHead(statusCode);
34
+ raw.write(chunk);
35
+ },
36
+ end() {
37
+ if (!raw.headersSent) raw.writeHead(statusCode);
38
+ raw.end();
39
+ }
40
+ };
41
+ };
42
+ //#endregion
43
+ export { buildResponseHelpers };
@@ -0,0 +1,55 @@
1
+ //#region src/helpers/buildServerInfo.ts
2
+ var getEnvironment = (env = "production", server) => {
3
+ switch (env) {
4
+ case "production": return server;
5
+ case "staging": return {
6
+ apiServer: "https://api-stg.plitzi.com",
7
+ ssrServer: "https://ssr-stg.plitzi.com",
8
+ nodeServer: "https://server-stg.plitzi.com",
9
+ graphqlServer: "https://server-stg.plitzi.com/graphql",
10
+ websocketServer: "wss://server-stg.plitzi.com",
11
+ subscriptionServer: "wss://server-stg.plitzi.com/subscriptions",
12
+ ...server
13
+ };
14
+ case "development": return {
15
+ apiServer: "https://api-dev.plitzi.com",
16
+ ssrServer: "https://ssr-dev.plitzi.com",
17
+ nodeServer: "https://server-dev.plitzi.com",
18
+ graphqlServer: "https://server-dev.plitzi.com/graphql",
19
+ websocketServer: "wss://server-dev.plitzi.com",
20
+ subscriptionServer: "wss://server-dev.plitzi.com/subscriptions",
21
+ ...server
22
+ };
23
+ default: return {
24
+ apiServer: "https://api.plitzi.local",
25
+ ssrServer: "https://ssr.plitzi.local:4000",
26
+ nodeServer: "https://server.plitzi.local:8888",
27
+ graphqlServer: "https://server.plitzi.local:8888/graphql",
28
+ websocketServer: "wss://server.plitzi.local:8888",
29
+ subscriptionServer: "wss://server.plitzi.local:8888/subscriptions",
30
+ ...server
31
+ };
32
+ }
33
+ };
34
+ var buildServerInfo = async (req, config) => {
35
+ const accessToken = req.query["access-token"];
36
+ const origin = `${req.protocol}://${req.hostname}`;
37
+ const user = req.ctx.user;
38
+ const { environment = "main", spaceId, revision = 0 } = req.ctx.spaceDeployment ?? {};
39
+ return getEnvironment(config.environment, {
40
+ basePath: "/",
41
+ requestUrl: req.url || "/",
42
+ origin,
43
+ location: {
44
+ hostname: req.hostname,
45
+ pathname: req.path || "/",
46
+ search: req.search
47
+ },
48
+ authenticated: !!user,
49
+ skipAuth: !!accessToken,
50
+ user: user ? { details: user } : void 0,
51
+ rscData: await config.adapters.getRscData?.(req, spaceId, environment, revision, user)
52
+ });
53
+ };
54
+ //#endregion
55
+ export { buildServerInfo };
@@ -0,0 +1,55 @@
1
+ import { DEFAULT_TTL_MS } from "./defaults.js";
2
+ //#region src/helpers/cache/TtlCache.ts
3
+ var TtlCache = class {
4
+ store = /* @__PURE__ */ new Map();
5
+ sweepTimer;
6
+ constructor(ttlMs = DEFAULT_TTL_MS.html, maxSize = 500, sweepIntervalMs = ttlMs) {
7
+ this.ttlMs = ttlMs;
8
+ this.maxSize = maxSize;
9
+ this.sweepTimer = setInterval(() => this.sweep(), sweepIntervalMs);
10
+ this.sweepTimer.unref?.();
11
+ }
12
+ get(key) {
13
+ const entry = this.store.get(key);
14
+ if (!entry) return;
15
+ if (Date.now() > entry.expiresAt) {
16
+ this.store.delete(key);
17
+ return;
18
+ }
19
+ return entry.value;
20
+ }
21
+ set(key, value) {
22
+ if (this.store.size >= this.maxSize) this.store.delete(this.store.keys().next().value);
23
+ this.store.set(key, {
24
+ value,
25
+ expiresAt: Date.now() + this.ttlMs
26
+ });
27
+ }
28
+ delete(key) {
29
+ this.store.delete(key);
30
+ }
31
+ invalidateWhere(predicate) {
32
+ let count = 0;
33
+ for (const key of this.store.keys()) if (predicate(key)) {
34
+ this.store.delete(key);
35
+ count++;
36
+ }
37
+ return count;
38
+ }
39
+ clear() {
40
+ this.store.clear();
41
+ }
42
+ destroy() {
43
+ clearInterval(this.sweepTimer);
44
+ this.store.clear();
45
+ }
46
+ get size() {
47
+ return this.store.size;
48
+ }
49
+ sweep() {
50
+ const now = Date.now();
51
+ for (const [key, entry] of this.store) if (now > entry.expiresAt) this.store.delete(key);
52
+ }
53
+ };
54
+ //#endregion
55
+ export { TtlCache };
@@ -0,0 +1,25 @@
1
+ //#region src/helpers/cache/cacheManager.ts
2
+ var buildCacheManager = (store) => ({
3
+ invalidate(filter) {
4
+ if (!filter || Object.keys(filter).length === 0) {
5
+ const count = store.size;
6
+ store.clear();
7
+ return count;
8
+ }
9
+ return store.invalidateWhere((key) => {
10
+ const [keySpaceId, keyEnvironment, , keyHostname] = key.split("\0");
11
+ if (filter.spaceId !== void 0 && keySpaceId !== String(filter.spaceId)) return false;
12
+ if (filter.environment !== void 0 && keyEnvironment !== filter.environment) return false;
13
+ if (filter.hostname !== void 0 && keyHostname !== filter.hostname) return false;
14
+ return true;
15
+ });
16
+ },
17
+ clear() {
18
+ store.clear();
19
+ },
20
+ get size() {
21
+ return store.size;
22
+ }
23
+ });
24
+ //#endregion
25
+ export { buildCacheManager };
@@ -0,0 +1,9 @@
1
+ //#region src/helpers/cache/defaults.ts
2
+ var DEFAULT_TTL_MS = {
3
+ html: 300 * 1e3,
4
+ rsc: 3e4,
5
+ auth: 300 * 1e3,
6
+ offlineData: 300 * 1e3
7
+ };
8
+ //#endregion
9
+ export { DEFAULT_TTL_MS };
@@ -0,0 +1,6 @@
1
+ //#region src/helpers/cache/keys.ts
2
+ var buildHtmlCacheKey = (accessToken = "anonymous", spaceId, environment, revision, req) => `${accessToken}\0${spaceId ?? 1}\0${environment}\0${revision}\0${req.hostname}\0${req.path}\0${req.search}`;
3
+ var buildOfflineDataCacheKey = (spaceId, environment, revision) => `${spaceId}|${environment}|${revision}`;
4
+ var buildRscCacheKey = (spaceId, environment, revision, userId, idsParam) => `${spaceId}|${environment}|${revision}|${userId ?? "anon"}|${idsParam ?? ""}`;
5
+ //#endregion
6
+ export { buildHtmlCacheKey, buildOfflineDataCacheKey, buildRscCacheKey };
@@ -0,0 +1,15 @@
1
+ import { DEFAULT_TTL_MS } from "./defaults.js";
2
+ import { TtlCache } from "./TtlCache.js";
3
+ //#region src/helpers/cache/serverCaches.ts
4
+ var createServerCaches = (htmlTtlMs = DEFAULT_TTL_MS.html, rscTtlMs = DEFAULT_TTL_MS.rsc) => ({
5
+ html: htmlTtlMs > 0 ? new TtlCache(htmlTtlMs) : void 0,
6
+ rsc: rscTtlMs > 0 ? new TtlCache(rscTtlMs) : void 0,
7
+ offlineData: htmlTtlMs > 0 ? new TtlCache(htmlTtlMs) : void 0
8
+ });
9
+ var destroyServerCaches = ({ html, rsc, offlineData }) => {
10
+ html?.destroy();
11
+ rsc?.destroy();
12
+ offlineData?.destroy();
13
+ };
14
+ //#endregion
15
+ export { createServerCaches, destroyServerCaches };
@@ -0,0 +1,17 @@
1
+ import { brotliCompressSync, constants, gzipSync } from "node:zlib";
2
+ //#region src/helpers/compress.ts
3
+ var MIN_SIZE = 1024;
4
+ var selectEncoding = (acceptEncoding) => {
5
+ if (!acceptEncoding) return "identity";
6
+ if (acceptEncoding.includes("br")) return "br";
7
+ if (acceptEncoding.includes("gzip")) return "gzip";
8
+ return "identity";
9
+ };
10
+ var compressBody = (body, encoding) => {
11
+ if (encoding === "identity" || body.length < MIN_SIZE) return body;
12
+ const buf = Buffer.from(body, "utf-8");
13
+ if (encoding === "br") return brotliCompressSync(buf, { params: { [constants.BROTLI_PARAM_QUALITY]: 4 } });
14
+ return gzipSync(buf, { level: 6 });
15
+ };
16
+ //#endregion
17
+ export { compressBody, selectEncoding };
@@ -0,0 +1,4 @@
1
+ //#region src/helpers/escapeJson.ts
2
+ var escapeJson = (str) => str.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
3
+ //#endregion
4
+ export { escapeJson };
@@ -0,0 +1,36 @@
1
+ //#region src/helpers/metrics.ts
2
+ var RequestMetrics = class {
3
+ start;
4
+ phases = [];
5
+ constructor() {
6
+ this.start = performance.now();
7
+ }
8
+ async measure(name, fn) {
9
+ const t = performance.now();
10
+ const result = await Promise.resolve(fn());
11
+ this.phases.push({
12
+ name,
13
+ dur: Math.round(performance.now() - t)
14
+ });
15
+ return result;
16
+ }
17
+ /** Formats as Server-Timing header value. */
18
+ toServerTimingHeader() {
19
+ const total = Math.round(performance.now() - this.start);
20
+ return [...this.phases.map((p) => `${p.name};dur=${p.dur}`), `total;dur=${total}`].join(", ");
21
+ }
22
+ record(name, dur) {
23
+ this.phases.push({
24
+ name,
25
+ dur
26
+ });
27
+ }
28
+ /** Logs a one-line summary to stdout (dev mode only). */
29
+ log(label) {
30
+ const total = Math.round(performance.now() - this.start);
31
+ const parts = this.phases.map((p) => `${p.name}=${p.dur}ms`).join(" ");
32
+ console.log(`[SSR] ${label} — ${parts} | total=${total}ms`);
33
+ }
34
+ };
35
+ //#endregion
36
+ export { RequestMetrics };
@@ -0,0 +1,16 @@
1
+ //#region src/helpers/normalizePlugins.ts
2
+ var DEFAULT_PLUGIN_VERSION = "1.0.0";
3
+ var normalizePluginSource = (source) => ({
4
+ ...source,
5
+ version: source.version ?? DEFAULT_PLUGIN_VERSION
6
+ });
7
+ var normalizePlugins = (plugins) => {
8
+ const out = {};
9
+ for (const [name, source] of Object.entries(plugins)) {
10
+ const normalized = normalizePluginSource(source);
11
+ out[`${name}@${normalized.version}`] = normalized;
12
+ }
13
+ return out;
14
+ };
15
+ //#endregion
16
+ export { normalizePlugins as default, normalizePluginSource };