@getstrata/core 2.2.12 → 2.2.14
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/CHANGELOG.md +10 -0
- package/dist/core/http/index.d.ts +2 -2
- package/dist/core/http/route.d.ts +64 -0
- package/dist/core/view/index.d.ts +4 -2
- package/dist/core/view/viewRenderer.d.ts +30 -0
- package/dist/core/view/webLayoutData.d.ts +3 -1
- package/dist/entries/http/corsMiddleware.js +10 -1
- package/dist/entries/http/route.js +45 -1
- package/dist/framework/public-api.d.ts +4 -3
- package/dist/index.js +136 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @getstrata/core changelog
|
|
2
2
|
|
|
3
|
+
## 2.2.14
|
|
4
|
+
|
|
5
|
+
Complete the scoped HTML rendering starter adoption: generated apps inject ViewRenderer through createViewProvider instead of copying renderPage; welcome pages opt into HTMX fragments with full-page fallbacks, auth retains existing contracts, and CORS preserves representation Vary headers. Includes generator, runtime auth and cache-header regressions.
|
|
6
|
+
|
|
7
|
+
Add optional typed resourceRoutes and strict mergeRouteMaps composition, preserving native Bun routing and app-owned middleware. Add explicit ViewRenderer.namespace prefixes with unchanged layout/Response/HTMX contracts; generated auth controllers use the namespace. Includes native HTTP, compile-time, rendering isolation and generator regressions.
|
|
8
|
+
|
|
9
|
+
## 2.2.13
|
|
10
|
+
|
|
11
|
+
- Improve view rendering and better htmx support
|
|
12
|
+
|
|
3
13
|
## 2.2.12
|
|
4
14
|
|
|
5
15
|
- Require Bun >=1.4.3; align CI, generated/existing example runtime pins and Docker stages to 1.4.3, with bun-types >=1.4.3 behind the official @types/bun shim.
|
|
@@ -14,8 +14,8 @@ export { parseMultipartUpload, sanitizeUploadFileName, validateUploadFile, } fro
|
|
|
14
14
|
export { createRequireAuthMiddleware } from "./requireAuthMiddleware";
|
|
15
15
|
export { serializeDate, toPaginatedResourceCollection, toResourceCollection } from "./resources";
|
|
16
16
|
export { createdResponse, errorResponse, jsonResponse, noContentResponse, withErrorHandling, } from "./response";
|
|
17
|
-
export type { RouteRequest } from "./route";
|
|
18
|
-
export { getRouteParams } from "./route";
|
|
17
|
+
export type { ResourceActions, ResourceMemberHandler, ResourceRouteMap, RouteRequest, } from "./route";
|
|
18
|
+
export { getRouteParams, mergeRouteMaps, resourceRoutes } from "./route";
|
|
19
19
|
export { withMiddleware } from "./routeMiddleware";
|
|
20
20
|
export { bindRouteModel } from "./routeModelBinding";
|
|
21
21
|
export { securedBindRouteModel, securedBindRouteModelByKey, } from "./securedRouteModelBinding";
|
|
@@ -1,3 +1,67 @@
|
|
|
1
|
+
import type { RouteHandler } from "./middleware";
|
|
2
|
+
type ResourceMemberHandler = RouteHandler<RouteRequest<{
|
|
3
|
+
id: string;
|
|
4
|
+
}>>;
|
|
5
|
+
interface ResourceActions {
|
|
6
|
+
index?: RouteHandler;
|
|
7
|
+
create?: RouteHandler;
|
|
8
|
+
store?: RouteHandler;
|
|
9
|
+
show?: ResourceMemberHandler;
|
|
10
|
+
edit?: ResourceMemberHandler;
|
|
11
|
+
update?: ResourceMemberHandler;
|
|
12
|
+
destroy?: ResourceMemberHandler;
|
|
13
|
+
}
|
|
14
|
+
type ResourceRouteMap<P extends string, A extends ResourceActions> = (A extends {
|
|
15
|
+
index: infer H;
|
|
16
|
+
} ? {
|
|
17
|
+
[K in P]: {
|
|
18
|
+
GET: H;
|
|
19
|
+
};
|
|
20
|
+
} : Record<never, never>) & (A extends {
|
|
21
|
+
store: infer H;
|
|
22
|
+
} ? {
|
|
23
|
+
[K in P]: {
|
|
24
|
+
POST: H;
|
|
25
|
+
};
|
|
26
|
+
} : Record<never, never>) & (A extends {
|
|
27
|
+
create: infer H;
|
|
28
|
+
} ? {
|
|
29
|
+
[K in `${P}/create`]: {
|
|
30
|
+
GET: H;
|
|
31
|
+
};
|
|
32
|
+
} : Record<never, never>) & (A extends {
|
|
33
|
+
show: infer H;
|
|
34
|
+
} ? {
|
|
35
|
+
[K in `${P}/:id`]: {
|
|
36
|
+
GET: H;
|
|
37
|
+
};
|
|
38
|
+
} : Record<never, never>) & (A extends {
|
|
39
|
+
edit: infer H;
|
|
40
|
+
} ? {
|
|
41
|
+
[K in `${P}/:id/edit`]: {
|
|
42
|
+
GET: H;
|
|
43
|
+
};
|
|
44
|
+
} : Record<never, never>) & (A extends {
|
|
45
|
+
update: infer H;
|
|
46
|
+
} ? {
|
|
47
|
+
[K in `${P}/:id`]: {
|
|
48
|
+
PUT: H;
|
|
49
|
+
PATCH: H;
|
|
50
|
+
};
|
|
51
|
+
} : Record<never, never>) & (A extends {
|
|
52
|
+
destroy: infer H;
|
|
53
|
+
} ? {
|
|
54
|
+
[K in `${P}/:id`]: {
|
|
55
|
+
DELETE: H;
|
|
56
|
+
};
|
|
57
|
+
} : Record<never, never>);
|
|
58
|
+
/** Expand only explicitly supplied actions into native Bun routes. */
|
|
59
|
+
declare function resourceRoutes<const P extends string, const A extends ResourceActions>(path: P, actions: A & Record<Exclude<keyof A, keyof ResourceActions>, never>): ResourceRouteMap<P, A>;
|
|
60
|
+
type Intersection<U> = (U extends unknown ? (value: U) => void : never) extends (value: infer I) => void ? I : never;
|
|
61
|
+
/** Strict composition: duplicate paths fail rather than silently replacing handlers. */
|
|
62
|
+
declare function mergeRouteMaps<const T extends readonly Record<string, unknown>[]>(...maps: T): Intersection<T[number]>;
|
|
63
|
+
export type { ResourceActions, ResourceMemberHandler, ResourceRouteMap };
|
|
64
|
+
export { mergeRouteMaps, resourceRoutes };
|
|
1
65
|
type RouteRequest<TParams extends Record<string, string>> = Request & {
|
|
2
66
|
params: TParams;
|
|
3
67
|
};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
export type { LayoutDataResolver } from "./etaViewEngine";
|
|
1
|
+
export type { LayoutDataResolver, RenderOptions } from "./etaViewEngine";
|
|
2
2
|
export { DEFAULT_VIEWS_DIRECTORY, EtaViewEngine } from "./etaViewEngine";
|
|
3
3
|
export { htmlResponse, isHtmxRequest, redirectResponse, rssResponse, textResponse, xmlResponse, } from "./htmlResponse";
|
|
4
4
|
export type { ViewEngine } from "./viewEngine";
|
|
5
|
+
export type { HtmxViewResponseOptions, NamespacedViewRenderer, ViewResponseOptions, } from "./viewRenderer";
|
|
6
|
+
export { CORE_VIEW_RENDERER_TOKEN, ViewRenderer } from "./viewRenderer";
|
|
5
7
|
export type { WebErrorViewInput, WebErrorViewOptions } from "./webErrorView";
|
|
6
8
|
export { configureWebErrorView, errorTemplateName, htmlErrorResponse, notFoundHtmlResponse, renderKernelErrorChrome, renderWebErrorHtml, } from "./webErrorView";
|
|
7
9
|
export type { WebLayoutAuthUser, WebLayoutData, WebLayoutDataOptions, WebLayoutUserKey, } from "./webLayoutData";
|
|
8
|
-
export { configureWebLayoutData, resolveWebLayoutData } from "./webLayoutData";
|
|
10
|
+
export { configureWebLayoutData, createWebLayoutDataResolver, resolveWebLayoutData, } from "./webLayoutData";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ViewEngine } from "./viewEngine";
|
|
2
|
+
interface ViewResponseOptions {
|
|
3
|
+
layout?: string | false;
|
|
4
|
+
request?: Request;
|
|
5
|
+
status?: number;
|
|
6
|
+
statusText?: string;
|
|
7
|
+
headers?: ConstructorParameters<typeof Headers>[0];
|
|
8
|
+
}
|
|
9
|
+
interface HtmxViewResponseOptions extends ViewResponseOptions {
|
|
10
|
+
/** Optional fragment template; otherwise render the page template without its layout. */
|
|
11
|
+
fragment?: string;
|
|
12
|
+
}
|
|
13
|
+
interface NamespacedViewRenderer {
|
|
14
|
+
render: ViewRenderer["render"];
|
|
15
|
+
renderHtmx: ViewRenderer["renderHtmx"];
|
|
16
|
+
}
|
|
17
|
+
/** App-owned renderer; request data stays in the engine's async resolver. */
|
|
18
|
+
declare class ViewRenderer {
|
|
19
|
+
private readonly engine;
|
|
20
|
+
private readonly defaultLayout?;
|
|
21
|
+
constructor(engine: ViewEngine, defaultLayout?: string | false | undefined);
|
|
22
|
+
/** Explicit template prefix; layout names and response contracts remain unchanged. */
|
|
23
|
+
namespace(prefix: string): NamespacedViewRenderer;
|
|
24
|
+
render(name: string, data?: Record<string, unknown>, options?: ViewResponseOptions): Promise<Response>;
|
|
25
|
+
/** Opt-in representation selection; auth/CSRF/tenancy remain the route's responsibility. */
|
|
26
|
+
renderHtmx(name: string, data?: Record<string, unknown>, options?: HtmxViewResponseOptions): Promise<Response>;
|
|
27
|
+
}
|
|
28
|
+
declare const CORE_VIEW_RENDERER_TOKEN: import("../contracts/container").ServiceToken<ViewRenderer>;
|
|
29
|
+
export type { HtmxViewResponseOptions, NamespacedViewRenderer, ViewResponseOptions };
|
|
30
|
+
export { CORE_VIEW_RENDERER_TOKEN, ViewRenderer };
|
|
@@ -24,5 +24,7 @@ interface WebLayoutDataOptions {
|
|
|
24
24
|
declare function configureWebLayoutData(options: WebLayoutDataOptions): void;
|
|
25
25
|
declare function resetWebLayoutDataConfigForTests(): void;
|
|
26
26
|
declare function resolveWebLayoutData(container: ServiceContainerLike, request?: Request, options?: WebLayoutDataOptions): Promise<Record<string, unknown>>;
|
|
27
|
+
/** Isolated configuration for one app; does not read legacy global defaults. */
|
|
28
|
+
declare function createWebLayoutDataResolver(container: ServiceContainerLike, options?: WebLayoutDataOptions): (request?: Request) => Promise<Record<string, unknown>>;
|
|
27
29
|
export type { WebLayoutAuthUser, WebLayoutData, WebLayoutDataOptions, WebLayoutUserKey };
|
|
28
|
-
export { configureWebLayoutData, resetWebLayoutDataConfigForTests, resolveWebLayoutData };
|
|
30
|
+
export { configureWebLayoutData, createWebLayoutDataResolver, resetWebLayoutDataConfigForTests, resolveWebLayoutData, };
|
|
@@ -141,7 +141,16 @@ function createCorsMiddleware(options = {}) {
|
|
|
141
141
|
const response = await next();
|
|
142
142
|
const headers = new Headers(response.headers);
|
|
143
143
|
for (const [key, value] of buildCorsHeaders(request, captured)) {
|
|
144
|
-
|
|
144
|
+
if (key === "vary") {
|
|
145
|
+
const fields = (headers.get("Vary") ?? "").split(",").map((field) => field.trim()).filter(Boolean);
|
|
146
|
+
if (fields.includes("*"))
|
|
147
|
+
continue;
|
|
148
|
+
if (!fields.some((field) => field.toLowerCase() === "origin"))
|
|
149
|
+
fields.push(value);
|
|
150
|
+
headers.set("Vary", fields.join(", "));
|
|
151
|
+
} else {
|
|
152
|
+
headers.set(key, value);
|
|
153
|
+
}
|
|
145
154
|
}
|
|
146
155
|
return new Response(response.body, {
|
|
147
156
|
status: response.status,
|
|
@@ -1,8 +1,52 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// ../../src/core/http/route.ts
|
|
3
|
+
function resourceRoutes(path, actions) {
|
|
4
|
+
if (!/^\/[A-Za-z0-9._~-]+(?:\/[A-Za-z0-9._~-]+)*$/.test(path) || path.split("/").some((part) => part === "." || part === "..")) {
|
|
5
|
+
throw new TypeError("Resource path must be a static absolute path without a trailing slash.");
|
|
6
|
+
}
|
|
7
|
+
const routes = {};
|
|
8
|
+
const definitions = {
|
|
9
|
+
index: [path, ["GET"]],
|
|
10
|
+
create: [`${path}/create`, ["GET"]],
|
|
11
|
+
store: [path, ["POST"]],
|
|
12
|
+
show: [`${path}/:id`, ["GET"]],
|
|
13
|
+
edit: [`${path}/:id/edit`, ["GET"]],
|
|
14
|
+
update: [`${path}/:id`, ["PUT", "PATCH"]],
|
|
15
|
+
destroy: [`${path}/:id`, ["DELETE"]]
|
|
16
|
+
};
|
|
17
|
+
for (const [action, handler] of Object.entries(actions)) {
|
|
18
|
+
if (!Object.hasOwn(definitions, action) || typeof handler !== "function") {
|
|
19
|
+
throw new TypeError(`Invalid resource action: ${action}`);
|
|
20
|
+
}
|
|
21
|
+
const [route, methods] = definitions[action];
|
|
22
|
+
const methodMap = routes[route] ?? {};
|
|
23
|
+
routes[route] = methodMap;
|
|
24
|
+
for (const method of methods)
|
|
25
|
+
methodMap[method] = handler;
|
|
26
|
+
}
|
|
27
|
+
return routes;
|
|
28
|
+
}
|
|
29
|
+
function mergeRouteMaps(...maps) {
|
|
30
|
+
const routes = {};
|
|
31
|
+
for (const map of maps) {
|
|
32
|
+
for (const [path, handler] of Object.entries(map)) {
|
|
33
|
+
if (Object.hasOwn(routes, path))
|
|
34
|
+
throw new Error(`Duplicate route path: ${path}`);
|
|
35
|
+
Object.defineProperty(routes, path, {
|
|
36
|
+
value: handler,
|
|
37
|
+
enumerable: true,
|
|
38
|
+
writable: true,
|
|
39
|
+
configurable: true
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return routes;
|
|
44
|
+
}
|
|
3
45
|
function getRouteParams(request) {
|
|
4
46
|
return request.params;
|
|
5
47
|
}
|
|
6
48
|
export {
|
|
7
|
-
getRouteParams
|
|
49
|
+
getRouteParams,
|
|
50
|
+
mergeRouteMaps,
|
|
51
|
+
resourceRoutes
|
|
8
52
|
};
|
|
@@ -107,8 +107,8 @@ export { createRequireGlobalAdminMiddleware } from "../core/http/requireGlobalAd
|
|
|
107
107
|
export { createRequireWebAuthMiddleware } from "../core/http/requireWebAuthMiddleware.ts";
|
|
108
108
|
export { JsonResource, ResourceCollection, serializeDate, toPaginatedResourceCollection, toResourceCollection, whenLoaded, } from "../core/http/resources.ts";
|
|
109
109
|
export { createdResponse, errorResponse, jsonResponse, noContentResponse, withErrorHandling, } from "../core/http/response.ts";
|
|
110
|
-
export type { RouteRequest } from "../core/http/route.ts";
|
|
111
|
-
export { getRouteParams } from "../core/http/route.ts";
|
|
110
|
+
export type { ResourceActions, ResourceMemberHandler, ResourceRouteMap, RouteRequest, } from "../core/http/route.ts";
|
|
111
|
+
export { getRouteParams, mergeRouteMaps, resourceRoutes } from "../core/http/route.ts";
|
|
112
112
|
export { loginRedirectLocation, safeInternalRedirectPath, sanitizeInternalPath, } from "../core/http/safeInternalPath.ts";
|
|
113
113
|
export { createScimThrottleMiddleware } from "../core/http/scimThrottleMiddleware.ts";
|
|
114
114
|
export { createSecurityHeadersMiddleware } from "../core/http/securityHeadersMiddleware.ts";
|
|
@@ -169,6 +169,7 @@ export type { TracingOptions, TracingRuntime } from "../core/tracing/tracingMidd
|
|
|
169
169
|
export { acquireTracingRuntime, createTracingMiddleware, createTracingRuntime, getTracingRuntime, } from "../core/tracing/tracingMiddleware.ts";
|
|
170
170
|
export type { ValidationRule, ValidationSchema } from "../core/validation/rules.ts";
|
|
171
171
|
export { emailRule, maxLength, minLength, required, stringRule, validateObject, } from "../core/validation/rules.ts";
|
|
172
|
-
export { configureWebErrorView, configureWebLayoutData, DEFAULT_VIEWS_DIRECTORY, EtaViewEngine, errorTemplateName, htmlErrorResponse, htmlResponse, isHtmxRequest, notFoundHtmlResponse, redirectResponse, renderKernelErrorChrome, renderWebErrorHtml, resolveWebLayoutData, rssResponse, textResponse, xmlResponse, } from "../core/view/index.ts";
|
|
172
|
+
export { CORE_VIEW_RENDERER_TOKEN, configureWebErrorView, configureWebLayoutData, createWebLayoutDataResolver, DEFAULT_VIEWS_DIRECTORY, EtaViewEngine, errorTemplateName, htmlErrorResponse, htmlResponse, isHtmxRequest, notFoundHtmlResponse, redirectResponse, renderKernelErrorChrome, renderWebErrorHtml, resolveWebLayoutData, rssResponse, textResponse, ViewRenderer, xmlResponse, } from "../core/view/index.ts";
|
|
173
173
|
export type { ViewEngine } from "../core/view/viewEngine.ts";
|
|
174
|
+
export type { HtmxViewResponseOptions, NamespacedViewRenderer, ViewResponseOptions, } from "../core/view/viewRenderer.ts";
|
|
174
175
|
export type { WebLayoutAuthUser, WebLayoutData, WebLayoutDataOptions, WebLayoutUserKey, } from "../core/view/webLayoutData.ts";
|
package/dist/index.js
CHANGED
|
@@ -11352,7 +11352,16 @@ function createCorsMiddleware(options = {}) {
|
|
|
11352
11352
|
const response = await next();
|
|
11353
11353
|
const headers = new Headers(response.headers);
|
|
11354
11354
|
for (const [key, value] of buildCorsHeaders(request, captured)) {
|
|
11355
|
-
|
|
11355
|
+
if (key === "vary") {
|
|
11356
|
+
const fields = (headers.get("Vary") ?? "").split(",").map((field) => field.trim()).filter(Boolean);
|
|
11357
|
+
if (fields.includes("*"))
|
|
11358
|
+
continue;
|
|
11359
|
+
if (!fields.some((field) => field.toLowerCase() === "origin"))
|
|
11360
|
+
fields.push(value);
|
|
11361
|
+
headers.set("Vary", fields.join(", "));
|
|
11362
|
+
} else {
|
|
11363
|
+
headers.set(key, value);
|
|
11364
|
+
}
|
|
11356
11365
|
}
|
|
11357
11366
|
return new Response(response.body, {
|
|
11358
11367
|
status: response.status,
|
|
@@ -12390,6 +12399,48 @@ function withErrorHandling(handler) {
|
|
|
12390
12399
|
};
|
|
12391
12400
|
}
|
|
12392
12401
|
// ../../src/core/http/route.ts
|
|
12402
|
+
function resourceRoutes(path, actions) {
|
|
12403
|
+
if (!/^\/[A-Za-z0-9._~-]+(?:\/[A-Za-z0-9._~-]+)*$/.test(path) || path.split("/").some((part) => part === "." || part === "..")) {
|
|
12404
|
+
throw new TypeError("Resource path must be a static absolute path without a trailing slash.");
|
|
12405
|
+
}
|
|
12406
|
+
const routes = {};
|
|
12407
|
+
const definitions = {
|
|
12408
|
+
index: [path, ["GET"]],
|
|
12409
|
+
create: [`${path}/create`, ["GET"]],
|
|
12410
|
+
store: [path, ["POST"]],
|
|
12411
|
+
show: [`${path}/:id`, ["GET"]],
|
|
12412
|
+
edit: [`${path}/:id/edit`, ["GET"]],
|
|
12413
|
+
update: [`${path}/:id`, ["PUT", "PATCH"]],
|
|
12414
|
+
destroy: [`${path}/:id`, ["DELETE"]]
|
|
12415
|
+
};
|
|
12416
|
+
for (const [action, handler] of Object.entries(actions)) {
|
|
12417
|
+
if (!Object.hasOwn(definitions, action) || typeof handler !== "function") {
|
|
12418
|
+
throw new TypeError(`Invalid resource action: ${action}`);
|
|
12419
|
+
}
|
|
12420
|
+
const [route, methods] = definitions[action];
|
|
12421
|
+
const methodMap = routes[route] ?? {};
|
|
12422
|
+
routes[route] = methodMap;
|
|
12423
|
+
for (const method of methods)
|
|
12424
|
+
methodMap[method] = handler;
|
|
12425
|
+
}
|
|
12426
|
+
return routes;
|
|
12427
|
+
}
|
|
12428
|
+
function mergeRouteMaps(...maps) {
|
|
12429
|
+
const routes = {};
|
|
12430
|
+
for (const map of maps) {
|
|
12431
|
+
for (const [path, handler] of Object.entries(map)) {
|
|
12432
|
+
if (Object.hasOwn(routes, path))
|
|
12433
|
+
throw new Error(`Duplicate route path: ${path}`);
|
|
12434
|
+
Object.defineProperty(routes, path, {
|
|
12435
|
+
value: handler,
|
|
12436
|
+
enumerable: true,
|
|
12437
|
+
writable: true,
|
|
12438
|
+
configurable: true
|
|
12439
|
+
});
|
|
12440
|
+
}
|
|
12441
|
+
}
|
|
12442
|
+
return routes;
|
|
12443
|
+
}
|
|
12393
12444
|
function getRouteParams(request) {
|
|
12394
12445
|
return request.params;
|
|
12395
12446
|
}
|
|
@@ -16118,6 +16169,75 @@ class EtaViewEngine {
|
|
|
16118
16169
|
});
|
|
16119
16170
|
}
|
|
16120
16171
|
}
|
|
16172
|
+
// ../../src/core/view/viewRenderer.ts
|
|
16173
|
+
function validateRelativeViewName(name) {
|
|
16174
|
+
if (!/^[A-Za-z0-9._~-]+(?:\/[A-Za-z0-9._~-]+)*$/.test(name) || name.split("/").some((part) => part === "." || part === "..")) {
|
|
16175
|
+
throw new TypeError("View namespace and names must be relative template paths without dot segments.");
|
|
16176
|
+
}
|
|
16177
|
+
}
|
|
16178
|
+
var HTMX_VARY_HEADERS = ["HX-Request", "HX-Boosted", "HX-History-Restore-Request"];
|
|
16179
|
+
function varyByHtmx(headers) {
|
|
16180
|
+
const existing = (headers.get("Vary") ?? "").split(",").map((field) => field.trim());
|
|
16181
|
+
if (existing.includes("*"))
|
|
16182
|
+
return;
|
|
16183
|
+
const fields = new Map;
|
|
16184
|
+
for (const field of [...existing, ...HTMX_VARY_HEADERS]) {
|
|
16185
|
+
if (field)
|
|
16186
|
+
fields.set(field.toLowerCase(), field);
|
|
16187
|
+
}
|
|
16188
|
+
headers.set("Vary", [...fields.values()].join(", "));
|
|
16189
|
+
}
|
|
16190
|
+
|
|
16191
|
+
class ViewRenderer {
|
|
16192
|
+
engine;
|
|
16193
|
+
defaultLayout;
|
|
16194
|
+
constructor(engine, defaultLayout) {
|
|
16195
|
+
this.engine = engine;
|
|
16196
|
+
this.defaultLayout = defaultLayout;
|
|
16197
|
+
}
|
|
16198
|
+
namespace(prefix) {
|
|
16199
|
+
validateRelativeViewName(prefix);
|
|
16200
|
+
const qualify = (name) => {
|
|
16201
|
+
validateRelativeViewName(name);
|
|
16202
|
+
return `${prefix}/${name}`;
|
|
16203
|
+
};
|
|
16204
|
+
return {
|
|
16205
|
+
render: async (name, data, options) => this.render(qualify(name), data, options),
|
|
16206
|
+
renderHtmx: async (name, data, options = {}) => this.renderHtmx(qualify(name), data, {
|
|
16207
|
+
...options,
|
|
16208
|
+
...options.fragment === undefined ? {} : { fragment: qualify(options.fragment) }
|
|
16209
|
+
})
|
|
16210
|
+
};
|
|
16211
|
+
}
|
|
16212
|
+
async render(name, data = {}, options = {}) {
|
|
16213
|
+
const html = await this.engine.render(name, data, {
|
|
16214
|
+
request: options.request,
|
|
16215
|
+
layout: options.layout ?? this.defaultLayout
|
|
16216
|
+
});
|
|
16217
|
+
const headers = new Headers(options.headers);
|
|
16218
|
+
headers.set("Content-Type", "text/html; charset=utf-8");
|
|
16219
|
+
return new Response(html, {
|
|
16220
|
+
status: options.status ?? 200,
|
|
16221
|
+
statusText: options.statusText,
|
|
16222
|
+
headers
|
|
16223
|
+
});
|
|
16224
|
+
}
|
|
16225
|
+
async renderHtmx(name, data = {}, options = {}) {
|
|
16226
|
+
const request = options.request ?? currentRequestMeta().request;
|
|
16227
|
+
if (!request)
|
|
16228
|
+
throw new Error("HTMX view rendering requires a request.");
|
|
16229
|
+
const fragment = request.headers.get("HX-Request") === "true" && request.headers.get("HX-Boosted") !== "true" && request.headers.get("HX-History-Restore-Request") !== "true";
|
|
16230
|
+
const headers = new Headers(options.headers);
|
|
16231
|
+
varyByHtmx(headers);
|
|
16232
|
+
return this.render(fragment ? options.fragment ?? name : name, data, {
|
|
16233
|
+
...options,
|
|
16234
|
+
request,
|
|
16235
|
+
headers,
|
|
16236
|
+
layout: fragment ? false : options.layout
|
|
16237
|
+
});
|
|
16238
|
+
}
|
|
16239
|
+
}
|
|
16240
|
+
var CORE_VIEW_RENDERER_TOKEN = createServiceToken("core.viewRenderer");
|
|
16121
16241
|
// ../../src/core/view/webLayoutData.ts
|
|
16122
16242
|
var configuredLayoutOptions = {};
|
|
16123
16243
|
function configureWebLayoutData(options) {
|
|
@@ -16156,6 +16276,16 @@ async function defaultLoadLayoutUser(container, _request) {
|
|
|
16156
16276
|
}
|
|
16157
16277
|
async function resolveWebLayoutData(container, request, options = {}) {
|
|
16158
16278
|
const resolved = { ...configuredLayoutOptions, ...options };
|
|
16279
|
+
return loadWebLayoutData(container, request, resolved);
|
|
16280
|
+
}
|
|
16281
|
+
function createWebLayoutDataResolver(container, options = {}) {
|
|
16282
|
+
const scoped = {
|
|
16283
|
+
...options,
|
|
16284
|
+
extra: typeof options.extra === "function" ? options.extra : { ...options.extra }
|
|
16285
|
+
};
|
|
16286
|
+
return (request) => loadWebLayoutData(container, request, scoped);
|
|
16287
|
+
}
|
|
16288
|
+
async function loadWebLayoutData(container, request, resolved) {
|
|
16159
16289
|
const csrfToken = request ? resolveCsrfTokenForRequest(request) : "";
|
|
16160
16290
|
const flash = request ? currentRequestMeta().flash ?? pullFlash(request) : null;
|
|
16161
16291
|
const userKey = layoutUserKey(resolved);
|
|
@@ -16183,6 +16313,7 @@ export {
|
|
|
16183
16313
|
BelongsToRelationQuery,
|
|
16184
16314
|
Blueprint,
|
|
16185
16315
|
CACHE_TAGS,
|
|
16316
|
+
CORE_VIEW_RENDERER_TOKEN,
|
|
16186
16317
|
repository_default as CacheRepository,
|
|
16187
16318
|
ColumnDefinition,
|
|
16188
16319
|
CompositeGuard,
|
|
@@ -16256,6 +16387,7 @@ export {
|
|
|
16256
16387
|
UnprocessableEntityError,
|
|
16257
16388
|
UnsupportedSchemaFeatureError,
|
|
16258
16389
|
ValidationError,
|
|
16390
|
+
ViewRenderer,
|
|
16259
16391
|
WebFormRequest,
|
|
16260
16392
|
WhereBuilder,
|
|
16261
16393
|
absoluteTemporarySignedUrl,
|
|
@@ -16374,6 +16506,7 @@ export {
|
|
|
16374
16506
|
createTrackedJob,
|
|
16375
16507
|
createTransactionAcquisitionMetrics,
|
|
16376
16508
|
createValidateSignatureMiddleware,
|
|
16509
|
+
createWebLayoutDataResolver,
|
|
16377
16510
|
createdResponse,
|
|
16378
16511
|
currentAuthUser,
|
|
16379
16512
|
currentCredentialSource,
|
|
@@ -16471,6 +16604,7 @@ export {
|
|
|
16471
16604
|
maxLength,
|
|
16472
16605
|
membershipContext,
|
|
16473
16606
|
membershipRepository,
|
|
16607
|
+
mergeRouteMaps,
|
|
16474
16608
|
migrateDatabase,
|
|
16475
16609
|
minLength,
|
|
16476
16610
|
modelEventName,
|
|
@@ -16573,6 +16707,7 @@ export {
|
|
|
16573
16707
|
resolveUserId,
|
|
16574
16708
|
resolveUserTenantId,
|
|
16575
16709
|
resolveWebLayoutData,
|
|
16710
|
+
resourceRoutes,
|
|
16576
16711
|
rollbackDatabase,
|
|
16577
16712
|
rssResponse,
|
|
16578
16713
|
runDueScheduledTasks,
|