lovable-ssr 0.1.22 → 0.1.24

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/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import './globals.js';
2
- export type { RouteConfig, ComponentWithGetData, RouteDataParams, SitemapEntry, SitemapRouteConfig, SitemapChangefreq, } from './types.js';
3
- export { registerRoutes, getRoutes } from './registry.js';
2
+ export type { RouteConfig, ComponentWithGetData, RouteDataParams, SitemapEntry, SitemapRouteConfig, SitemapChangefreq, MiddlewareContext, MiddlewareFn, MiddlewareResponse, } from './types.js';
3
+ export { registerRoutes, getRoutes, registerMiddleware } from './registry.js';
4
4
  export { BrowserRouteDataProvider } from './components/BrowserRouteDataProvider.js';
5
5
  export { SEO, type SEOProps } from './components/SEO.js';
6
6
  export { default as RouterService } from './router/RouterService.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AACtB,YAAY,EACV,WAAW,EACX,oBAAoB,EACpB,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,wBAAwB,EAAE,MAAM,0CAA0C,CAAC;AACpF,OAAO,EAAE,GAAG,EAAE,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,KAAK,cAAc,EACnB,KAAK,iBAAiB,GACvB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EACL,MAAM,EACN,KAAK,YAAY,EACjB,KAAK,aAAa,GACnB,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AACtB,YAAY,EACV,WAAW,EACX,oBAAoB,EACpB,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAC9E,OAAO,EAAE,wBAAwB,EAAE,MAAM,0CAA0C,CAAC;AACpF,OAAO,EAAE,GAAG,EAAE,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,KAAK,cAAc,EACnB,KAAK,iBAAiB,GACvB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EACL,MAAM,EACN,KAAK,YAAY,EACjB,KAAK,aAAa,GACnB,MAAM,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import './globals.js';
2
- export { registerRoutes, getRoutes } from './registry.js';
2
+ export { registerRoutes, getRoutes, registerMiddleware } from './registry.js';
3
3
  export { BrowserRouteDataProvider } from './components/BrowserRouteDataProvider.js';
4
4
  export { SEO } from './components/SEO.js';
5
5
  export { default as RouterService } from './router/RouterService.js';
@@ -1,4 +1,6 @@
1
- import type { RouteConfig } from './types.js';
1
+ import type { MiddlewareFn, RouteConfig } from './types.js';
2
2
  export declare function registerRoutes(r: RouteConfig[]): void;
3
3
  export declare function getRoutes(): RouteConfig[];
4
+ export declare function registerMiddleware(m: MiddlewareFn): void;
5
+ export declare function getMiddleware(): MiddlewareFn | null;
4
6
  //# sourceMappingURL=registry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAI9C,wBAAgB,cAAc,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,IAAI,CAErD;AAED,wBAAgB,SAAS,IAAI,WAAW,EAAE,CAEzC"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAK5D,wBAAgB,cAAc,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,IAAI,CAErD;AAED,wBAAgB,SAAS,IAAI,WAAW,EAAE,CAEzC;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,YAAY,GAAG,IAAI,CAExD;AAED,wBAAgB,aAAa,IAAI,YAAY,GAAG,IAAI,CAEnD"}
package/dist/registry.js CHANGED
@@ -1,7 +1,14 @@
1
1
  let routes = [];
2
+ let middleware = null;
2
3
  export function registerRoutes(r) {
3
4
  routes = r;
4
5
  }
5
6
  export function getRoutes() {
6
7
  return routes;
7
8
  }
