hadars 0.2.0 → 0.2.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/dist/lambda.js CHANGED
@@ -1,10 +1,14 @@
1
1
  import {
2
- renderPreflight,
3
- renderToString
4
- } from "./chunk-LY5MTHFV.js";
5
- import {
6
- createElement
7
- } from "./chunk-OS3V4CPN.js";
2
+ buildHeadHtml,
3
+ buildSsrHtml,
4
+ createProxyHandler,
5
+ createRenderCache,
6
+ getReactResponse,
7
+ makePrecontentHtmlGetter,
8
+ parseRequest
9
+ } from "./chunk-HWOLYLPF.js";
10
+ import "./chunk-LY5MTHFV.js";
11
+ import "./chunk-OS3V4CPN.js";
8
12
 
9
13
  // src/lambda.ts
10
14
  import "react";
@@ -12,122 +16,6 @@ import pathMod from "node:path";
12
16
  import { pathToFileURL } from "node:url";
13
17
  import fs from "node:fs/promises";
14
18
 
15
- // src/utils/proxyHandler.tsx
16
- var cloneHeaders = (headers) => {
17
- return new Headers(headers);
18
- };
19
- var getCORSHeaders = (req) => {
20
- const origin = req.headers.get("Origin") || "*";
21
- return {
22
- "Access-Control-Allow-Origin": origin,
23
- "Access-Control-Allow-Methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
24
- "Access-Control-Allow-Headers": req.headers.get("Access-Control-Request-Headers") || "*",
25
- "Access-Control-Allow-Credentials": "true"
26
- };
27
- };
28
- var createProxyHandler = (options) => {
29
- const { proxy, proxyCORS } = options;
30
- if (!proxy) {
31
- return () => void 0;
32
- }
33
- if (typeof proxy === "function") {
34
- return async (req) => {
35
- if (req.method === "OPTIONS" && options.proxyCORS) {
36
- return new Response(null, {
37
- status: 204,
38
- headers: getCORSHeaders(req)
39
- });
40
- }
41
- const res = await proxy(req);
42
- if (res && proxyCORS) {
43
- const modifiedHeaders = new Headers(res.headers);
44
- Object.entries(getCORSHeaders(req)).forEach(([key, value]) => {
45
- modifiedHeaders.set(key, value);
46
- });
47
- return new Response(res.body, {
48
- status: res.status,
49
- statusText: res.statusText,
50
- headers: modifiedHeaders
51
- });
52
- }
53
- return res || void 0;
54
- };
55
- }
56
- const proxyRules = Object.entries(proxy).sort((a, b) => b[0].length - a[0].length);
57
- return async (req) => {
58
- for (const [path, target] of proxyRules) {
59
- if (req.pathname.startsWith(path)) {
60
- if (req.method === "OPTIONS" && proxyCORS) {
61
- return new Response(null, {
62
- status: 204,
63
- headers: getCORSHeaders(req)
64
- });
65
- }
66
- const targetURL = new URL(target);
67
- targetURL.pathname = targetURL.pathname.replace(/\/$/, "") + req.pathname.slice(path.length);
68
- targetURL.search = req.search;
69
- const sendHeaders = cloneHeaders(req.headers);
70
- sendHeaders.set("Host", targetURL.host);
71
- const hasBody = !["GET", "HEAD"].includes(req.method);
72
- const proxyReq = new Request(targetURL.toString(), {
73
- method: req.method,
74
- headers: sendHeaders,
75
- body: hasBody ? req.body : void 0,
76
- redirect: "follow",
77
- // Node.js (undici) requires duplex:'half' when body is a ReadableStream
78
- ...hasBody ? { duplex: "half" } : {}
79
- });
80
- const res = await fetch(proxyReq);
81
- const body = await res.arrayBuffer();
82
- const clonedRes = new Headers(res.headers);
83
- clonedRes.delete("content-length");
84
- clonedRes.delete("content-encoding");
85
- if (proxyCORS) {
86
- Object.entries(getCORSHeaders(req)).forEach(([key, value]) => {
87
- clonedRes.set(key, value);
88
- });
89
- }
90
- return new Response(body, {
91
- status: res.status,
92
- statusText: res.statusText,
93
- headers: clonedRes
94
- });
95
- }
96
- }
97
- return void 0;
98
- };
99
- };
100
-
101
- // src/utils/cookies.ts
102
- var parseCookies = (cookieString) => {
103
- const cookies = {};
104
- if (!cookieString) {
105
- return cookies;
106
- }
107
- const pairs = cookieString.split(";");
108
- for (const pair of pairs) {
109
- const index = pair.indexOf("=");
110
- if (index > -1) {
111
- const key = pair.slice(0, index).trim();
112
- const value = pair.slice(index + 1).trim();
113
- try {
114
- cookies[key] = decodeURIComponent(value);
115
- } catch {
116
- cookies[key] = value;
117
- }
118
- }
119
- }
120
- return cookies;
121
- };
122
-
123
- // src/utils/request.tsx
124
- var parseRequest = (request) => {
125
- const url = new URL(request.url);
126
- const cookies = request.headers.get("Cookie") || "";
127
- const cookieRecord = parseCookies(cookies);
128
- return Object.assign(request, { pathname: url.pathname, search: url.search, location: url.pathname + url.search, cookies: cookieRecord });
129
- };
130
-
131
19
  // src/utils/staticFile.ts
