@xemahq/ui-kernel 0.11.1 → 0.12.0
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/errors/http-error-status.d.ts +17 -0
- package/dist/errors/http-error-status.d.ts.map +1 -0
- package/dist/errors/http-error-status.js +59 -0
- package/dist/errors/http-error-status.js.map +1 -0
- package/dist/errors/index.d.ts +2 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +2 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/biome-host/errors.d.ts.map +1 -1
- package/dist/lib/biome-host/errors.js +1 -19
- package/dist/lib/biome-host/errors.js.map +1 -1
- package/dist/offline/index.d.ts.map +1 -1
- package/dist/offline/index.js.map +1 -1
- package/dist/offline/online-controller.d.ts.map +1 -1
- package/dist/offline/online-controller.js +6 -1
- package/dist/offline/online-controller.js.map +1 -1
- package/dist/offline/outbox.d.ts.map +1 -1
- package/dist/offline/outbox.js +2 -24
- package/dist/offline/outbox.js.map +1 -1
- package/package.json +1 -2
- package/src/index.ts +0 -17
- package/src/lib/biome-host/agent-validation.ts +0 -239
- package/src/lib/biome-host/biome-builders.ts +0 -112
- package/src/lib/biome-host/biome-mode.ts +0 -6
- package/src/lib/biome-host/biome-navigation.ts +0 -37
- package/src/lib/biome-host/biome-registry.ts +0 -299
- package/src/lib/biome-host/biome-scope-hooks.ts +0 -41
- package/src/lib/biome-host/biome-scope.tsx +0 -52
- package/src/lib/biome-host/biome-scoped-query.ts +0 -134
- package/src/lib/biome-host/create-biome-orval-config.ts +0 -82
- package/src/lib/biome-host/define-web-biome.spec.ts +0 -221
- package/src/lib/biome-host/define-web-biome.ts +0 -222
- package/src/lib/biome-host/errors.ts +0 -220
- package/src/lib/biome-host/frontend-biome.ts +0 -256
- package/src/lib/biome-host/host-bridge.ts +0 -281
- package/src/lib/biome-host/host-sources.ts +0 -54
- package/src/lib/biome-host/i18n-hooks.ts +0 -51
- package/src/lib/biome-host/i18n-port.test.ts +0 -50
- package/src/lib/biome-host/i18n-port.ts +0 -96
- package/src/lib/biome-host/index.ts +0 -35
- package/src/lib/biome-host/realtime-hooks.ts +0 -75
- package/src/lib/biome-host/realtime-port.ts +0 -115
- package/src/lib/biome-host/session-contributions.ts +0 -296
- package/src/lib/biome-host/use-mutation-with-error-toast.ts +0 -90
- package/src/lib/biome-host/use-page-state.ts +0 -248
- package/src/lib/capabilities/capability-context.ts +0 -25
- package/src/lib/capabilities/capability-provider.tsx +0 -73
- package/src/lib/capabilities/index.ts +0 -17
- package/src/lib/capabilities/types.ts +0 -69
- package/src/lib/capabilities/use-capability.ts +0 -79
- package/src/lib/contribution-registry/contribution-registry.spec.ts +0 -141
- package/src/lib/contribution-registry/contribution-registry.ts +0 -204
- package/src/lib/contribution-registry/index.ts +0 -9
- package/src/lib/contribution-registry/renderable-registry.ts +0 -142
- package/src/lib/system-bus/capability-invoke.ts +0 -92
- package/src/lib/system-bus/deeplink.ts +0 -210
- package/src/lib/system-bus/enums.ts +0 -86
- package/src/lib/system-bus/host-ports.ts +0 -93
- package/src/lib/system-bus/index.ts +0 -16
- package/src/lib/system-bus/intent-registry.ts +0 -109
- package/src/lib/system-bus/intents.ts +0 -113
- package/src/lib/system-bus/palette.ts +0 -78
- package/src/lib/system-bus/system-bus-builder.ts +0 -159
- package/src/lib/system-bus/system-bus.ts +0 -37
- package/src/lib/system-bus/windows.ts +0 -51
- package/src/offline/id.ts +0 -16
- package/src/offline/index.ts +0 -54
- package/src/offline/mutation.ts +0 -75
- package/src/offline/offline-status-badge.tsx +0 -51
- package/src/offline/online-controller.ts +0 -105
- package/src/offline/outbox.test.ts +0 -172
- package/src/offline/outbox.ts +0 -348
- package/src/offline/react-hooks.ts +0 -40
- package/src/offline/react.tsx +0 -32
- package/src/offline/storage.ts +0 -112
- package/src/offline/types.ts +0 -77
- package/src/org-db/components/DbResultTable.tsx +0 -176
- package/src/org-db/index.ts +0 -1
- package/src/pdf/index.ts +0 -11
- package/src/registry/index.ts +0 -9
- package/src/registry/lib/agent-validation-host.ts +0 -36
- package/src/registry/lib/biome-slot.tsx +0 -50
- package/src/registry/lib/biomes-enabled-context.ts +0 -20
- package/src/registry/lib/extension-points.ts +0 -134
- package/src/registry/lib/primitives/SessionEventCard.tsx +0 -164
- package/src/registry/lib/primitives/SessionEventTimeline.tsx +0 -95
- package/src/registry/lib/primitives/SessionMutationBar.tsx +0 -76
- package/src/registry/lib/primitives/SessionTabbedDrawer.tsx +0 -165
- package/src/registry/lib/primitives/index.ts +0 -18
- package/src/registry/lib/session-context-builder.ts +0 -84
- package/src/registry/lib/session-context-provider.tsx +0 -33
- package/src/registry/lib/session-context.ts +0 -21
- package/src/registry/lib/session-selectors.ts +0 -272
- package/src/session/comments/CommentRail.tsx +0 -164
- package/src/session/index.ts +0 -3
- package/src/session/lib/cn.ts +0 -11
- package/src/session/shell/SessionWorkspaceShell.tsx +0 -147
- package/src/session-kit/approvals/ApprovalButton.tsx +0 -89
- package/src/session-kit/approvals/ApprovalCard.tsx +0 -341
- package/src/session-kit/approvals/ApprovalsCenter.tsx +0 -336
- package/src/session-kit/approvals/CapabilityApprovalStickyBar.tsx +0 -118
- package/src/session-kit/approvals/Obligation.tsx +0 -111
- package/src/session-kit/approvals/ScopedBody.tsx +0 -392
- package/src/session-kit/approvals/approval-icons.ts +0 -31
- package/src/session-kit/approvals/approval-model.ts +0 -204
- package/src/session-kit/approvals/index.ts +0 -22
- package/src/session-kit/approvals/obligation-display.ts +0 -100
- package/src/session-kit/approvals/risk-accent.ts +0 -47
- package/src/session-kit/approvals/scope-icons.ts +0 -19
- package/src/session-kit/combobox/Combobox.tsx +0 -358
- package/src/session-kit/combobox/use-click-outside.ts +0 -21
- package/src/session-kit/display/ContextHeader.tsx +0 -148
- package/src/session-kit/display/FileDiffCard.tsx +0 -140
- package/src/session-kit/display/MD.tsx +0 -162
- package/src/session-kit/display/MessageTurn.tsx +0 -157
- package/src/session-kit/display/ThinkingPanel.tsx +0 -81
- package/src/session-kit/display/TodoChecklist.tsx +0 -120
- package/src/session-kit/display/TokenMeter.tsx +0 -89
- package/src/session-kit/display/ToolStrip.tsx +0 -278
- package/src/session-kit/display/TypingDots.tsx +0 -24
- package/src/session-kit/index.ts +0 -44
- package/src/session-kit/lib/enums.ts +0 -66
- package/src/session-kit/lib/portal-accent.ts +0 -30
- package/src/session-kit/lib/status-dot.ts +0 -68
- package/src/session-kit/primitives/Avatar.tsx +0 -44
- package/src/session-kit/primitives/PortalGlyph.tsx +0 -51
- package/src/session-kit/primitives/RiskPill.tsx +0 -95
- package/src/session-kit/primitives/ScopeDot.tsx +0 -47
- package/src/session-kit/primitives/Segmented.tsx +0 -71
- package/src/ui/branding/app-branding-context.ts +0 -20
- package/src/ui/branding/app-branding-provider.tsx +0 -123
- package/src/ui/branding/sanitize.ts +0 -89
- package/src/ui/breakpoints.ts +0 -14
- package/src/ui/capture/document-preview.tsx +0 -166
- package/src/ui/capture/field-spec-to-zod.test.ts +0 -81
- package/src/ui/capture/field-spec-to-zod.ts +0 -174
- package/src/ui/capture/field-spec-validate.ts +0 -236
- package/src/ui/capture/field-spec.test.ts +0 -49
- package/src/ui/capture/field-spec.ts +0 -274
- package/src/ui/capture/index.ts +0 -51
- package/src/ui/capture/schema-field-input.tsx +0 -620
- package/src/ui/capture/schema-form.tsx +0 -149
- package/src/ui/capture/submit-values.test.ts +0 -70
- package/src/ui/capture/submit-values.ts +0 -63
- package/src/ui/capture/use-prefill.ts +0 -186
- package/src/ui/capture/use-schema-validation.ts +0 -145
- package/src/ui/capture/use-wizard.ts +0 -180
- package/src/ui/capture/value-format.ts +0 -32
- package/src/ui/capture/wizard.tsx +0 -105
- package/src/ui/charts/bar-chart.tsx +0 -139
- package/src/ui/charts/chart-frame.tsx +0 -125
- package/src/ui/charts/chart-types.ts +0 -52
- package/src/ui/charts/chart-utils.ts +0 -190
- package/src/ui/charts/charts.test.tsx +0 -204
- package/src/ui/charts/donut-chart.tsx +0 -129
- package/src/ui/charts/index.ts +0 -27
- package/src/ui/charts/line-chart.tsx +0 -202
- package/src/ui/charts/sparkline.tsx +0 -109
- package/src/ui/chrome/AsyncBoundary.tsx +0 -74
- package/src/ui/chrome/EmptyState.tsx +0 -257
- package/src/ui/chrome/ErrorCard.tsx +0 -63
- package/src/ui/chrome/LoadingState.tsx +0 -61
- package/src/ui/chrome/PageHeader.tsx +0 -137
- package/src/ui/chrome/RecordCardList.tsx +0 -98
- package/src/ui/chrome/StateCard.tsx +0 -150
- package/src/ui/chrome/confirm-delete-dialog.tsx +0 -59
- package/src/ui/chrome/page-shell.spec.tsx +0 -117
- package/src/ui/chrome/page-shell.tsx +0 -221
- package/src/ui/chrome/scope-badge.tsx +0 -40
- package/src/ui/chrome/status-badge.tsx +0 -79
- package/src/ui/cn.ts +0 -32
- package/src/ui/design-tokens.ts +0 -372
- package/src/ui/hooks/use-debounced-value.ts +0 -16
- package/src/ui/hooks/use-is-mobile.ts +0 -28
- package/src/ui/index.ts +0 -110
- package/src/ui/pdf/pdf-preview.tsx +0 -122
- package/src/ui/primitives/alert-dialog.tsx +0 -104
- package/src/ui/primitives/async-combobox.tsx +0 -182
- package/src/ui/primitives/badge-variants.ts +0 -21
- package/src/ui/primitives/badge.tsx +0 -25
- package/src/ui/primitives/button-variants.ts +0 -31
- package/src/ui/primitives/button.tsx +0 -29
- package/src/ui/primitives/card.tsx +0 -43
- package/src/ui/primitives/checkbox.tsx +0 -26
- package/src/ui/primitives/collapsible.tsx +0 -9
- package/src/ui/primitives/dialog.tsx +0 -103
- package/src/ui/primitives/dropdown-menu.tsx +0 -179
- package/src/ui/primitives/form-stepper.tsx +0 -94
- package/src/ui/primitives/form-stepper.types.ts +0 -15
- package/src/ui/primitives/input.tsx +0 -22
- package/src/ui/primitives/label.tsx +0 -17
- package/src/ui/primitives/overflow-tabs.tsx +0 -331
- package/src/ui/primitives/popover.tsx +0 -33
- package/src/ui/primitives/preview-device-frame.tsx +0 -213
- package/src/ui/primitives/radio-group.tsx +0 -36
- package/src/ui/primitives/resizable.tsx +0 -67
- package/src/ui/primitives/scroll-area.tsx +0 -38
- package/src/ui/primitives/select.tsx +0 -143
- package/src/ui/primitives/separator.tsx +0 -20
- package/src/ui/primitives/sheet.tsx +0 -107
- package/src/ui/primitives/skeleton.tsx +0 -99
- package/src/ui/primitives/switch.tsx +0 -27
- package/src/ui/primitives/table.tsx +0 -72
- package/src/ui/primitives/tabs.tsx +0 -53
- package/src/ui/primitives/tag-multi-select.tsx +0 -261
- package/src/ui/primitives/textarea.tsx +0 -21
- package/src/ui/primitives/tooltip.tsx +0 -30
- package/src/ui/primitives/url-preview-chrome.tsx +0 -103
- package/src/vista/GenericCatalogueSurface.tsx +0 -179
- package/src/vista/SurfaceFallbackCard.tsx +0 -109
- package/src/vista/VistaPanel.tsx +0 -163
- package/src/vista/form-payload.spec.ts +0 -139
- package/src/vista/html/CustomUiFrame.spec.tsx +0 -127
- package/src/vista/html/CustomUiFrame.tsx +0 -206
- package/src/vista/html/custom-ui-frame-bridge.spec.ts +0 -191
- package/src/vista/html/custom-ui-frame-bridge.ts +0 -98
- package/src/vista/html/custom-ui-frame-contract.ts +0 -88
- package/src/vista/html/custom-ui-intents.spec.ts +0 -135
- package/src/vista/html/custom-ui-intents.ts +0 -155
- package/src/vista/html/custom-ui-srcdoc.spec.ts +0 -151
- package/src/vista/html/custom-ui-srcdoc.ts +0 -193
- package/src/vista/html/index.ts +0 -13
- package/src/vista/index.ts +0 -52
- package/src/vista/surface-catalogue.spec.ts +0 -140
- package/src/vista/surface-catalogue.ts +0 -90
- package/src/vista/surface-icons.ts +0 -57
- package/src/vista/surface-registry.ts +0 -56
- package/src/vista/surfaces/ChartSurface.tsx +0 -34
- package/src/vista/surfaces/FilesSurface.tsx +0 -202
- package/src/vista/surfaces/FormSurface.tsx +0 -380
- package/src/vista/surfaces/HtmlSurface.tsx +0 -43
- package/src/vista/surfaces/TableSurface.tsx +0 -100
- package/src/vista/surfaces/UrlSurface.tsx +0 -40
- package/src/vista/surfaces/blob-surfaces.tsx +0 -139
- package/src/vista/surfaces/elicitation-transport.spec.ts +0 -369
- package/src/vista/surfaces/elicitation-transport.ts +0 -367
- package/src/vista/surfaces/file-surface-utils.ts +0 -69
- package/src/vista/surfaces/index.ts +0 -24
- package/src/vista/surfaces/register-surfaces.tsx +0 -139
- package/src/vista/surfaces/text-surfaces.tsx +0 -132
- package/src/vista/use-vista-tabs.ts +0 -196
- package/src/vista/vista-code-renderer-context.ts +0 -16
- package/src/vista/vista-code-renderer.tsx +0 -35
- package/src/vista/vista-file-context.ts +0 -14
- package/src/vista/vista-file-source.tsx +0 -141
- package/src/vista/vista-form-context.ts +0 -20
- package/src/vista/vista-form-host.tsx +0 -48
- package/src/vista/vista-model.ts +0 -137
- package/src/vista/vista-open-context.tsx +0 -27
- package/src/vista/vista-open-state.ts +0 -11
- package/src/vista/vista-payloads.ts +0 -635
- package/src/vista/vista-tabs-manifest.ts +0 -138
- package/src/vista/vista-tools.ts +0 -232
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Frontend-biome route/biome builders.
|
|
3
|
-
*
|
|
4
|
-
* The `createElement(Suspense, { fallback: null }, createElement(LazyPage))`
|
|
5
|
-
* wrapper is hand-repeated ~74× across biome `index.ts` files — once per lazy
|
|
6
|
-
* route. `lazyRoute` collapses it to one call; `defineSinglePageBiome` builds
|
|
7
|
-
* the whole one-page biome (nav item + single route) that the vast majority of
|
|
8
|
-
* biomes are. Host-framework-agnostic: pure React (`lazy` / `Suspense` /
|
|
9
|
-
* `createElement`), no router and no Next.js.
|
|
10
|
-
*/
|
|
11
|
-
import {
|
|
12
|
-
Suspense,
|
|
13
|
-
createElement,
|
|
14
|
-
lazy,
|
|
15
|
-
type ComponentType,
|
|
16
|
-
type ReactNode,
|
|
17
|
-
} from 'react';
|
|
18
|
-
|
|
19
|
-
import type {
|
|
20
|
-
FrontendBiome,
|
|
21
|
-
NavItemContribution,
|
|
22
|
-
RouteAccess,
|
|
23
|
-
RouteContribution,
|
|
24
|
-
} from './frontend-biome';
|
|
25
|
-
|
|
26
|
-
/** A dynamic import of a module whose default export is a React component. */
|
|
27
|
-
export type LazyComponentImport = () => Promise<{ default: ComponentType }>;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Wrap a dynamic page import as a `RouteContribution['element']` factory: lazy
|
|
31
|
-
* import + `<Suspense>` boundary. Replaces the hand-written
|
|
32
|
-
* `createElement(Suspense, { fallback }, createElement(lazy(importFn)))` block.
|
|
33
|
-
*
|
|
34
|
-
* The `lazy(...)` call is hoisted out of the returned thunk so the component is
|
|
35
|
-
* created ONCE (not on every route render), matching how biomes hand-declare
|
|
36
|
-
* `const Page = lazy(() => import('./Page'))` at module scope.
|
|
37
|
-
*
|
|
38
|
-
* @param importFn Dynamic import of the page module (`() => import('./Page')`).
|
|
39
|
-
* @param fallback Suspense fallback. Defaults to `null` (the prevailing biome
|
|
40
|
-
* convention) — pass a skeleton to be explicit.
|
|
41
|
-
*/
|
|
42
|
-
export function lazyRoute(
|
|
43
|
-
importFn: LazyComponentImport,
|
|
44
|
-
fallback: ReactNode = null,
|
|
45
|
-
): () => ReactNode {
|
|
46
|
-
const Lazy = lazy(importFn);
|
|
47
|
-
const LazyRoute = () =>
|
|
48
|
-
createElement(Suspense, { fallback }, createElement(Lazy));
|
|
49
|
-
LazyRoute.displayName = 'LazyRoute';
|
|
50
|
-
return LazyRoute;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface SinglePageBiomeRoute {
|
|
54
|
-
/** Route path. May include `:param` segments. */
|
|
55
|
-
readonly path: string;
|
|
56
|
-
/** Dynamic import of the page module. */
|
|
57
|
-
readonly page: LazyComponentImport;
|
|
58
|
-
/**
|
|
59
|
-
* Whether the route mounts under `/projects/:projectId` (project-scoped) or
|
|
60
|
-
* at the org root. REQUIRED — mirrors {@link RouteContribution.projectScoped}.
|
|
61
|
-
*/
|
|
62
|
-
readonly projectScoped: boolean;
|
|
63
|
-
/** Access tier required to render. Defaults to `'member'`. */
|
|
64
|
-
readonly access?: RouteAccess;
|
|
65
|
-
/** Suspense fallback for the lazy page. Defaults to `null`. */
|
|
66
|
-
readonly fallback?: ReactNode;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface SinglePageBiomeSpec {
|
|
70
|
-
/** Stable biome id; matches `xema-biome.json` `xema.id`. */
|
|
71
|
-
readonly id: string;
|
|
72
|
-
/** Displayed name. */
|
|
73
|
-
readonly displayName: string;
|
|
74
|
-
/** The single nav item this biome contributes. */
|
|
75
|
-
readonly nav: NavItemContribution;
|
|
76
|
-
/** The single route this biome contributes. */
|
|
77
|
-
readonly route: SinglePageBiomeRoute;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Build a `FrontendBiome` for the common one-nav-item / one-route biome.
|
|
82
|
-
*
|
|
83
|
-
* Most biomes are exactly this shape — a nav entry plus a single lazy page —
|
|
84
|
-
* yet each re-declares the nav array, the route array, the `lazy(...)`, and the
|
|
85
|
-
* `Suspense` wrapper by hand. This builder centralizes all four.
|
|
86
|
-
*
|
|
87
|
-
* @example
|
|
88
|
-
* const biome: FrontendBiomeFactory = () =>
|
|
89
|
-
* defineSinglePageBiome({
|
|
90
|
-
* id: 'org-databases',
|
|
91
|
-
* displayName: 'Organization Databases',
|
|
92
|
-
* nav: { id: 'org-databases', label: 'Databases', route: 'org-databases',
|
|
93
|
-
* icon: Database, section: 'Organization', weight: 30 },
|
|
94
|
-
* route: { path: 'org-databases', projectScoped: false,
|
|
95
|
-
* page: () => import('./pages/DatabasesListPage') },
|
|
96
|
-
* });
|
|
97
|
-
*/
|
|
98
|
-
export function defineSinglePageBiome(spec: SinglePageBiomeSpec): FrontendBiome {
|
|
99
|
-
const { route } = spec;
|
|
100
|
-
const contribution: RouteContribution = {
|
|
101
|
-
path: route.path,
|
|
102
|
-
projectScoped: route.projectScoped,
|
|
103
|
-
element: lazyRoute(route.page, route.fallback ?? null),
|
|
104
|
-
...(route.access ? { access: route.access } : {}),
|
|
105
|
-
};
|
|
106
|
-
return {
|
|
107
|
-
id: spec.id,
|
|
108
|
-
displayName: spec.displayName,
|
|
109
|
-
navItems: [spec.nav],
|
|
110
|
-
routes: [contribution],
|
|
111
|
-
};
|
|
112
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bridge-backed navigation convenience hooks for frontend biomes.
|
|
3
|
-
*
|
|
4
|
-
* These wrap the host-agnostic `HostBridge.navigation` surface so biome code
|
|
5
|
-
* never imports a router primitive (`next/navigation`, `react-router-dom`)
|
|
6
|
-
* directly — the host wires the concrete implementation once on the bridge and
|
|
7
|
-
* every biome reads it through here. Host-framework-agnostic: no Next.js, no
|
|
8
|
-
* React-Router. React itself IS used (these are React hooks).
|
|
9
|
-
*/
|
|
10
|
-
import { useMemo } from 'react';
|
|
11
|
-
|
|
12
|
-
import { useHostBridge } from './host-bridge';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Current URL query string as a read-only `URLSearchParams`, read through the
|
|
16
|
-
* host-provided navigation adapter (`bridge.navigation.useSearchParams`).
|
|
17
|
-
*
|
|
18
|
-
* The one router primitive biomes could not previously reach through the
|
|
19
|
-
* bridge. Use this INSTEAD of `import { useSearchParams } from
|
|
20
|
-
* 'next/navigation'` so biome code stays portable across hosts.
|
|
21
|
-
*/
|
|
22
|
-
export function useSearchParams(): URLSearchParams {
|
|
23
|
-
return useHostBridge().navigation.useSearchParams();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Read a single query-string parameter by key, or `null` when absent.
|
|
28
|
-
*
|
|
29
|
-
* Sugar over {@link useSearchParams} for the common "one param" case (e.g.
|
|
30
|
-
* `?tab=overview`, `?runId=…`) that otherwise forces a direct
|
|
31
|
-
* `next/navigation` import. Returns the first value when a key repeats,
|
|
32
|
-
* matching `URLSearchParams.get` semantics.
|
|
33
|
-
*/
|
|
34
|
-
export function useQueryParam(key: string): string | null {
|
|
35
|
-
const searchParams = useSearchParams();
|
|
36
|
-
return useMemo(() => searchParams.get(key), [searchParams, key]);
|
|
37
|
-
}
|
|
@@ -1,299 +0,0 @@
|
|
|
1
|
-
import { Registry } from '@xemahq/registry';
|
|
2
|
-
import { useSyncExternalStore } from 'react';
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
DuplicateContributionPolicy,
|
|
6
|
-
createContributionRegistry,
|
|
7
|
-
} from '../contribution-registry';
|
|
8
|
-
|
|
9
|
-
import type {
|
|
10
|
-
FrontendBiome,
|
|
11
|
-
FrontendBiomeFactory,
|
|
12
|
-
OutputRendererContribution,
|
|
13
|
-
PanelContribution,
|
|
14
|
-
} from './frontend-biome';
|
|
15
|
-
import type { HostBridge } from './host-bridge';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Singleton frontend biome registry. Populated at app bootstrap by
|
|
19
|
-
* `registerFrontendBiome()` (factory form) or `registerFrontendBiomeInstance()`
|
|
20
|
-
* (already-built form). Bootstrap order: fetch enabled biome ids from
|
|
21
|
-
* `GET /platform/biomes/web`, dynamically `import()` each biome module
|
|
22
|
-
* from the auto-generated loader map (`xema:web-biome-loaders`), then
|
|
23
|
-
* call its default export with the host's concrete `HostBridge` and
|
|
24
|
-
* register the result.
|
|
25
|
-
*
|
|
26
|
-
* Reactivity: components consume registry data via `useBiomeRegistryRevision()`
|
|
27
|
-
* (or `<BiomeSlot>` / `useBiomeRoutes()` which use it internally) so a
|
|
28
|
-
* re-render fires automatically when a biome registers or unregisters —
|
|
29
|
-
* no race between Sidebar mount and async bootstrap, no manual subscription
|
|
30
|
-
* wiring per consumer.
|
|
31
|
-
*
|
|
32
|
-
* Master-disable: hosts that embed `@xemahq/ui-kernel` without
|
|
33
|
-
* participating in Xema's biome ecosystem flip
|
|
34
|
-
* `biomeRegistry.setEnabled(false)` (typically via
|
|
35
|
-
* `<BiomeHostProvider biomes="disabled">`). When disabled, every
|
|
36
|
-
* register/unregister/bootstrap call is a no-op — a single flag controls
|
|
37
|
-
* the entire surface.
|
|
38
|
-
*/
|
|
39
|
-
class FrontendBiomeRegistry extends Registry<
|
|
40
|
-
string,
|
|
41
|
-
FrontendBiome & { key: string }
|
|
42
|
-
> {
|
|
43
|
-
private revisionCount = 0;
|
|
44
|
-
private readonly listeners = new Set<() => void>();
|
|
45
|
-
/**
|
|
46
|
-
* Biome ids whose `init()` callback has been invoked. Tracked
|
|
47
|
-
* separately from the registry entries so `init()` runs exactly once
|
|
48
|
-
* per biome per SPA session even if `registerBiome` is called
|
|
49
|
-
* multiple times (HMR, strict-mode double-mount, retries). Biome
|
|
50
|
-
* authors get an at-most-once execution guarantee.
|
|
51
|
-
*/
|
|
52
|
-
private readonly initialized = new Set<string>();
|
|
53
|
-
private enabled = true;
|
|
54
|
-
|
|
55
|
-
constructor() {
|
|
56
|
-
super({ name: 'FrontendBiomeRegistry' });
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
registerBiome(biome: FrontendBiome): void {
|
|
60
|
-
if (!this.enabled) {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
this.register({ ...biome, key: biome.id });
|
|
64
|
-
this.revisionCount += 1;
|
|
65
|
-
this.notify();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Invoke a biome's `init()` callback exactly once per SPA session.
|
|
70
|
-
* The caller (bootstrap) is responsible for catching/logging promise
|
|
71
|
-
* rejections — the registry intentionally does not swallow them.
|
|
72
|
-
*/
|
|
73
|
-
async runInit(biomeId: string, bridge: HostBridge): Promise<void> {
|
|
74
|
-
const entry = this.get(biomeId);
|
|
75
|
-
if (!entry || !entry.init) {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
if (this.initialized.has(biomeId)) {
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
this.initialized.add(biomeId);
|
|
82
|
-
await entry.init({ bridge, biomeId });
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
unregisterBiome(id: string): boolean {
|
|
86
|
-
if (!this.enabled) {
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
const entry = this.get(id);
|
|
90
|
-
const removed = this.unregister(id);
|
|
91
|
-
if (removed) {
|
|
92
|
-
this.revisionCount += 1;
|
|
93
|
-
this.initialized.delete(id);
|
|
94
|
-
if (entry?.dispose) {
|
|
95
|
-
// Fire-and-forget; teardown errors are logged by callers but
|
|
96
|
-
// never block the unregister path — disposal is best-effort
|
|
97
|
-
// because the biome is being torn down anyway.
|
|
98
|
-
void Promise.resolve(entry.dispose()).catch((error: unknown) => {
|
|
99
|
-
// Surface for devtools without crashing the unregister flow.
|
|
100
|
-
console.error(
|
|
101
|
-
`[xema-ui-kernel] dispose() failed for '${id}':`,
|
|
102
|
-
error,
|
|
103
|
-
);
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
this.notify();
|
|
107
|
-
}
|
|
108
|
-
return removed;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Snapshot of currently-registered biome ids. Useful for the admin UI
|
|
113
|
-
* to detect "registered in this session" vs "enabled in catalog".
|
|
114
|
-
*/
|
|
115
|
-
snapshot(): readonly string[] {
|
|
116
|
-
return this.list().map((b) => b.id);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
setEnabled(enabled: boolean): void {
|
|
120
|
-
if (this.enabled === enabled) {
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
this.enabled = enabled;
|
|
124
|
-
if (!enabled) {
|
|
125
|
-
// When master-disabling, drop any previously-registered biomes so
|
|
126
|
-
// BiomeSlot/useBiomeRoutes consumers re-render to empty.
|
|
127
|
-
const had = this.size() > 0;
|
|
128
|
-
this.clear();
|
|
129
|
-
this.initialized.clear();
|
|
130
|
-
if (had) {
|
|
131
|
-
this.revisionCount += 1;
|
|
132
|
-
this.notify();
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
isEnabled(): boolean {
|
|
138
|
-
return this.enabled;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
revision(): number {
|
|
142
|
-
return this.revisionCount;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Subscribe to revision-counter changes. Returns an unsubscribe
|
|
147
|
-
* function. Used internally by `useBiomeRegistryRevision()` (via
|
|
148
|
-
* `useSyncExternalStore`) so components automatically re-render when
|
|
149
|
-
* a biome is added or removed.
|
|
150
|
-
*/
|
|
151
|
-
subscribe(listener: () => void): () => void {
|
|
152
|
-
this.listeners.add(listener);
|
|
153
|
-
return () => {
|
|
154
|
-
this.listeners.delete(listener);
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
panelsForSlot(slot: string): readonly PanelContribution[] {
|
|
159
|
-
if (!this.enabled) {
|
|
160
|
-
return [];
|
|
161
|
-
}
|
|
162
|
-
const out: PanelContribution[] = [];
|
|
163
|
-
for (const biome of this.list()) {
|
|
164
|
-
for (const panel of biome.panels ?? []) {
|
|
165
|
-
if (panel.slot === slot) {
|
|
166
|
-
out.push(panel);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
return out.sort((a, b) => (a.weight ?? 100) - (b.weight ?? 100));
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Flatten every registered biome's `outputRenderers[]` contributions
|
|
175
|
-
* into a `(outputKind → factory)` map. The host's renderer registry
|
|
176
|
-
* overlays this on top of the built-in set so biome-defined kinds
|
|
177
|
-
* light up the moment the biome's frontend loads. Duplicate kinds
|
|
178
|
-
* across biomes are surfaced as a console error and the first
|
|
179
|
-
* registration wins (deterministic + fail-loud, not silent).
|
|
180
|
-
*
|
|
181
|
-
* Built on the shared `createContributionRegistry` core — the same
|
|
182
|
-
* closed+open two-layer bookkeeping the ChatWidget, reply-variant and
|
|
183
|
-
* Vista-surface registries use. This overlay is rebuilt on every call
|
|
184
|
-
* (it is read during render), so it takes the `FirstWins` duplicate
|
|
185
|
-
* policy rather than `Throw`: a duplicate must not take down a render.
|
|
186
|
-
* `FirstWins` still REQUIRES the `onDuplicate` reporter below — the
|
|
187
|
-
* rejected contribution is never dropped silently.
|
|
188
|
-
*/
|
|
189
|
-
outputRenderers(): ReadonlyMap<string, OutputRendererContribution> {
|
|
190
|
-
if (!this.enabled) {
|
|
191
|
-
return new Map<string, OutputRendererContribution>();
|
|
192
|
-
}
|
|
193
|
-
// `onDuplicate` fires SYNCHRONOUSLY from inside `register()`, so this
|
|
194
|
-
// cursor always names the biome whose contribution was rejected — the
|
|
195
|
-
// contribution object itself does not carry its owner.
|
|
196
|
-
let contributingBiomeId = '';
|
|
197
|
-
const overlay = createContributionRegistry<OutputRendererContribution>({
|
|
198
|
-
name: 'biome outputRenderer',
|
|
199
|
-
duplicatePolicy: DuplicateContributionPolicy.FirstWins,
|
|
200
|
-
onDuplicate: (outputKind) => {
|
|
201
|
-
console.error(
|
|
202
|
-
`[xema-ui-kernel] duplicate outputRenderer for kind '${outputKind}' ` +
|
|
203
|
-
`from biome '${contributingBiomeId}'; first registration wins.`,
|
|
204
|
-
);
|
|
205
|
-
},
|
|
206
|
-
});
|
|
207
|
-
for (const biome of this.list()) {
|
|
208
|
-
contributingBiomeId = biome.id;
|
|
209
|
-
for (const entry of biome.outputRenderers ?? []) {
|
|
210
|
-
overlay.register(entry.outputKind, entry);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
return overlay.entries();
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
private notify(): void {
|
|
217
|
-
for (const listener of this.listeners) {
|
|
218
|
-
listener();
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export const biomeRegistry = new FrontendBiomeRegistry();
|
|
224
|
-
|
|
225
|
-
function isFrontendBiome(value: unknown): value is FrontendBiome {
|
|
226
|
-
return (
|
|
227
|
-
value !== null &&
|
|
228
|
-
typeof value === 'object' &&
|
|
229
|
-
'id' in value &&
|
|
230
|
-
typeof value.id === 'string' &&
|
|
231
|
-
'displayName' in value &&
|
|
232
|
-
typeof value.displayName === 'string'
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Register an already-built `FrontendBiome` instance. Prefer
|
|
238
|
-
* `registerFrontendBiome(factory, bridge)` for biomes shipped via
|
|
239
|
-
* the factory contract.
|
|
240
|
-
*/
|
|
241
|
-
export function registerFrontendBiomeInstance(biome: FrontendBiome): void {
|
|
242
|
-
biomeRegistry.registerBiome(biome);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Invoke a `FrontendBiomeFactory` with the host's `HostBridge` and
|
|
247
|
-
* register the resulting `FrontendBiome`. This is the standard
|
|
248
|
-
* registration path for biomes shipped as workspace packages.
|
|
249
|
-
*
|
|
250
|
-
* Fail-fast: a factory that returns a non-`FrontendBiome` value, or
|
|
251
|
-
* that produces a duplicate id, propagates the underlying registry
|
|
252
|
-
* error — no swallowing.
|
|
253
|
-
*/
|
|
254
|
-
export function registerFrontendBiome(
|
|
255
|
-
factory: FrontendBiomeFactory,
|
|
256
|
-
bridge: HostBridge,
|
|
257
|
-
): void {
|
|
258
|
-
if (!biomeRegistry.isEnabled()) {
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
const biome: unknown = factory(bridge);
|
|
262
|
-
if (!isFrontendBiome(biome)) {
|
|
263
|
-
throw new Error(
|
|
264
|
-
'[xema-ui-kernel] FrontendBiomeFactory returned a non-FrontendBiome value. ' +
|
|
265
|
-
'A biome module must default-export `(bridge) => FrontendBiome`.',
|
|
266
|
-
);
|
|
267
|
-
}
|
|
268
|
-
biomeRegistry.registerBiome(biome);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Unregister a biome by id. Triggered by the admin UI's runtime
|
|
273
|
-
* disable flow so BiomeSlot / useBiomeRoutes consumers reflow without
|
|
274
|
-
* a page reload. Returns `true` if a registration was removed.
|
|
275
|
-
*/
|
|
276
|
-
export function unregisterFrontendBiome(id: string): boolean {
|
|
277
|
-
return biomeRegistry.unregisterBiome(id);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* React hook that subscribes the calling component to the biome
|
|
282
|
-
* registry's revision counter. Returns the current revision (a number
|
|
283
|
-
* that increments by 1 on every biome registration or unregistration).
|
|
284
|
-
* Use the return value as a `useMemo` dep when computing data derived
|
|
285
|
-
* from the registry — the memo recomputes on every change.
|
|
286
|
-
*
|
|
287
|
-
* Implemented with `useSyncExternalStore` so it integrates cleanly with
|
|
288
|
-
* React 18 concurrent rendering and SSR.
|
|
289
|
-
*
|
|
290
|
-
* `<BiomeSlot>` uses this hook internally so any host page that mounts
|
|
291
|
-
* a slot re-renders automatically when biomes arrive or leave.
|
|
292
|
-
*/
|
|
293
|
-
export function useBiomeRegistryRevision(): number {
|
|
294
|
-
return useSyncExternalStore(
|
|
295
|
-
(listener) => biomeRegistry.subscribe(listener),
|
|
296
|
-
() => biomeRegistry.revision(),
|
|
297
|
-
() => biomeRegistry.revision(),
|
|
298
|
-
);
|
|
299
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { useHostBridge } from './host-bridge';
|
|
2
|
-
|
|
3
|
-
export interface ProjectScope {
|
|
4
|
-
readonly orgId: string | null;
|
|
5
|
-
readonly projectId: string | null;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface RequiredProjectScope {
|
|
9
|
-
readonly orgId: string;
|
|
10
|
-
readonly projectId: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function normalize(value: string | null | undefined): string | null {
|
|
14
|
-
if (typeof value !== 'string') {
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
const trimmed = value.trim();
|
|
18
|
-
return trimmed.length > 0 ? trimmed : null;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function useProjectScope(): ProjectScope {
|
|
22
|
-
const bridge = useHostBridge();
|
|
23
|
-
const params = bridge.navigation.useRouteParams<{ projectId?: string }>();
|
|
24
|
-
const orgId = normalize(bridge.auth.getOrgId());
|
|
25
|
-
const projectId =
|
|
26
|
-
normalize(params.projectId) ?? normalize(bridge.auth.getProjectId());
|
|
27
|
-
return { orgId, projectId };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function useRequiredScope(): RequiredProjectScope {
|
|
31
|
-
const { orgId, projectId } = useProjectScope();
|
|
32
|
-
if (!orgId || !projectId) {
|
|
33
|
-
throw new Error(
|
|
34
|
-
`[xema-ui-kernel] useRequiredScope() requires both orgId and projectId, ` +
|
|
35
|
-
`got orgId=${orgId ?? 'null'} projectId=${projectId ?? 'null'}. ` +
|
|
36
|
-
`Render this component under <ScopeGuard> or use useProjectScope() for ` +
|
|
37
|
-
`org-scoped surfaces.`,
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
return { orgId, projectId };
|
|
41
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bridge-backed org/project scope hooks for frontend biomes.
|
|
3
|
-
*
|
|
4
|
-
* Biomes need the active `{ orgId, projectId }` everywhere they call a scoped
|
|
5
|
-
* API or build a scoped query key. Today each biome rolls its own read — some
|
|
6
|
-
* pull `bridge.auth.getOrgId()`, some read `projectId` off route params, some
|
|
7
|
-
* mix both — which drifts and quietly produces null/undefined holes. This
|
|
8
|
-
* module collapses those into ONE normalized read and ONE guard component.
|
|
9
|
-
*
|
|
10
|
-
* Host-framework-agnostic: it only reads the host-implemented bridge surface
|
|
11
|
-
* (`auth.getOrgId` / `auth.getProjectId` and `navigation.useRouteParams`). No
|
|
12
|
-
* Next.js, no React-Router.
|
|
13
|
-
*/
|
|
14
|
-
import { Fragment, type ReactElement, type ReactNode } from 'react';
|
|
15
|
-
|
|
16
|
-
import { useProjectScope, type ProjectScope } from './biome-scope-hooks';
|
|
17
|
-
|
|
18
|
-
export interface ScopeGuardProps {
|
|
19
|
-
/**
|
|
20
|
-
* What to render while the scope is incomplete. Defaults to `null` (render
|
|
21
|
-
* nothing) — pass a skeleton or an empty-state to be explicit.
|
|
22
|
-
*/
|
|
23
|
-
readonly fallback?: ReactNode;
|
|
24
|
-
/**
|
|
25
|
-
* When `true` (the default) both `orgId` AND `projectId` are required. Set
|
|
26
|
-
* `false` to gate only on `orgId` — for org-scoped surfaces that still want
|
|
27
|
-
* to wait for an active org before rendering.
|
|
28
|
-
*/
|
|
29
|
-
readonly requireProject?: boolean;
|
|
30
|
-
/**
|
|
31
|
-
* Children to render once the scope is complete. May be a render-prop that
|
|
32
|
-
* receives the resolved {@link ProjectScope}, or plain nodes.
|
|
33
|
-
*/
|
|
34
|
-
readonly children: ReactNode | ((scope: ProjectScope) => ReactNode);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Gate a subtree on a complete active scope. Renders `fallback` until the
|
|
39
|
-
* required scope fields are present, then renders `children` (passing the
|
|
40
|
-
* resolved scope when `children` is a render-prop). Collapses the repeated
|
|
41
|
-
* `if (!orgId || !projectId) return null` guard biome pages hand-write.
|
|
42
|
-
*/
|
|
43
|
-
export function ScopeGuard(props: ScopeGuardProps): ReactElement {
|
|
44
|
-
const { fallback = null, requireProject = true, children } = props;
|
|
45
|
-
const scope = useProjectScope();
|
|
46
|
-
const ready = Boolean(scope.orgId) && (!requireProject || Boolean(scope.projectId));
|
|
47
|
-
if (!ready) {
|
|
48
|
-
return <Fragment>{fallback}</Fragment>;
|
|
49
|
-
}
|
|
50
|
-
const resolved = typeof children === 'function' ? children(scope) : children;
|
|
51
|
-
return <Fragment>{resolved}</Fragment>;
|
|
52
|
-
}
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bridge-backed scoped-fetch helpers for frontend biomes.
|
|
3
|
-
*
|
|
4
|
-
* Every scoped biome data hook hand-repeats the same three things: build the
|
|
5
|
-
* org/project request headers (`reqOpts`), gate the query on the scope being
|
|
6
|
-
* present (`enabled`), and fold org/project into the query key so cache entries
|
|
7
|
-
* don't bleed across tenants/projects. This module folds those into
|
|
8
|
-
* `useScopedClient()` + `useScopedQuery()` so a biome hook becomes one call.
|
|
9
|
-
*
|
|
10
|
-
* Host-framework-agnostic. TanStack Query is an OPTIONAL peer dependency — it
|
|
11
|
-
* is imported only here, behind the `useScopedQuery` surface, so biomes that
|
|
12
|
-
* don't use it pay nothing and the kernel never hard-requires it.
|
|
13
|
-
*/
|
|
14
|
-
import {
|
|
15
|
-
useQuery,
|
|
16
|
-
type QueryKey,
|
|
17
|
-
type UseQueryOptions,
|
|
18
|
-
type UseQueryResult,
|
|
19
|
-
} from '@tanstack/react-query';
|
|
20
|
-
|
|
21
|
-
import { useProjectScope, type ProjectScope } from './biome-scope-hooks';
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* The canonical Xema-platform tenant header. Every platform biome-api reads it
|
|
25
|
-
* via `RequestContext`. Mirrors the host's `reqOpts` (see
|
|
26
|
-
* `submodules/xema-host-web/src/lib/api/orval/configure-clients.ts`).
|
|
27
|
-
*/
|
|
28
|
-
const ORG_HEADER = 'X-Xema-Org-Id';
|
|
29
|
-
const PROJECT_HEADER = 'X-Project-Id';
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Build a `RequestInit` carrying the active org/project headers, to pass as the
|
|
33
|
-
* per-call `options` argument of any Orval endpoint function. Single source of
|
|
34
|
-
* the header names so biomes stop hand-rolling `reqOpts` per package.
|
|
35
|
-
*/
|
|
36
|
-
export function buildScopedRequestInit(scope: ProjectScope): RequestInit {
|
|
37
|
-
const headers: Record<string, string> = {};
|
|
38
|
-
if (scope.orgId) {
|
|
39
|
-
headers[ORG_HEADER] = scope.orgId;
|
|
40
|
-
}
|
|
41
|
-
if (scope.projectId) {
|
|
42
|
-
headers[PROJECT_HEADER] = scope.projectId;
|
|
43
|
-
}
|
|
44
|
-
return { headers };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface ScopedClient {
|
|
48
|
-
/** The resolved active scope (null-normalized). */
|
|
49
|
-
readonly scope: ProjectScope;
|
|
50
|
-
/**
|
|
51
|
-
* Per-call Orval options bound to the active scope. Pass straight into an
|
|
52
|
-
* endpoint function: `await fooControllerList(client.reqOpts())`.
|
|
53
|
-
*/
|
|
54
|
-
reqOpts(): RequestInit;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* A scope-bound request-options factory. Reads the active `{ orgId, projectId }`
|
|
59
|
-
* from the bridge and exposes a `reqOpts()` that stamps the canonical tenant
|
|
60
|
-
* headers — the per-biome `reqOpts(orgId, projectId)` helper, centralized.
|
|
61
|
-
*/
|
|
62
|
-
export function useScopedClient(): ScopedClient {
|
|
63
|
-
const scope = useProjectScope();
|
|
64
|
-
return {
|
|
65
|
-
scope,
|
|
66
|
-
reqOpts: () => buildScopedRequestInit(scope),
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Extra options accepted by {@link useScopedQuery} on top of the underlying
|
|
72
|
-
* TanStack `useQuery` options.
|
|
73
|
-
*/
|
|
74
|
-
export interface UseScopedQueryExtras {
|
|
75
|
-
/**
|
|
76
|
-
* When `true` (the default) the query is gated on `orgId` AND `projectId`.
|
|
77
|
-
* Set `false` to gate only on `orgId` — for org-scoped reads that don't need
|
|
78
|
-
* a project. The gate ANDs with any caller-supplied `enabled`.
|
|
79
|
-
*/
|
|
80
|
-
readonly requireProject?: boolean;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* The `queryFn` shape `useScopedQuery` calls — it receives the resolved scope
|
|
85
|
-
* plus the scope-bound `reqOpts()` so the body never re-reads the bridge.
|
|
86
|
-
*/
|
|
87
|
-
export type ScopedQueryFn<TData> = (ctx: {
|
|
88
|
-
readonly scope: ProjectScope;
|
|
89
|
-
reqOpts(): RequestInit;
|
|
90
|
-
}) => TData | Promise<TData>;
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Scope-aware `useQuery`. Folds the active org/project into:
|
|
94
|
-
* - the **query key** — `key` is prefixed with `['scope', orgId, projectId]`
|
|
95
|
-
* so cache entries never leak across tenants/projects;
|
|
96
|
-
* - the **`enabled` gate** — ANDed with the scope-present check (and any
|
|
97
|
-
* caller `enabled`), so the query stays idle until the scope resolves;
|
|
98
|
-
* - the **`queryFn` context** — the resolved scope + a bound `reqOpts()`.
|
|
99
|
-
*
|
|
100
|
-
* Everything else (`staleTime`, `select`, `retry`, …) passes through to the
|
|
101
|
-
* underlying `useQuery`.
|
|
102
|
-
*/
|
|
103
|
-
export function useScopedQuery<
|
|
104
|
-
TQueryFnData,
|
|
105
|
-
TError = Error,
|
|
106
|
-
TData = TQueryFnData,
|
|
107
|
-
>(
|
|
108
|
-
key: QueryKey,
|
|
109
|
-
queryFn: ScopedQueryFn<TQueryFnData>,
|
|
110
|
-
options: Omit<
|
|
111
|
-
UseQueryOptions<TQueryFnData, TError, TData>,
|
|
112
|
-
'queryKey' | 'queryFn'
|
|
113
|
-
> &
|
|
114
|
-
UseScopedQueryExtras = {},
|
|
115
|
-
): UseQueryResult<TData, TError> {
|
|
116
|
-
const scope = useProjectScope();
|
|
117
|
-
const { requireProject = true, enabled, ...rest } = options;
|
|
118
|
-
|
|
119
|
-
const scopeReady =
|
|
120
|
-
Boolean(scope.orgId) && (!requireProject || Boolean(scope.projectId));
|
|
121
|
-
|
|
122
|
-
return useQuery<TQueryFnData, TError, TData>({
|
|
123
|
-
...rest,
|
|
124
|
-
queryKey: ['scope', scope.orgId, scope.projectId, ...key],
|
|
125
|
-
enabled: scopeReady && (enabled ?? true),
|
|
126
|
-
queryFn: () =>
|
|
127
|
-
Promise.resolve(
|
|
128
|
-
queryFn({
|
|
129
|
-
scope,
|
|
130
|
-
reqOpts: () => buildScopedRequestInit(scope),
|
|
131
|
-
}),
|
|
132
|
-
),
|
|
133
|
-
});
|
|
134
|
-
}
|