9
+ export function registerMiddleware(m) {
10
+ middleware = m;
11
+ }
12
+ export function getMiddleware() {
13
+ return middleware;
14
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"buildHeadHtml.d.ts","sourceRoot":"","sources":["../../src/ssr/buildHeadHtml.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAW5D,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,GAAG;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,CAwBA"}
1
+ {"version":3,"file":"buildHeadHtml.d.ts","sourceRoot":"","sources":["../../src/ssr/buildHeadHtml.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAG5D,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,GAAG;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,CAwBA"}
@@ -1,11 +1,4 @@
1
- function escapeHtml(s) {
2
- return s
3
- .replace(/&/g, '&')
4
- .replace(/</g, '&lt;')
5
- .replace(/>/g, '&gt;')
6
- .replace(/"/g, '&quot;')
7
- .replace(/'/g, '&#39;');
8
- }
1
+ import { escapeHtml } from '../utils/escapeHtml.js';
9
2
  export function buildHeadHtmlFromSEO(props) {
10
3
  const title = `<title>${escapeHtml(props.title)}</title>`;
11
4
  const metaTags = [
@@ -1,4 +1,5 @@
1
1
  import { type Express } from 'express';
2
+ import type { MiddlewareFn } from '../types.js';
2
3
  export interface RenderResult {
3
4
  html: string;
4
5
  preloadedData: Record<string, unknown>;
@@ -22,6 +23,8 @@ export interface CreateServerConfig {
22
23
  sitemap?: {
23
24
  siteUrl: string;
24
25
  };
26
+ /** Middleware executed before rendering each route. If it returns a response, the route is not rendered. */
27
+ middleware?: MiddlewareFn;
25
28
  }
26
29
  export declare function createServer(config: CreateServerConfig): Promise<{
27
30
  getApp: () => Express;
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/ssr/server.ts"],"names":[],"mappings":"AAGA,OAAgB,EAAE,KAAK,OAAO,EAAkD,MAAM,SAAS,CAAC;AAOhG,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CACxE;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,yFAAyF;IACzF,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wGAAwG;IACxG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACrC,uGAAuG;IACvG,OAAO,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/B;AAmVD,wBAAsB,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC;IACtE,MAAM,EAAE,MAAM,OAAO,CAAC;IACtB,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;CACxD,CAAC,CAOD;AAED;qDACqD;AACrD,wBAAgB,SAAS,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,QAO7D"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/ssr/server.ts"],"names":[],"mappings":"AAGA,OAAgB,EAAE,KAAK,OAAO,EAAkD,MAAM,SAAS,CAAC;AAKhG,OAAO,KAAK,EAAqB,YAAY,EAAoD,MAAM,aAAa,CAAC;AAGrH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CACxE;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,yFAAyF;IACzF,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wGAAwG;IACxG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACrC,uGAAuG;IACvG,OAAO,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9B,4GAA4G;IAC5G,UAAU,CAAC,EAAE,YAAY,CAAC;CAC3B;AA2TD,wBAAsB,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC;IACtE,MAAM,EAAE,MAAM,OAAO,CAAC;IACtB,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;CACxD,CAAC,CAOD;AAED;qDACqD;AACrD,wBAAgB,SAAS,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,QAO7D"}
@@ -3,8 +3,10 @@ import path from 'node:path';
3
3
  import { pathToFileURL } from 'node:url';
4
4
  import express from 'express';
5
5
  import { createServer as createViteServer } from 'vite';
6
- import { getRoutes } from '../registry.js';
6
+ import { getMiddleware, getRoutes } from '../registry.js';
7
7
  import RouterService from '../router/RouterService.js';
8
+ import { buildRequestContext } from '../types.js';
9
+ import { escapeHtml } from '../utils/escapeHtml.js';
8
10
  function defaultDistEntryPath(entryPath) {
9
11
  return entryPath
10
12
  .replace(/^src\//, 'dist/')
@@ -16,15 +18,6 @@ class SsrServer {
16
18
  config;
17
19
  isProd;
18
20
  _rendererCache;
19
- /** On-demand SSR cache: key = pathname + normalized search params, value = render result. Only used in production. */
20
- _ssrCache = new Map();
21
- normalizeCacheKey(url) {
22
- const u = new URL(url, 'http://localhost');
23
- const search = new URLSearchParams(u.search);
24
- const sorted = new URLSearchParams([...search.entries()].sort((a, b) => a[0].localeCompare(b[0])));
25
- const q = sorted.toString();
26
- return (u.pathname || '/') + (q ? `?${q}` : '');
27
- }
28
21
  constructor(config) {
29
22
  this.config = {
30
23
  root: path.resolve(config.root),
@@ -32,6 +25,7 @@ class SsrServer {
32
25
  port: config.port ?? 5173,
33
26
  cssLinkInDev: config.cssLinkInDev ?? '<link rel="stylesheet" href="/src/index.css"></head>',
34
27
  extraRoutes: config.extraRoutes ?? (() => { }),
28
+ middleware: config.middleware ?? null,
35
29
  sitemap: config.sitemap ?? undefined,
36
30
  };
37
31
  this.isProd = process.env.NODE_ENV === 'production';
@@ -122,28 +116,52 @@ Sitemap: ${baseUrl}/sitemap.xml`);
122
116
  });
123
117
  }
124
118
  buildSitemapXml(entries) {
125
- const lines = entries.map((e) => ` <url><loc>${this.escapeXml(e.loc)}</loc><lastmod>${e.lastmod ?? ''}</lastmod><changefreq>${e.changefreq ?? 'weekly'}</changefreq><priority>${e.priority ?? 0.5}</priority></url>`);
119
+ const lines = entries.map((e) => ` <url><loc>${escapeHtml(e.loc)}</loc><lastmod>${e.lastmod ?? ''}</lastmod><changefreq>${e.changefreq ?? 'weekly'}</changefreq><priority>${e.priority ?? 0.5}</priority></url>`);
126
120
  return `<?xml version="1.0" encoding="UTF-8"?>
127
121
  <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
128
122
  ${lines.join('\n')}
129
123
  </urlset>`;
130
124
  }
131
- escapeXml(str) {
132
- return str
133
- .replace(/&/g, '&amp;')
134
- .replace(/</g, '&lt;')
135
- .replace(/>/g, '&gt;')
136
- .replace(/"/g, '&quot;')
137
- .replace(/'/g, '&apos;');
125
+ async runMiddleware(ctx) {
126
+ const mw = this.config.middleware ?? getMiddleware();
127
+ if (!mw)
128
+ return;
129
+ return await mw(ctx);
130
+ }
131
+ sendMiddlewareResponse(res, mwRes) {
132
+ if (mwRes.headers) {
133
+ for (const [key, value] of Object.entries(mwRes.headers)) {
134
+ res.setHeader(key, value);
135
+ }
136
+ }
137
+ if (mwRes.redirect) {
138
+ res.redirect(mwRes.status ?? 302, mwRes.redirect);
139
+ return;
140
+ }
141
+ res.status(mwRes.status ?? 200).send(mwRes.body ?? '');
138
142
  }
139
143
  async handleRequest(req, res, next) {
140
144
  const url = req.originalUrl;
141
145
  const pathname = url.replace(/\?.*$/, '').replace(/#.*$/, '') || '/';
142
146
  try {
147
+ const matchedRoute = RouterService.matchRoute(pathname);
148
+ const params = matchedRoute
149
+ ? RouterService.routeParams(matchedRoute.path, pathname)
150
+ : { routeParams: {}, searchParams: {} };
151
+ const requestContext = buildRequestContext(req);
152
+ const mwResult = await this.runMiddleware({
153
+ request: requestContext,
154
+ route: matchedRoute,
155
+ pathname,
156
+ params,
157
+ });
158
+ if (mwResult) {
159
+ return this.sendMiddlewareResponse(res, mwResult);
160
+ }
143
161
  if (!RouterService.isSsrRoute(pathname)) {
144
162
  return await this.renderSpa(url, res);
145
163
  }
146
- return await this.renderSsr(url, res, req);
164
+ return await this.renderSsr(url, res, requestContext);
147
165
  }
148
166
  catch (e) {
149
167
  if (this.vite) {
@@ -162,68 +180,20 @@ ${lines.join('\n')}
162
180
  const template = this.readTemplate(path.join(this.config.root, 'dist', 'index.html'));
163
181
  return res.status(200).set({ 'Content-Type': 'text/html' }).send(template);
164
182
  }
165
- async renderSsr(url, res, req) {
183
+ async resolveRenderResult(url, render, requestContext) {
184
+ const result = await render(url, { requestContext });
185
+ return {
186
+ appHtml: typeof result.html === 'string' ? result.html : '',
187
+ preloadedData: result.preloadedData ?? {},
188
+ helmet: result.helmet,
189
+ };
190
+ }
191
+ async renderSsr(url, res, requestContext) {
166
192
  const { template, render } = await this.getSsrRenderer();
167
193
  if (process.env.NODE_ENV !== 'production' && process.env.LOVABLE_SSR_DEBUG) {
168
194
  console.log('[lovable-ssr] render(url)', url);
169
195
  }
170
- let appHtml;
171
- let preloadedData;
172
- let helmet;
173
- // Constrói um contexto simples de request (cookies raw + headers) para o getData.
174
- const cookiesRaw = req.headers.cookie ?? '';
175
- const cookies = {};
176
- if (cookiesRaw) {
177
- cookiesRaw.split(';').forEach((part) => {
178
- const [k, ...rest] = part.split('=');
179
- if (!k)
180
- return;
181
- const key = k.trim();
182
- if (!key)
183
- return;
184
- const value = rest.join('=').trim();
185
- cookies[key] = decodeURIComponent(value);
186
- });
187
- }
188
- const requestContext = {
189
- cookiesRaw,
190
- cookies,
191
- headers: req.headers,
192
- method: req.method,
193
- url: req.originalUrl,
194
- };
195
- if (this.isProd) {
196
- const cacheKey = this.normalizeCacheKey(url);
197
- const hasCookies = !!cookiesRaw;
198
- // Evita cachear respostas personalizadas por cookies (ex.: auth).
199
- if (!hasCookies) {
200
- const cached = this._ssrCache.get(cacheKey);
201
- if (cached) {
202
- appHtml = cached.html;
203
- preloadedData = cached.preloadedData;
204
- helmet = cached.helmet;
205
- }
206
- else {
207
- const result = await render(url, { requestContext });
208
- appHtml = typeof result.html === 'string' ? result.html : '';
209
- preloadedData = result.preloadedData ?? {};
210
- helmet = result.helmet;
211
- this._ssrCache.set(cacheKey, { html: appHtml, preloadedData, helmet });
212
- }
213
- }
214
- else {
215
- const result = await render(url, { requestContext });
216
- appHtml = typeof result.html === 'string' ? result.html : '';
217
- preloadedData = result.preloadedData ?? {};
218
- helmet = result.helmet;
219
- }
220
- }
221
- else {
222
- const result = await render(url, { requestContext });
223
- appHtml = typeof result.html === 'string' ? result.html : '';
224
- preloadedData = result.preloadedData ?? {};
225
- helmet = result.helmet;
226
- }
196
+ const { appHtml, preloadedData, helmet } = await this.resolveRenderResult(url, render, requestContext);
227
197
  let html = template.replace('<div id="root"></div>', `<div id="root">${appHtml}</div>`);
228
198
  html = this.injectHelmet(html, helmet);
229
199
  html = this.injectPreloadedData(html, preloadedData);
package/dist/types.d.ts CHANGED
@@ -32,9 +32,28 @@ export type RequestContext = {
32
32
  method: string;
33
33
  url: string;
34
34
  };
35
+ export declare function parseCookies(raw: string): Record<string, string>;
36
+ export declare function buildRequestContext(req: {
37
+ headers: IncomingHttpHeaders;
38
+ method: string;
39
+ originalUrl: string;
40
+ }): RequestContext;
35
41
  export type RouteDataParams = {
36
42
  routeParams: Record<string, string>;
37
43
  searchParams: Record<string, string>;
38
44
  request?: RequestContext;
39
45
  };
46
+ export type MiddlewareContext = {
47
+ request: RequestContext;
48
+ route: RouteConfig | undefined;
49
+ pathname: string;
50
+ params: Omit<RouteDataParams, 'request'>;
51
+ };
52
+ export type MiddlewareResponse = {
53
+ redirect?: string;
54
+ status?: number;
55
+ headers?: Record<string, string>;
56
+ body?: string;
57
+ };
58
+ export type MiddlewareFn = (ctx: MiddlewareContext) => Promise<MiddlewareResponse | void> | MiddlewareResponse | void;
40
59
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,MAAM,oBAAoB,GAAG,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG;IAC5D,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAC1E,CAAC;AAEF,MAAM,MAAM,iBAAiB,GACzB,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,OAAO,CAAC;AAEZ,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;CACpE,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,oBAAoB,CAAC;IAChC,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,kBAAkB,CAAC;CAC9B,CAAC;AACF,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,OAAO,EAAE,mBAAmB,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,MAAM,oBAAoB,GAAG,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG;IAC5D,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAC1E,CAAC;AAEF,MAAM,MAAM,iBAAiB,GACzB,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,OAAO,CAAC;AAEZ,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;CACpE,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,oBAAoB,CAAC;IAChC,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,kBAAkB,CAAC;CAC9B,CAAC;AACF,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,OAAO,EAAE,mBAAmB,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAWhE;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE;IACvC,OAAO,EAAE,mBAAmB,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,cAAc,CASjB;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,cAAc,CAAC;IACxB,KAAK,EAAE,WAAW,GAAG,SAAS,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,CACzB,GAAG,EAAE,iBAAiB,KACnB,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,GAAG,kBAAkB,GAAG,IAAI,CAAC"}
package/dist/types.js CHANGED
@@ -1 +1,25 @@
1
- export {};
1
+ export function parseCookies(raw) {
2
+ const cookies = {};
3
+ if (!raw)
4
+ return cookies;
5
+ raw.split(';').forEach((part) => {
6
+ const [k, ...rest] = part.split('=');
7
+ if (!k)
8
+ return;
9
+ const key = k.trim();
10
+ if (!key)
11
+ return;
12
+ cookies[key] = decodeURIComponent(rest.join('=').trim());
13
+ });
14
+ return cookies;
15
+ }
16
+ export function buildRequestContext(req) {
17
+ const cookiesRaw = req.headers.cookie ?? '';
18
+ return {
19
+ cookiesRaw,
20
+ cookies: parseCookies(cookiesRaw),
21
+ headers: req.headers,
22
+ method: req.method,
23
+ url: req.originalUrl,
24
+ };
25
+ }
@@ -0,0 +1,2 @@
1
+ export declare function escapeHtml(s: string): string;
2
+ //# sourceMappingURL=escapeHtml.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"escapeHtml.d.ts","sourceRoot":"","sources":["../../src/utils/escapeHtml.ts"],"names":[],"mappings":"AAAA,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAO5C"}
@@ -0,0 +1,8 @@
1
+ export function escapeHtml(s) {
2
+ return s
3
+ .replace(/&/g, '&amp;')
4
+ .replace(/</g, '&lt;')
5
+ .replace(/>/g, '&gt;')
6
+ .replace(/"/g, '&quot;')
7
+ .replace(/'/g, '&#39;');
8
+ }
package/package.json CHANGED
@@ -1,66 +1,72 @@
1
- {
2
- "name": "lovable-ssr",
3
- "version": "0.1.22",
4
- "description": "SSR and route data engine for Lovable projects",
5
- "license": "MIT",
6
- "keywords": [
7
- "ssr",
8
- "react",
9
- "vite",
10
- "express",
11
- "lovable",
12
- "getData",
13
- "route-registry",
14
- "server-side-rendering"
15
- ],
16
- "type": "module",
17
- "main": "./dist/index.js",
18
- "module": "./dist/index.js",
19
- "types": "./dist/index.d.ts",
20
- "exports": {
21
- ".": {
22
- "import": "./dist/index.js",
23
- "types": "./dist/index.d.ts"
24
- },
25
- "./server": {
26
- "import": "./dist/ssr/server.js",
27
- "types": "./dist/ssr/server.d.ts"
28
- }
29
- },
30
- "files": [
31
- "dist"
32
- ],
33
- "scripts": {
34
- "build": "tsc && npm run build:docs",
35
- "dev": "tsc --watch",
36
- "changelog": "standard-version --message \"[skip ci]\"",
37
- "changelog:rc": "npm run changelog -- --prerelease rc --skip.changelog --skip.tag",
38
- "changelog:patch": "npm run changelog -- --release-as patch --prerelease rc",
39
- "changelog:minor": "npm run changelog -- --release-as minor --prerelease rc",
40
- "changelog:major": "npm run changelog -- --release-as major --prerelease rc",
41
- "build:docs": "cd doc && npm run docs:build"
42
- },
43
- "peerDependencies": {
44
- "react": "^18.0.0",
45
- "react-dom": "^18.0.0",
46
- "react-router-dom": "^6.0.0"
47
- },
48
- "dependencies": {
49
- "express": "^4.21.0"
50
- },
51
- "devDependencies": {
52
- "@types/express": "^4.17.21",
53
- "@types/node": "^22.16.5",
54
- "@types/react": "^18.3.23",
55
- "@types/react-dom": "^18.3.7",
56
- "react": "^18.3.1",
57
- "react-dom": "^18.3.1",
58
- "react-router-dom": "^6.30.1",
59
- "standard-version": "^9.5.0",
60
- "typescript": "^5.8.3",
61
- "vite": "^5.4.19"
62
- },
63
- "engines": {
64
- "node": ">=18"
65
- }
66
- }
1
+ {
2
+ "name": "lovable-ssr",
3
+ "version": "0.1.24",
4
+ "description": "SSR and route data engine for Lovable projects",
5
+ "license": "MIT",
6
+ "keywords": [
7
+ "ssr",
8
+ "react",
9
+ "vite",
10
+ "express",
11
+ "lovable",
12
+ "getData",
13
+ "route-registry",
14
+ "server-side-rendering"
15
+ ],
16
+ "type": "module",
17
+ "main": "./dist/index.js",
18
+ "module": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "import": "./dist/index.js",
23
+ "types": "./dist/index.d.ts"
24
+ },
25
+ "./server": {
26
+ "import": "./dist/ssr/server.js",
27
+ "types": "./dist/ssr/server.d.ts"
28
+ }
29
+ },
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "scripts": {
34
+ "build": "tsc && npm run build:docs",
35
+ "dev": "tsc --watch",
36
+ "changelog": "standard-version --message \"[skip ci]\"",
37
+ "changelog:rc": "npm run changelog -- --prerelease rc --skip.changelog --skip.tag",
38
+ "changelog:patch": "npm run changelog -- --release-as patch --prerelease rc",
39
+ "changelog:minor": "npm run changelog -- --release-as minor --prerelease rc",
40
+ "changelog:major": "npm run changelog -- --release-as major --prerelease rc",
41
+ "test": "vitest run",
42
+ "test:watch": "vitest",
43
+ "test:e2e": "playwright test",
44
+ "build:docs": "cd doc && npm run docs:build"
45
+ },
46
+ "peerDependencies": {
47
+ "react": "^18.0.0",
48
+ "react-dom": "^18.0.0",
49
+ "react-router-dom": "^6.0.0"
50
+ },
51
+ "dependencies": {
52
+ "express": "^4.21.0"
53
+ },
54
+ "devDependencies": {
55
+ "@playwright/test": "^1.59.1",
56
+ "@types/express": "^4.17.21",
57
+ "@types/node": "^22.16.5",
58
+ "@types/react": "^18.3.23",
59
+ "@types/react-dom": "^18.3.7",
60
+ "happy-dom": "^20.8.9",
61
+ "react": "^18.3.1",
62
+ "react-dom": "^18.3.1",
63
+ "react-router-dom": "^6.30.1",
64
+ "standard-version": "^9.5.0",
65
+ "typescript": "^5.8.3",
66
+ "vite": "^5.4.19",
67
+ "vitest": "^4.1.4"
68
+ },
69
+ "engines": {
70
+ "node": ">=18"
71
+ }
72
+ }