132
20
  import { readFile } from "node:fs/promises";
133
21
  var MIME = {
@@ -165,201 +53,6 @@ async function tryServeFile(filePath) {
165
53
  }
166
54
  }
167
55
 
168
- // src/utils/response.tsx
169
- var ESC = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" };
170
- var escAttr = (s) => s.replace(/[&<>"]/g, (c) => ESC[c] ?? c);
171
- var escText = (s) => s.replace(/[&<>]/g, (c) => ESC[c] ?? c);
172
- var ATTR = {
173
- className: "class",
174
- htmlFor: "for",
175
- httpEquiv: "http-equiv",
176
- charSet: "charset",
177
- crossOrigin: "crossorigin",
178
- noModule: "nomodule",
179
- referrerPolicy: "referrerpolicy",
180
- fetchPriority: "fetchpriority",
181
- hrefLang: "hreflang"
182
- };
183
- function renderHeadTag(tag, id, opts, selfClose = false) {
184
- let attrs = ` id="${escAttr(id)}"`;
185
- let inner = "";
186
- for (const [k, v] of Object.entries(opts)) {
187
- if (k === "key" || k === "children") continue;
188
- if (k === "dangerouslySetInnerHTML") {
189
- inner = v.__html ?? "";
190
- continue;
191
- }
192
- const attr = ATTR[k] ?? k;
193
- if (v === true) attrs += ` ${attr}`;
194
- else if (v !== false && v != null) attrs += ` ${attr}="${escAttr(String(v))}"`;
195
- }
196
- return selfClose ? `<${tag}${attrs}>` : `<${tag}${attrs}>${inner}</${tag}>`;
197
- }
198
- function buildHeadHtml(seoData) {
199
- let html = `<title>${escText(seoData.title ?? "")}</title>`;
200
- for (const [id, opts] of Object.entries(seoData.meta))
201
- html += renderHeadTag("meta", id, opts, true);
202
- for (const [id, opts] of Object.entries(seoData.link))
203
- html += renderHeadTag("link", id, opts, true);
204
- for (const [id, opts] of Object.entries(seoData.style))
205
- html += renderHeadTag("style", id, opts);
206
- for (const [id, opts] of Object.entries(seoData.script))
207
- html += renderHeadTag("script", id, opts);
208
- return html;
209
- }
210
- var getReactResponse = async (req, opts) => {
211
- const App = opts.document.body;
212
- const { getInitProps, getFinalProps } = opts.document;
213
- const context = {
214
- head: { title: "Hadars App", meta: {}, link: {}, style: {}, script: {}, status: 200 }
215
- };
216
- let props = {
217
- ...getInitProps ? await getInitProps(req) : {},
218
- location: req.location,
219
- context
220
- };
221
- const unsuspend = { cache: /* @__PURE__ */ new Map() };
222
- globalThis.__hadarsUnsuspend = unsuspend;
223
- const element = createElement(App, props);
224
- try {
225
- await renderPreflight(element);
226
- } finally {
227
- globalThis.__hadarsUnsuspend = null;
228
- }
229
- const status = context.head.status;
230
- const getAppBody = async () => {
231
- globalThis.__hadarsUnsuspend = unsuspend;
232
- try {
233
- return await renderToString(element);
234
- } finally {
235
- globalThis.__hadarsUnsuspend = null;
236
- }
237
- };
238
- const finalize = async () => {
239
- const { context: _, ...restProps } = getFinalProps ? await getFinalProps(props) : props;
240
- const serverData = {};
241
- let hasServerData = false;
242
- for (const [key, entry] of unsuspend.cache) {
243
- if (entry.status === "fulfilled") {
244
- serverData[key] = entry.value;
245
- hasServerData = true;
246
- }
247
- }
248
- return {
249
- clientProps: {
250
- ...restProps,
251
- location: req.location,
252
- ...hasServerData ? { __serverData: serverData } : {}
253
- }
254
- };
255
- };
256
- return { head: context.head, status, getAppBody, finalize };
257
- };
258
-
259
- // src/utils/ssrHandler.ts
260
- var HEAD_MARKER = '<meta name="HADARS_HEAD">';
261
- var BODY_MARKER = '<meta name="HADARS_BODY">';
262
- var encoder = new TextEncoder();
263
- async function buildSsrHtml(bodyHtml, clientProps, headHtml, getPrecontentHtml) {
264
- const [precontentHtml, postContent] = await Promise.resolve(getPrecontentHtml(headHtml));
265
- const scriptContent = JSON.stringify({ hadars: { props: clientProps } }).replace(/</g, "\\u003c");
266
- return precontentHtml + `<div id="app">${bodyHtml}</div><script id="hadars" type="application/json">${scriptContent}</script>` + postContent;
267
- }
268
- var makePrecontentHtmlGetter = (htmlFilePromise) => {
269
- let preHead = null;
270
- let postHead = null;
271
- let postContent = null;
272
- return (headHtml) => {
273
- if (preHead !== null) {
274
- return [preHead + headHtml + postHead, postContent];
275
- }
276
- return htmlFilePromise.then((html) => {
277
- const headEnd = html.indexOf(HEAD_MARKER);
278
- const contentStart = html.indexOf(BODY_MARKER);
279
- preHead = html.slice(0, headEnd);
280
- postHead = html.slice(headEnd + HEAD_MARKER.length, contentStart);
281
- postContent = html.slice(contentStart + BODY_MARKER.length);
282
- return [preHead + headHtml + postHead, postContent];
283
- });
284
- };
285
- };
286
- async function transformStream(data, stream) {
287
- const writer = stream.writable.getWriter();
288
- writer.write(data);
289
- writer.close();
290
- const chunks = [];
291
- const reader = stream.readable.getReader();
292
- while (true) {
293
- const { done, value } = await reader.read();
294
- if (done) break;
295
- chunks.push(value);
296
- }
297
- const total = chunks.reduce((n, c) => n + c.length, 0);
298
- const out = new Uint8Array(total);
299
- let offset = 0;
300
- for (const c of chunks) {
301
- out.set(c, offset);
302
- offset += c.length;
303
- }
304
- return out;
305
- }
306
- var gzipCompress = (d) => transformStream(d, new globalThis.CompressionStream("gzip"));
307
- var gzipDecompress = (d) => transformStream(d, new globalThis.DecompressionStream("gzip"));
308
- async function buildCacheEntry(res, ttl) {
309
- const buf = await res.arrayBuffer();
310
- const body = await gzipCompress(new Uint8Array(buf));
311
- const headers = [];
312
- res.headers.forEach((v, k) => {
313
- if (k.toLowerCase() !== "content-encoding" && k.toLowerCase() !== "content-length") {
314
- headers.push([k, v]);
315
- }
316
- });
317
- headers.push(["content-encoding", "gzip"]);
318
- return { body, status: res.status, headers, expiresAt: ttl != null ? Date.now() + ttl : null };
319
- }
320
- async function serveFromEntry(entry, req) {
321
- const accept = req.headers.get("Accept-Encoding") ?? "";
322
- if (accept.includes("gzip")) {
323
- return new Response(entry.body.buffer, { status: entry.status, headers: entry.headers });
324
- }
325
- const plain = await gzipDecompress(entry.body);
326
- const headers = entry.headers.filter(([k]) => k.toLowerCase() !== "content-encoding");
327
- return new Response(plain.buffer, { status: entry.status, headers });
328
- }
329
- function createRenderCache(opts, handler) {
330
- const store = /* @__PURE__ */ new Map();
331
- const inFlight = /* @__PURE__ */ new Map();
332
- return async (req, ctx) => {
333
- const hadarsReq = parseRequest(req);
334
- const cacheOpts = await opts(hadarsReq);
335
- const key = cacheOpts?.key ?? null;
336
- if (key != null) {
337
- const entry = store.get(key);
338
- if (entry) {
339
- const expired = entry.expiresAt != null && Date.now() >= entry.expiresAt;
340
- if (!expired) return serveFromEntry(entry, req);
341
- store.delete(key);
342
- }
343
- let flight = inFlight.get(key);
344
- if (!flight) {
345
- const ttl = cacheOpts?.ttl;
346
- flight = handler(new Request(req), ctx).then(async (res) => {
347
- if (!res || res.status < 200 || res.status >= 300 || res.headers.has("set-cookie")) {
348
- return null;
349
- }
350
- const newEntry2 = await buildCacheEntry(res, ttl);
351
- store.set(key, newEntry2);
352
- return newEntry2;
353
- }).catch(() => null).finally(() => inFlight.delete(key));
354
- inFlight.set(key, flight);
355
- }
356
- const newEntry = await flight;
357
- if (newEntry) return serveFromEntry(newEntry, req);
358
- }
359
- return handler(req, ctx);
360
- };
361
- }
362
-
363
56
  // src/lambda.ts
364
57
  function eventToRequest(event) {
365
58
  let method;
@@ -1031,16 +1031,17 @@ async function runFullLifecycle(serialReq) {
1031
1031
  };
1032
1032
  let props = {
1033
1033
  ...getInitProps ? await getInitProps(parsedReq) : {},
1034
- location: serialReq.location,
1035
- context
1034
+ location: serialReq.location
1036
1035
  };
1037
1036
  const unsuspend = { cache: /* @__PURE__ */ new Map() };
1038
1037
  globalThis.__hadarsUnsuspend = unsuspend;
1038
+ globalThis.__hadarsContext = context;
1039
1039
  let appHtml;
1040
1040
  try {
1041
1041
  appHtml = await renderToString(createElement(Component, props));
1042
1042
  } finally {
1043
1043
  globalThis.__hadarsUnsuspend = null;
1044
+ globalThis.__hadarsContext = null;
1044
1045
  }
1045
1046
  const headHtml = buildHeadHtml(context.head);
1046
1047
  const status = context.head.status ?? 200;