@betterportal/framework 10.1.43 → 10.1.44
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/lib/adapters/h3.d.ts.map +1 -1
- package/lib/adapters/h3.js +94 -79
- package/lib/adapters/h3.js.map +1 -1
- package/lib/cli/bp.js +2 -2
- package/lib/cli/bp.js.map +1 -1
- package/lib/codegen/cli.js +11 -5
- package/lib/codegen/cli.js.map +1 -1
- package/lib/codegen/emitter.d.ts.map +1 -1
- package/lib/codegen/emitter.js +78 -55
- package/lib/codegen/emitter.js.map +1 -1
- package/lib/codegen/scanner.d.ts +14 -7
- package/lib/codegen/scanner.d.ts.map +1 -1
- package/lib/codegen/scanner.js +53 -35
- package/lib/codegen/scanner.js.map +1 -1
- package/lib/codegen/validate.d.ts.map +1 -1
- package/lib/codegen/validate.js +31 -14
- package/lib/codegen/validate.js.map +1 -1
- package/lib/contracts/binding.d.ts +3 -1
- package/lib/contracts/binding.d.ts.map +1 -1
- package/lib/contracts/binding.js +1 -1
- package/lib/contracts/binding.js.map +1 -1
- package/lib/contracts/controlPlane.d.ts +3 -3
- package/lib/contracts/controlPlane.d.ts.map +1 -1
- package/lib/contracts/manifest.d.ts +50 -8
- package/lib/contracts/manifest.d.ts.map +1 -1
- package/lib/contracts/manifest.js +16 -3
- package/lib/contracts/manifest.js.map +1 -1
- package/lib/contracts/platformConfig.d.ts +126 -4
- package/lib/contracts/platformConfig.d.ts.map +1 -1
- package/lib/contracts/platformConfig.js +28 -2
- package/lib/contracts/platformConfig.js.map +1 -1
- package/lib/contracts/registry.d.ts +59 -19
- package/lib/contracts/registry.d.ts.map +1 -1
- package/lib/contracts/route.d.ts +3 -3
- package/lib/contracts/route.d.ts.map +1 -1
- package/lib/contracts/streaming.d.ts +2 -2
- package/lib/contracts/view.d.ts +4 -4
- package/lib/contracts/view.d.ts.map +1 -1
- package/lib/contracts/view.js +2 -2
- package/lib/contracts/view.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/runtime/configProvider.d.ts +5 -0
- package/lib/runtime/configProvider.d.ts.map +1 -1
- package/lib/runtime/configProvider.js +30 -1
- package/lib/runtime/configProvider.js.map +1 -1
- package/lib/runtime/element.d.ts +13 -0
- package/lib/runtime/element.d.ts.map +1 -0
- package/lib/runtime/element.js +39 -0
- package/lib/runtime/element.js.map +1 -0
- package/lib/runtime/llms.js +1 -1
- package/lib/runtime/media.d.ts +0 -1
- package/lib/runtime/media.d.ts.map +1 -1
- package/lib/runtime/media.js +0 -1
- package/lib/runtime/media.js.map +1 -1
- package/lib/runtime/registry.d.ts +8 -7
- package/lib/runtime/registry.d.ts.map +1 -1
- package/lib/runtime/registry.js +49 -33
- package/lib/runtime/registry.js.map +1 -1
- package/lib/runtime/statusViews.d.ts +3 -3
- package/lib/runtime/statusViews.d.ts.map +1 -1
- package/lib/runtime/statusViews.js +5 -5
- package/lib/runtime/statusViews.js.map +1 -1
- package/lib/runtime/view.d.ts +1 -1
- package/lib/runtime/view.d.ts.map +1 -1
- package/lib/runtime/view.js +7 -13
- package/lib/runtime/view.js.map +1 -1
- package/package.json +1 -1
package/lib/adapters/h3.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"h3.d.ts","sourceRoot":"","sources":["../../src/adapters/h3.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC/E,OAAO,KAAK,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AACxG,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"h3.d.ts","sourceRoot":"","sources":["../../src/adapters/h3.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC/E,OAAO,KAAK,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AACxG,OAAO,KAAK,EAAsB,oBAAoB,EAAE,eAAe,EAAwD,MAAM,0BAA0B,CAAC;AAChK,OAAO,KAAK,EAGV,WAAW,EAIX,mBAAmB,EACnB,oBAAoB,EAIrB,MAAM,uBAAuB,CAAC;AAI/B,OAAO,KAAK,EAAE,aAAa,EAAa,MAAM,sBAAsB,CAAC;AAErE,OAAO,EAKL,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACvB,MAAM,kBAAkB,CAAC;AA0B1B,MAAM,WAAW,4BAA4B;IAC3C,0BAA0B,CAAC,EAAE,CAC3B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,uBAAuB,KAChC,yBAAyB,CAAC;IAC/B,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yGAAyG;IACzG,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,GAAG,aAAa,GAAG,SAAS,CAAC;IACnI;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,sBAAsB,EAAE,mBAAmB,CAAC,GAAG,OAAO,sBAAsB,EAAE,mBAAmB,CAAC;IAC1K,qEAAqE;IACrE,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;CAC7I;AAOD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,WAAW,CAAC;IAChC,QAAQ,CAAC,eAAe,CAAC,EAAE,oBAAoB,CAAC;IAChD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC7D,QAAQ,CAAC,YAAY,CAAC,EAAE;QACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;CACH;AA0GD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAKvE;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,aAAa,CAAC,eAAe,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAExG;AA2CD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,oBAAoB,EAC9B,GAAG,EAAE,iBAAiB,EACtB,OAAO,GAAE,4BAAiC,GACzC,IAAI,CAmGN;AA8wCD;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,iBAAiB,EACtB,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,cAAc,EACxB,OAAO,GAAE;IACP,MAAM,CAAC,EAAE,MAAM,QAAQ,GAAG,SAAS,CAAC;CAChC,GACL,IAAI,CAyCN"}
|
package/lib/adapters/h3.js
CHANGED
|
@@ -145,8 +145,11 @@ function chromeContentTypeParams(chrome) {
|
|
|
145
145
|
}
|
|
146
146
|
return params.length ? `; ${params.join("; ")}` : "";
|
|
147
147
|
}
|
|
148
|
-
function htmlContentType(
|
|
149
|
-
return `text/html;
|
|
148
|
+
function htmlContentType(mode, chrome) {
|
|
149
|
+
return `text/html; mode=${mode}${chromeContentTypeParams(chrome)}`;
|
|
150
|
+
}
|
|
151
|
+
function rendererFromEvent(event) {
|
|
152
|
+
return event.__bpApp?.shell?.renderer;
|
|
150
153
|
}
|
|
151
154
|
// -- Router registration ----------------------------------------------
|
|
152
155
|
/**
|
|
@@ -162,7 +165,7 @@ export function createH3Router(registry, app, options = {}) {
|
|
|
162
165
|
for (const method of route.methods) {
|
|
163
166
|
const register = methodRegistrar(app, method);
|
|
164
167
|
register(route.path, async (event) => {
|
|
165
|
-
return withRequestObservability(event, route, method, options, (obs) => handleRouteRequest(registry.routes, route, method, event, obs, options));
|
|
168
|
+
return withRequestObservability(event, route, method, options, (obs) => handleRouteRequest(registry.routes, registry.dependencies ?? {}, route, method, event, obs, options));
|
|
166
169
|
});
|
|
167
170
|
}
|
|
168
171
|
// Streaming routes (createStreamHandler) expose their frame stream at
|
|
@@ -198,40 +201,17 @@ export function createH3Router(registry, app, options = {}) {
|
|
|
198
201
|
}
|
|
199
202
|
// Generator path: framework drives the stream.
|
|
200
203
|
if (typeof result === "object" && result !== null && Symbol.asyncIterator in result) {
|
|
201
|
-
//
|
|
202
|
-
// disambiguated - with multiple themes registered, picking the first
|
|
203
|
-
// match would silently render another theme's fragment. Prefer the
|
|
204
|
-
// theme resolved from request context (__bpThemeId), then an explicit
|
|
205
|
-
// `?_theme=` pin; only fall back to a cross-theme scan when exactly one
|
|
206
|
-
// theme provides the fragment.
|
|
204
|
+
// Renderer identity comes only from the server-resolved app shell.
|
|
207
205
|
const fragmentKey = rawQuery._f ?? undefined;
|
|
208
206
|
let sseRender;
|
|
209
207
|
if (fragmentKey) {
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const resolved = resolveRenderer(route, themeId, "fragment", "GET", undefined, fragmentKey);
|
|
208
|
+
const renderer = rendererFromEvent(event);
|
|
209
|
+
if (renderer) {
|
|
210
|
+
const resolved = resolveRenderer(route, renderer, "fragment", "GET", undefined, fragmentKey);
|
|
214
211
|
if (resolved?.renderer.sseRender) {
|
|
215
212
|
sseRender = resolved.renderer.sseRender;
|
|
216
213
|
}
|
|
217
214
|
}
|
|
218
|
-
else {
|
|
219
|
-
// No theme context. Only render if the match is unambiguous across
|
|
220
|
-
// themes; otherwise leave it to the JSON passthrough rather than guess.
|
|
221
|
-
const matches = [];
|
|
222
|
-
for (const candidateThemeId of Object.keys(route.themeRenderers)) {
|
|
223
|
-
const resolved = resolveRenderer(route, candidateThemeId, "fragment", "GET", undefined, fragmentKey);
|
|
224
|
-
if (resolved?.renderer.sseRender) {
|
|
225
|
-
matches.push(resolved.renderer.sseRender);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
if (matches.length === 1) {
|
|
229
|
-
sseRender = matches[0];
|
|
230
|
-
}
|
|
231
|
-
else if (matches.length > 1) {
|
|
232
|
-
obs?.logger.warn("BP SSE: ambiguous fragment '{fragmentKey}' across {count} themes and no theme context; sending raw ticks", { fragmentKey, count: matches.length });
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
215
|
}
|
|
236
216
|
const stream = createEventStream(event);
|
|
237
217
|
const iterable = result;
|
|
@@ -322,7 +302,7 @@ function routePathsMatch(left, right) {
|
|
|
322
302
|
return false;
|
|
323
303
|
return a.every((segment, index) => {
|
|
324
304
|
const other = b[index];
|
|
325
|
-
return segment === other || segment
|
|
305
|
+
return segment === other || pathParamName(segment) !== null || pathParamName(other) !== null;
|
|
326
306
|
});
|
|
327
307
|
}
|
|
328
308
|
function routeMountServicePath(routeMount) {
|
|
@@ -431,7 +411,45 @@ function createRouteUiAttributes(url, options = {}, form = false) {
|
|
|
431
411
|
attrs["hx-push-url"] = String(options.push);
|
|
432
412
|
return attrs;
|
|
433
413
|
}
|
|
434
|
-
function
|
|
414
|
+
function createElementResolver(extraContext, dependencyAliases) {
|
|
415
|
+
return (reference) => {
|
|
416
|
+
if (!reference.fragment.trim())
|
|
417
|
+
return { unavailable: "fragment_required" };
|
|
418
|
+
if (reference.service === "shell") {
|
|
419
|
+
const shellServiceId = extraContext.app.shell?.serviceId;
|
|
420
|
+
const origin = shellServiceId ? serviceOrigin(extraContext, shellServiceId) : null;
|
|
421
|
+
if (!shellServiceId || !origin)
|
|
422
|
+
return { unavailable: "shell_unavailable" };
|
|
423
|
+
const path = `/.well-known/bp/shell/fragment/${encodeURIComponent(reference.fragment)}`;
|
|
424
|
+
return { serviceId: shellServiceId, url: appendQuery(path, reference.args?.query, origin) };
|
|
425
|
+
}
|
|
426
|
+
if (!reference.path?.startsWith("/"))
|
|
427
|
+
return { unavailable: "service_path_required" };
|
|
428
|
+
const pluginId = dependencyAliases[reference.service] ?? reference.service;
|
|
429
|
+
const serviceIds = new Set(extraContext.tenant.services
|
|
430
|
+
.filter((service) => service.enabled && (service.serviceId === pluginId || service.id === pluginId))
|
|
431
|
+
.map((service) => service.id));
|
|
432
|
+
const mounts = extraContext.app.routes.filter((mount) => {
|
|
433
|
+
const servicePath = routeMountServicePath(mount);
|
|
434
|
+
return mount.enabled !== false
|
|
435
|
+
&& serviceIds.has(mount.serviceId)
|
|
436
|
+
&& Boolean(servicePath)
|
|
437
|
+
&& routePathsMatch(servicePath, reference.path);
|
|
438
|
+
});
|
|
439
|
+
if (mounts.length !== 1)
|
|
440
|
+
return { unavailable: mounts.length ? "ambiguous_provider" : "service_unavailable" };
|
|
441
|
+
const mount = mounts[0];
|
|
442
|
+
const origin = serviceOrigin(extraContext, mount.serviceId);
|
|
443
|
+
if (!origin)
|
|
444
|
+
return { unavailable: "service_unavailable" };
|
|
445
|
+
const servicePath = fillAppPath(routeMountServicePath(mount), reference.args?.params);
|
|
446
|
+
return {
|
|
447
|
+
serviceId: mount.serviceId,
|
|
448
|
+
url: renderUrl(servicePath, { absolute: true, origin, query: reference.args?.query, fragment: reference.fragment })
|
|
449
|
+
};
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
function createViewRenderContext(route, ctx, dependencyAliases, renderer, mode, kind, key, status) {
|
|
435
453
|
const current = (options = {}) => renderUrl(ctx.path, options);
|
|
436
454
|
const path = (value, options = {}) => renderUrl(value, options);
|
|
437
455
|
const routeUrl = (viewId, options) => ctx.routeUrl?.(viewId, options) ?? null;
|
|
@@ -440,14 +458,15 @@ function createViewRenderContext(route, ctx, theme, mode, kind, key, status) {
|
|
|
440
458
|
const form = (url, options) => createRouteUiAttributes(url, options, true);
|
|
441
459
|
return {
|
|
442
460
|
request: { method: ctx.method, path: ctx.path, params: ctx.params, query: ctx.query },
|
|
443
|
-
route: { viewId: route.viewId, path: route.path,
|
|
461
|
+
route: { viewId: route.viewId, path: route.path, renderer, mode, kind, key, status },
|
|
444
462
|
url: { current, path, route: routeUrl, uiRoute },
|
|
445
463
|
routeUi: {
|
|
446
464
|
link: ui,
|
|
447
465
|
current: (options = {}) => ui(current(options), options),
|
|
448
466
|
fragment: ui,
|
|
449
467
|
form
|
|
450
|
-
}
|
|
468
|
+
},
|
|
469
|
+
element: createElementResolver(ctx, dependencyAliases)
|
|
451
470
|
};
|
|
452
471
|
}
|
|
453
472
|
function createServiceRouteUrlBuilder(routes, extraContext, currentServiceId) {
|
|
@@ -570,7 +589,7 @@ async function withSpan(obs, name, attributes, handler) {
|
|
|
570
589
|
throw error;
|
|
571
590
|
}
|
|
572
591
|
}
|
|
573
|
-
async function handleRouteRequest(registryRoutes, route, method, event, obs, routerOptions = {}) {
|
|
592
|
+
async function handleRouteRequest(registryRoutes, dependencyAliases, route, method, event, obs, routerOptions = {}) {
|
|
574
593
|
const methodRoute = route.methodRoutes?.[method];
|
|
575
594
|
const handler = methodRoute?.handler ?? route.handlers[method];
|
|
576
595
|
const schemas = methodRoute?.schemas ?? route.schemas;
|
|
@@ -756,33 +775,31 @@ async function handleRouteRequest(registryRoutes, route, method, event, obs, rou
|
|
|
756
775
|
if (representation.kind === "json") {
|
|
757
776
|
return jsonResponse(data, handlerStatus);
|
|
758
777
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
if (!themeId) {
|
|
763
|
-
logNegotiationFailure(obs, route, method, "theme_not_resolved", {
|
|
778
|
+
const renderer = rendererFromEvent(event);
|
|
779
|
+
if (!renderer) {
|
|
780
|
+
logNegotiationFailure(obs, route, method, "renderer_not_resolved", {
|
|
764
781
|
"http.request.accept": acceptHeader ?? "",
|
|
765
782
|
"bp.representation.kind": representation.kind
|
|
766
783
|
});
|
|
767
|
-
return jsonResponse({ error: "
|
|
784
|
+
return jsonResponse({ error: "Renderer could not be resolved from the app shell" }, 406);
|
|
768
785
|
}
|
|
769
786
|
// Determine the renderer kind requested
|
|
770
787
|
const fragmentKey = url.searchParams.get("_f") ?? fragmentFromAcceptHeader(acceptHeader);
|
|
771
788
|
const componentId = url.searchParams.get("_c");
|
|
772
789
|
const requestedKind = fragmentKey ? "fragment" : componentId ? "component" : "page";
|
|
773
790
|
const requestedKey = fragmentKey ?? componentId ?? undefined;
|
|
774
|
-
const renderContext = createViewRenderContext(route, ctx,
|
|
791
|
+
const renderContext = createViewRenderContext(route, ctx, dependencyAliases, renderer, representation.mode ?? "page", requestedKind, requestedKey, handlerStatus);
|
|
775
792
|
// Status-specific renderer lookup (any non-undefined status code)
|
|
776
793
|
if (handlerStatus !== 200) {
|
|
777
|
-
const statusRenderer = resolveStatusRenderer(route,
|
|
794
|
+
const statusRenderer = resolveStatusRenderer(route, renderer, handlerStatus, requestedKind, requestedKey, method);
|
|
778
795
|
if (statusRenderer) {
|
|
779
796
|
const html = await withSpan(obs, "bp.view.render", {
|
|
780
797
|
"bp.route.view_id": route.viewId,
|
|
781
|
-
"bp.view.
|
|
798
|
+
"bp.view.renderer": renderer,
|
|
782
799
|
"bp.view.kind": requestedKind,
|
|
783
800
|
"bp.view.status": handlerStatus
|
|
784
801
|
}, () => statusRenderer.render(data, renderContext));
|
|
785
|
-
return htmlResponse(rewriteServiceRouteTokens(toHtmlString(html), ctx.routeUrl, obs), handlerStatus, htmlContentType(
|
|
802
|
+
return htmlResponse(rewriteServiceRouteTokens(toHtmlString(html), ctx.routeUrl, obs), handlerStatus, htmlContentType("status", route.chrome));
|
|
786
803
|
}
|
|
787
804
|
// No specific renderer found.
|
|
788
805
|
if (!shouldFallThroughToDefaultRenderer(handlerStatus)) {
|
|
@@ -793,67 +810,67 @@ async function handleRouteRequest(registryRoutes, route, method, event, obs, rou
|
|
|
793
810
|
}
|
|
794
811
|
// Fragment request via `_f` query param or Accept header
|
|
795
812
|
if (fragmentKey) {
|
|
796
|
-
const resolved = resolveRenderer(route,
|
|
813
|
+
const resolved = resolveRenderer(route, renderer, "fragment", method, undefined, fragmentKey);
|
|
797
814
|
if (!resolved) {
|
|
798
815
|
logNegotiationFailure(obs, route, method, "fragment_renderer_not_found", {
|
|
799
816
|
"http.request.accept": acceptHeader ?? "",
|
|
800
|
-
"bp.view.
|
|
817
|
+
"bp.view.renderer": renderer,
|
|
801
818
|
"bp.view.kind": "fragment",
|
|
802
819
|
"bp.view.key": fragmentKey
|
|
803
820
|
});
|
|
804
821
|
return jsonResponse({
|
|
805
|
-
error: `No fragment renderer found for fragment="${fragmentKey}" in
|
|
822
|
+
error: `No fragment renderer found for fragment="${fragmentKey}" in renderer "${renderer}"`
|
|
806
823
|
}, 406);
|
|
807
824
|
}
|
|
808
825
|
const html = await withSpan(obs, "bp.view.render", {
|
|
809
826
|
"bp.route.view_id": route.viewId,
|
|
810
|
-
"bp.view.
|
|
827
|
+
"bp.view.renderer": renderer,
|
|
811
828
|
"bp.view.kind": "fragment",
|
|
812
829
|
"bp.view.key": fragmentKey
|
|
813
830
|
}, () => resolved.renderer.render(data, renderContext));
|
|
814
|
-
return htmlResponse(rewriteServiceRouteTokens(toHtmlString(html), ctx.routeUrl, obs), handlerStatus, htmlContentType(
|
|
831
|
+
return htmlResponse(rewriteServiceRouteTokens(toHtmlString(html), ctx.routeUrl, obs), handlerStatus, htmlContentType("fragment", route.chrome));
|
|
815
832
|
}
|
|
816
833
|
// Component request via `_c` query param
|
|
817
834
|
if (componentId) {
|
|
818
|
-
const resolved = resolveRenderer(route,
|
|
835
|
+
const resolved = resolveRenderer(route, renderer, "component", method, componentId);
|
|
819
836
|
if (!resolved) {
|
|
820
837
|
logNegotiationFailure(obs, route, method, "component_renderer_not_found", {
|
|
821
838
|
"http.request.accept": acceptHeader ?? "",
|
|
822
|
-
"bp.view.
|
|
839
|
+
"bp.view.renderer": renderer,
|
|
823
840
|
"bp.view.kind": "component",
|
|
824
841
|
"bp.view.key": componentId
|
|
825
842
|
});
|
|
826
843
|
return jsonResponse({
|
|
827
|
-
error: `No component renderer found for _c="${componentId}" in
|
|
844
|
+
error: `No component renderer found for _c="${componentId}" in renderer "${renderer}"`
|
|
828
845
|
}, 406);
|
|
829
846
|
}
|
|
830
847
|
const html = await withSpan(obs, "bp.view.render", {
|
|
831
848
|
"bp.route.view_id": route.viewId,
|
|
832
|
-
"bp.view.
|
|
849
|
+
"bp.view.renderer": renderer,
|
|
833
850
|
"bp.view.kind": "component",
|
|
834
851
|
"bp.view.key": componentId
|
|
835
852
|
}, () => resolved.renderer.render(data, renderContext));
|
|
836
|
-
return htmlResponse(rewriteServiceRouteTokens(toHtmlString(html), ctx.routeUrl, obs), handlerStatus, htmlContentType(
|
|
853
|
+
return htmlResponse(rewriteServiceRouteTokens(toHtmlString(html), ctx.routeUrl, obs), handlerStatus, htmlContentType("fragment", route.chrome));
|
|
837
854
|
}
|
|
838
855
|
// Page request - only page renderers allowed
|
|
839
|
-
const resolved = resolveRenderer(route,
|
|
856
|
+
const resolved = resolveRenderer(route, renderer, "page", method);
|
|
840
857
|
if (!resolved) {
|
|
841
858
|
logNegotiationFailure(obs, route, method, "page_renderer_not_found", {
|
|
842
859
|
"http.request.accept": acceptHeader ?? "",
|
|
843
|
-
"bp.view.
|
|
860
|
+
"bp.view.renderer": renderer,
|
|
844
861
|
"bp.view.kind": "page"
|
|
845
862
|
});
|
|
846
863
|
return jsonResponse({
|
|
847
|
-
error: `No page renderer found for
|
|
864
|
+
error: `No page renderer found for renderer "${renderer}"`
|
|
848
865
|
}, 406);
|
|
849
866
|
}
|
|
850
867
|
const html = await withSpan(obs, "bp.view.render", {
|
|
851
868
|
"bp.route.view_id": route.viewId,
|
|
852
|
-
"bp.view.
|
|
869
|
+
"bp.view.renderer": renderer,
|
|
853
870
|
"bp.view.kind": "page"
|
|
854
871
|
}, () => resolved.renderer.render(data, renderContext));
|
|
855
872
|
const mode = representation.mode ?? "page";
|
|
856
|
-
return htmlResponse(rewriteServiceRouteTokens(toHtmlString(html), ctx.routeUrl, obs), handlerStatus, htmlContentType(
|
|
873
|
+
return htmlResponse(rewriteServiceRouteTokens(toHtmlString(html), ctx.routeUrl, obs), handlerStatus, htmlContentType(mode, route.chrome));
|
|
857
874
|
}
|
|
858
875
|
/**
|
|
859
876
|
* Handle representations that stream, returning null for buffered ones so the
|
|
@@ -870,16 +887,15 @@ async function handleStreamRepresentation(route, handler, ctx, event, url, metho
|
|
|
870
887
|
// Fragment/component selectors render over the buffered data set.
|
|
871
888
|
if (url.searchParams.get("_f") || url.searchParams.get("_c"))
|
|
872
889
|
return null;
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
if (!themeId)
|
|
890
|
+
const renderer = rendererFromEvent(event);
|
|
891
|
+
if (!renderer)
|
|
876
892
|
return null;
|
|
877
|
-
const streamSet = route.
|
|
893
|
+
const streamSet = route.renderers[renderer]?.stream;
|
|
878
894
|
if (!streamSet)
|
|
879
895
|
return null;
|
|
880
896
|
// Full-page request with a page renderer available -> buffered render of the
|
|
881
897
|
// complete data set (crawlers, no-SSE clients). Fragment swaps stream.
|
|
882
|
-
if (representation.mode === "page" && resolveRenderer(route,
|
|
898
|
+
if (representation.mode === "page" && resolveRenderer(route, renderer, "page", method)) {
|
|
883
899
|
return null;
|
|
884
900
|
}
|
|
885
901
|
const shellCtx = {
|
|
@@ -889,10 +905,10 @@ async function handleStreamRepresentation(route, handler, ctx, event, url, metho
|
|
|
889
905
|
};
|
|
890
906
|
const html = await withSpan(obs, "bp.view.render", {
|
|
891
907
|
"bp.route.view_id": route.viewId,
|
|
892
|
-
"bp.view.
|
|
908
|
+
"bp.view.renderer": renderer,
|
|
893
909
|
"bp.view.kind": "stream-shell"
|
|
894
910
|
}, () => streamSet.renderShell(shellCtx));
|
|
895
|
-
return htmlResponse(rewriteServiceRouteTokens(toHtmlString(html), ctx.routeUrl, obs), 200, htmlContentType(
|
|
911
|
+
return htmlResponse(rewriteServiceRouteTokens(toHtmlString(html), ctx.routeUrl, obs), 200, htmlContentType("fragment", route.chrome));
|
|
896
912
|
}
|
|
897
913
|
/**
|
|
898
914
|
* SSE delivery of the frame stream at `{path}/__sse`. With a theme context and
|
|
@@ -936,9 +952,8 @@ async function handleStreamSse(registryRoutes, route, handler, event, obs, route
|
|
|
936
952
|
file: fileResponseHelper,
|
|
937
953
|
...(obs ? { obs } : {})
|
|
938
954
|
};
|
|
939
|
-
const
|
|
940
|
-
|
|
941
|
-
const streamSet = themeId ? route.themeRenderers[themeId]?.stream : undefined;
|
|
955
|
+
const renderer = rendererFromEvent(event);
|
|
956
|
+
const streamSet = renderer ? route.renderers[renderer]?.stream : undefined;
|
|
942
957
|
const stream = createEventStream(event);
|
|
943
958
|
(async () => {
|
|
944
959
|
try {
|
|
@@ -1209,7 +1224,7 @@ function formatRequiredPermissions(requiredPermissions = []) {
|
|
|
1209
1224
|
.join("; ");
|
|
1210
1225
|
}
|
|
1211
1226
|
function renderAuthError(route, event, status, message, requiredPermissions = []) {
|
|
1212
|
-
const
|
|
1227
|
+
const renderer = rendererFromEvent(event);
|
|
1213
1228
|
const acceptHeader = acceptHeaderFromEvent(event);
|
|
1214
1229
|
const representation = resolveRequestedRepresentation(acceptHeader);
|
|
1215
1230
|
const corsHeaders = corsHeadersFromEvent(event);
|
|
@@ -1221,14 +1236,14 @@ function renderAuthError(route, event, status, message, requiredPermissions = []
|
|
|
1221
1236
|
// app.auth config and redirects on seeing this 401. Services just report status.
|
|
1222
1237
|
// Prefer a route/theme status view so the body swaps cleanly into the htmx
|
|
1223
1238
|
// target as a fragment rather than replacing the shell.
|
|
1224
|
-
if (
|
|
1225
|
-
const statusRenderer = resolveStatusRenderer(route,
|
|
1239
|
+
if (renderer && (representation.kind === "html")) {
|
|
1240
|
+
const statusRenderer = resolveStatusRenderer(route, renderer, status, "page", undefined, "GET");
|
|
1226
1241
|
if (statusRenderer) {
|
|
1227
1242
|
try {
|
|
1228
1243
|
const html = statusRenderer.render({ error: message, status, requiredPermissions });
|
|
1229
1244
|
return new Response(toHtmlString(html), {
|
|
1230
1245
|
status,
|
|
1231
|
-
headers: { ...corsHeaders, "content-type": htmlContentType(
|
|
1246
|
+
headers: { ...corsHeaders, "content-type": htmlContentType("status", route.chrome) }
|
|
1232
1247
|
});
|
|
1233
1248
|
}
|
|
1234
1249
|
catch {
|
|
@@ -1249,7 +1264,7 @@ function renderAuthError(route, event, status, message, requiredPermissions = []
|
|
|
1249
1264
|
`;
|
|
1250
1265
|
return new Response(html, {
|
|
1251
1266
|
status,
|
|
1252
|
-
headers: { ...corsHeaders, "content-type":
|
|
1267
|
+
headers: { ...corsHeaders, "content-type": renderer ? htmlContentType("status", route.chrome) : "text/html; charset=utf-8" }
|
|
1253
1268
|
});
|
|
1254
1269
|
}
|
|
1255
1270
|
return jsonResponse({ error: message, status, requiredPermissions }, status, corsHeaders);
|
|
@@ -1261,7 +1276,7 @@ function readTenantAppFromEvent(event) {
|
|
|
1261
1276
|
return { tenantId: ctx.__bpTenantId, appId: ctx.__bpAppId };
|
|
1262
1277
|
}
|
|
1263
1278
|
function renderUpgradeRequired(route, event, validation) {
|
|
1264
|
-
const
|
|
1279
|
+
const renderer = rendererFromEvent(event);
|
|
1265
1280
|
const acceptHeader = acceptHeaderFromEvent(event);
|
|
1266
1281
|
const representation = resolveRequestedRepresentation(acceptHeader);
|
|
1267
1282
|
const status = 426;
|
|
@@ -1270,8 +1285,8 @@ function renderUpgradeRequired(route, event, validation) {
|
|
|
1270
1285
|
if (validation.retryAfterSeconds) {
|
|
1271
1286
|
extraHeaders["retry-after"] = String(validation.retryAfterSeconds);
|
|
1272
1287
|
}
|
|
1273
|
-
if (
|
|
1274
|
-
const statusRenderer = resolveStatusRenderer(route,
|
|
1288
|
+
if (renderer && representation.kind === "html") {
|
|
1289
|
+
const statusRenderer = resolveStatusRenderer(route, renderer, status, "page", undefined, "GET");
|
|
1275
1290
|
if (statusRenderer) {
|
|
1276
1291
|
try {
|
|
1277
1292
|
const html = statusRenderer.render({
|
|
@@ -1279,7 +1294,7 @@ function renderUpgradeRequired(route, event, validation) {
|
|
|
1279
1294
|
reason: validation.reason,
|
|
1280
1295
|
upgradeUrl: validation.upgradeUrl
|
|
1281
1296
|
});
|
|
1282
|
-
return htmlResponse(toHtmlString(html), status, htmlContentType(
|
|
1297
|
+
return htmlResponse(toHtmlString(html), status, htmlContentType("status", route.chrome));
|
|
1283
1298
|
}
|
|
1284
1299
|
catch {
|
|
1285
1300
|
// fall through to JSON
|