hadars 0.4.1 → 0.4.2

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 (58) hide show
  1. package/dist/{chunk-TV37IMRB.js → chunk-2TMQUXFL.js} +10 -10
  2. package/dist/{chunk-2J2L2H3H.js → chunk-NYLXE7T7.js} +6 -6
  3. package/dist/{chunk-OS3V4CPN.js → chunk-OZUZS2PD.js} +4 -4
  4. package/dist/cli.js +462 -496
  5. package/dist/cloudflare.cjs +11 -11
  6. package/dist/cloudflare.js +3 -3
  7. package/dist/index.d.cts +8 -4
  8. package/dist/index.d.ts +8 -4
  9. package/dist/lambda.cjs +11 -11
  10. package/dist/lambda.js +7 -7
  11. package/dist/loader.cjs +90 -54
  12. package/dist/slim-react/index.cjs +13 -13
  13. package/dist/slim-react/index.js +2 -2
  14. package/dist/slim-react/jsx-runtime.cjs +2 -4
  15. package/dist/slim-react/jsx-runtime.js +1 -1
  16. package/dist/ssr-render-worker.js +174 -161
  17. package/dist/ssr-watch.js +40 -74
  18. package/package.json +8 -10
  19. package/cli-lib.ts +0 -676
  20. package/cli.ts +0 -36
  21. package/index.ts +0 -17
  22. package/src/build.ts +0 -805
  23. package/src/cloudflare.ts +0 -140
  24. package/src/index.tsx +0 -41
  25. package/src/lambda.ts +0 -287
  26. package/src/slim-react/context.ts +0 -55
  27. package/src/slim-react/dispatcher.ts +0 -87
  28. package/src/slim-react/hooks.ts +0 -137
  29. package/src/slim-react/index.ts +0 -232
  30. package/src/slim-react/jsx-runtime.ts +0 -7
  31. package/src/slim-react/jsx.ts +0 -53
  32. package/src/slim-react/render.ts +0 -1101
  33. package/src/slim-react/renderContext.ts +0 -294
  34. package/src/slim-react/types.ts +0 -33
  35. package/src/source/context.ts +0 -113
  36. package/src/source/graphiql.ts +0 -101
  37. package/src/source/inference.ts +0 -260
  38. package/src/source/runner.ts +0 -138
  39. package/src/source/store.ts +0 -50
  40. package/src/ssr-render-worker.ts +0 -116
  41. package/src/ssr-watch.ts +0 -62
  42. package/src/static.ts +0 -109
  43. package/src/types/global.d.ts +0 -5
  44. package/src/types/hadars.ts +0 -350
  45. package/src/utils/Head.tsx +0 -462
  46. package/src/utils/clientScript.tsx +0 -71
  47. package/src/utils/cookies.ts +0 -16
  48. package/src/utils/loader.ts +0 -335
  49. package/src/utils/proxyHandler.tsx +0 -104
  50. package/src/utils/request.tsx +0 -9
  51. package/src/utils/response.tsx +0 -141
  52. package/src/utils/rspack.ts +0 -467
  53. package/src/utils/runtime.ts +0 -19
  54. package/src/utils/serve.ts +0 -155
  55. package/src/utils/ssrHandler.ts +0 -239
  56. package/src/utils/staticFile.ts +0 -43
  57. package/src/utils/template.html +0 -11
  58. package/src/utils/upgradeRequest.tsx +0 -19
@@ -1,239 +0,0 @@
1
- import { parseRequest } from './request';
2
- import { buildHeadHtml } from './response';
3
- import type { AppHead, HadarsOptions } from '../types/hadars';
4
-
5
- export const HEAD_MARKER = '<meta name="HADARS_HEAD">';
6
- export const BODY_MARKER = '<meta name="HADARS_BODY">';
7
-
8
- const encoder = new TextEncoder();
9
-
10
- // ── HTML response assembly ────────────────────────────────────────────────────
11
-
12
- export function buildSsrResponse(
13
- head: AppHead,
14
- status: number,
15
- getAppBody: () => Promise<string>,
16
- finalize: () => Promise<{ clientProps: Record<string, unknown> }>,
17
- getPrecontentHtml: (headHtml: string) => [string, string] | Promise<[string, string]>,
18
- ): Response {
19
- const headHtml = buildHeadHtml(head);
20
- const precontentResult = getPrecontentHtml(headHtml);
21
-
22
- const responseStream = new ReadableStream({
23
- async start(controller) {
24
- try {
25
- // Resolve the template — sync on the hot path (every request after the first).
26
- const [precontentHtml, postContent] = precontentResult instanceof Promise
27
- ? await precontentResult
28
- : precontentResult;
29
-
30
- // Chunk 1 — flush the full <head> shell immediately so the browser
31
- // can start loading CSS / fonts / preload hints before the body arrives.
32
- controller.enqueue(encoder.encode(precontentHtml));
33
-
34
- // Chunk 2 — body HTML. getAppBody() triggers the actual renderToString
35
- // now that head has been flushed. All data is cached from the preflight
36
- // so this pass is fast (no async waits).
37
- const bodyHtml = await getAppBody();
38
- controller.enqueue(encoder.encode(`<div id="app">${bodyHtml}</div>`));
39
-
40
- // Chunk 3 — JSON props script + post-content. Separated so the browser
41
- // can parse/render the body while getFinalProps is still completing.
42
- const { clientProps } = await finalize();
43
- const scriptContent = JSON.stringify({ hadars: { props: clientProps } }).replace(/</g, '\\u003c');
44
- controller.enqueue(encoder.encode(
45
- `<script id="hadars" type="application/json">${scriptContent}</script>` +
46
- postContent,
47
- ));
48
- controller.close();
49
- } catch (err) {
50
- // Head chunk may already be sent; signal a stream error so the
51
- // connection is closed cleanly rather than hanging.
52
- controller.error(err);
53
- }
54
- },
55
- });
56
- return new Response(responseStream, {
57
- headers: { 'Content-Type': 'text/html; charset=utf-8' },
58
- status,
59
- });
60
- }
61
-
62
- /**
63
- * Like {@link buildSsrResponse} but returns the complete HTML string directly
64
- * instead of wrapping it in a streaming Response. Use this in environments
65
- * where streaming is not beneficial (e.g. AWS Lambda) to avoid the
66
- * ReadableStream allocation and the subsequent `.text()` drain overhead.
67
- */
68
- export async function buildSsrHtml(
69
- bodyHtml: string,
70
- clientProps: Record<string, unknown>,
71
- headHtml: string,
72
- getPrecontentHtml: (headHtml: string) => [string, string] | Promise<[string, string]>,
73
- ): Promise<string> {
74
- const [precontentHtml, postContent] = await Promise.resolve(getPrecontentHtml(headHtml));
75
- const scriptContent = JSON.stringify({ hadars: { props: clientProps } }).replace(/</g, '\\u003c');
76
- return (
77
- precontentHtml +
78
- `<div id="app">${bodyHtml}</div><script id="hadars" type="application/json">${scriptContent}</script>` +
79
- postContent
80
- );
81
- }
82
-
83
- /**
84
- * Returns a function that parses `out.html` into pre-head, post-head, and
85
- * post-content segments and caches the result. Call the returned function with
86
- * the per-request headHtml to produce the full HTML prefix and suffix.
87
- */
88
- export const makePrecontentHtmlGetter = (htmlFilePromise: Promise<string>) => {
89
- let preHead: string | null = null;
90
- let postHead: string | null = null;
91
- let postContent: string | null = null;
92
-
93
- // Parse the template as soon as the file read completes — during the
94
- // process's idle time before the first request arrives. This way the
95
- // first real request finds preHead !== null and takes the sync path.
96
- const primed = htmlFilePromise.then(html => {
97
- const headEnd = html.indexOf(HEAD_MARKER);
98
- const contentStart = html.indexOf(BODY_MARKER);
99
- preHead = html.slice(0, headEnd);
100
- postHead = html.slice(headEnd + HEAD_MARKER.length, contentStart);
101
- postContent = html.slice(contentStart + BODY_MARKER.length);
102
- });
103
-
104
- // Returns synchronously once the template has been loaded and parsed
105
- // (every request after the first). Callers can check `instanceof Promise`
106
- // to take a zero-await hot path.
107
- return (headHtml: string): [string, string] | Promise<[string, string]> => {
108
- if (preHead !== null) {
109
- // Hot path — sync return, no Promise allocation.
110
- return [preHead + headHtml + postHead!, postContent!];
111
- }
112
- return primed.then(() => [preHead! + headHtml + postHead!, postContent!]);
113
- };
114
- };
115
-
116
- // ── SSR response cache ────────────────────────────────────────────────────────
117
-
118
- export interface CacheEntry {
119
- /** Gzip-compressed response body. */
120
- body: Uint8Array;
121
- status: number;
122
- /** Headers with Content-Encoding: gzip already set. */
123
- headers: [string, string][];
124
- expiresAt: number | null;
125
- }
126
-
127
- export type CacheFetchHandler = (req: Request, ctx: any) => Promise<Response | undefined>;
128
-
129
- async function transformStream(
130
- data: Uint8Array,
131
- stream: { writable: WritableStream; readable: ReadableStream<Uint8Array> },
132
- ): Promise<Uint8Array> {
133
- const writer = stream.writable.getWriter();
134
- writer.write(data);
135
- writer.close();
136
- const chunks: Uint8Array[] = [];
137
- const reader = stream.readable.getReader();
138
- while (true) {
139
- const { done, value } = await reader.read();
140
- if (done) break;
141
- chunks.push(value);
142
- }
143
- const total = chunks.reduce((n, c) => n + c.length, 0);
144
- const out = new Uint8Array(total);
145
- let offset = 0;
146
- for (const c of chunks) { out.set(c, offset); offset += c.length; }
147
- return out;
148
- }
149
-
150
- // Use the Web Streams CompressionStream when available (Bun, Deno, Node ≥ 18).
151
- // Fall back to node:zlib on older Node versions.
152
- async function zlibGzip(d: Uint8Array): Promise<Uint8Array> {
153
- const zlib = await import('node:zlib');
154
- const { promisify } = await import('node:util');
155
- return promisify(zlib.gzip)(d) as Promise<Uint8Array>;
156
- }
157
- async function zlibGunzip(d: Uint8Array): Promise<Uint8Array> {
158
- const zlib = await import('node:zlib');
159
- const { promisify } = await import('node:util');
160
- return promisify(zlib.gunzip)(d) as Promise<Uint8Array>;
161
- }
162
-
163
- export const gzipCompress = (d: Uint8Array): Promise<Uint8Array> =>
164
- (globalThis as any).CompressionStream
165
- ? transformStream(d, new (globalThis as any).CompressionStream('gzip'))
166
- : zlibGzip(d);
167
-
168
- export const gzipDecompress = (d: Uint8Array): Promise<Uint8Array> =>
169
- (globalThis as any).DecompressionStream
170
- ? transformStream(d, new (globalThis as any).DecompressionStream('gzip'))
171
- : zlibGunzip(d);
172
-
173
- export async function buildCacheEntry(res: Response, ttl: number | undefined): Promise<CacheEntry> {
174
- const buf = await res.arrayBuffer();
175
- const body = await gzipCompress(new Uint8Array(buf));
176
- const headers: [string, string][] = [];
177
- res.headers.forEach((v, k) => {
178
- if (k.toLowerCase() !== 'content-encoding' && k.toLowerCase() !== 'content-length') {
179
- headers.push([k, v]);
180
- }
181
- });
182
- headers.push(['content-encoding', 'gzip']);
183
- return { body, status: res.status, headers, expiresAt: ttl != null ? Date.now() + ttl : null };
184
- }
185
-
186
- export async function serveFromEntry(entry: CacheEntry, req: Request): Promise<Response> {
187
- const accept = req.headers.get('Accept-Encoding') ?? '';
188
- if (accept.includes('gzip')) {
189
- return new Response(entry.body.buffer as ArrayBuffer, { status: entry.status, headers: entry.headers });
190
- }
191
- const plain = await gzipDecompress(entry.body);
192
- const headers = entry.headers.filter(([k]) => k.toLowerCase() !== 'content-encoding');
193
- return new Response(plain.buffer as ArrayBuffer, { status: entry.status, headers });
194
- }
195
-
196
- export function createRenderCache(
197
- opts: NonNullable<HadarsOptions['cache']>,
198
- handler: CacheFetchHandler,
199
- ): CacheFetchHandler {
200
- const store = new Map<string, CacheEntry>();
201
- const inFlight = new Map<string, Promise<CacheEntry | null>>();
202
-
203
- return async (req, ctx) => {
204
- const hadarsReq = parseRequest(req);
205
- const cacheOpts = await opts(hadarsReq);
206
- const key = cacheOpts?.key ?? null;
207
-
208
- if (key != null) {
209
- const entry = store.get(key);
210
- if (entry) {
211
- const expired = entry.expiresAt != null && Date.now() >= entry.expiresAt;
212
- if (!expired) return serveFromEntry(entry, req);
213
- store.delete(key);
214
- }
215
-
216
- let flight = inFlight.get(key);
217
- if (!flight) {
218
- const ttl = cacheOpts?.ttl;
219
- flight = handler(new Request(req), ctx)
220
- .then(async (res) => {
221
- if (!res || res.status < 200 || res.status >= 300 || res.headers.has('set-cookie')) {
222
- return null;
223
- }
224
- const newEntry = await buildCacheEntry(res, ttl);
225
- store.set(key, newEntry);
226
- return newEntry;
227
- })
228
- .catch(() => null)
229
- .finally(() => inFlight.delete(key));
230
- inFlight.set(key, flight);
231
- }
232
-
233
- const newEntry = await flight;
234
- if (newEntry) return serveFromEntry(newEntry, req);
235
- }
236
-
237
- return handler(req, ctx);
238
- };
239
- }
@@ -1,43 +0,0 @@
1
- import { readFile } from 'node:fs/promises';
2
-
3
- /** MIME type map keyed by lowercase file extension. */
4
- const MIME: Record<string, string> = {
5
- html: 'text/html; charset=utf-8',
6
- htm: 'text/html; charset=utf-8',
7
- css: 'text/css',
8
- js: 'application/javascript',
9
- mjs: 'application/javascript',
10
- cjs: 'application/javascript',
11
- json: 'application/json',
12
- map: 'application/json',
13
- png: 'image/png',
14
- jpg: 'image/jpeg',
15
- jpeg: 'image/jpeg',
16
- gif: 'image/gif',
17
- webp: 'image/webp',
18
- svg: 'image/svg+xml',
19
- ico: 'image/x-icon',
20
- woff: 'font/woff',
21
- woff2: 'font/woff2',
22
- ttf: 'font/ttf',
23
- otf: 'font/otf',
24
- txt: 'text/plain',
25
- xml: 'application/xml',
26
- pdf: 'application/pdf',
27
- };
28
-
29
- /**
30
- * Tries to serve a file at the given absolute path.
31
- * Returns a Response with the correct Content-Type, or null if the file does
32
- * not exist or cannot be read.
33
- */
34
- export async function tryServeFile(filePath: string): Promise<Response | null> {
35
- try {
36
- const data = await readFile(filePath);
37
- const ext = filePath.split('.').pop()?.toLowerCase() ?? '';
38
- const contentType = MIME[ext] ?? 'application/octet-stream';
39
- return new Response(data as BodyInit, { headers: { 'Content-Type': contentType } });
40
- } catch {
41
- return null;
42
- }
43
- }
@@ -1,11 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <meta name="HADARS_HEAD">
7
- </head>
8
- <body>
9
- <meta name="HADARS_BODY">
10
- </body>
11
- </html>
@@ -1,19 +0,0 @@
1
- import type { ServerContext } from './serve';
2
- import type { HadarsOptions, HadarsRequest } from "../types/hadars";
3
-
4
- type UpgradeHandle = (req: HadarsRequest, ctx: ServerContext) => boolean;
5
-
6
- export const upgradeHandler = (options: HadarsOptions): UpgradeHandle | null => {
7
- const { wsPath = '/ws' } = options;
8
-
9
- if (options.websocket) {
10
- return (req: HadarsRequest, ctx: ServerContext) => {
11
- if (req.pathname === wsPath) {
12
- return ctx.upgrade(req);
13
- }
14
- return false;
15
- };
16
- }
17
-
18
- return null;
19
- };