@savvifi/meridian-aion-web 0.1.0 → 0.2.1
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/README.md +71 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/render-client.d.ts +8 -2
- package/dist/render-client.js +3 -3
- package/dist/render-server.d.ts +25 -0
- package/dist/render-server.js +28 -1
- package/package.json +5 -5
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# @savvifi/meridian-aion-web
|
|
2
|
+
|
|
3
|
+
The **aion/web ↔ meridian-ux render seam**. Lets an SSR host (e.g. `aion/web` on
|
|
4
|
+
Hono + Vite classic SSR) render aion **entity routes** through meridian-ux —
|
|
5
|
+
`ViewDescriptor → muiKit` — instead of the legacy React entity-page path, behind a
|
|
6
|
+
per-entity feature flag so parity can be proven before switching.
|
|
7
|
+
|
|
8
|
+
Meridian owns everything from the composition down (project → render → paginate);
|
|
9
|
+
the host keeps the SSR harness, the app shell, navigation, providers, and the
|
|
10
|
+
session-bound tRPC caller.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
pnpm add @savvifi/meridian-aion-web
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Peers the host provides: `react` `react-dom` (19), `@mui/material` (7),
|
|
19
|
+
`@emotion/react` `@emotion/styled` (11), and `@aion/entity-route-runtime` (the
|
|
20
|
+
composition loader + route parser — the host's version wins).
|
|
21
|
+
|
|
22
|
+
## Server (SSR)
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { renderMeridianEntityView, NotFoundSignal } from "@savvifi/meridian-aion-web/server";
|
|
26
|
+
|
|
27
|
+
const { node, dehydrated } = await renderMeridianEntityView({
|
|
28
|
+
pathname, // "/entities/products" | "/entities/products/id/123"
|
|
29
|
+
caller, // session-bound createCaller -> caller.entities.ops
|
|
30
|
+
graph, // ctx.graph -> composition loader's systemGraphFactory
|
|
31
|
+
mode, // "light" | "dark" (default "light")
|
|
32
|
+
// notFound: "throw" (default) throws NotFoundSignal; "return" -> { node: null }
|
|
33
|
+
});
|
|
34
|
+
// stream `node` as the page content; embed `dehydrated` = { viewJson, initialData }
|
|
35
|
+
// in your boot script for the client.
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`renderMeridianEntityView` parses the route, loads the installed composition,
|
|
39
|
+
projects it to a `ViewDescriptor`, **pre-fetches page-0** through the caller (so the
|
|
40
|
+
SSR HTML carries real rows), renders it through `muiKit` + the `savviSkin` theme,
|
|
41
|
+
and returns the node plus the dehydrated payload.
|
|
42
|
+
|
|
43
|
+
## Client (hydration)
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import { MeridianEntityView } from "@savvifi/meridian-aion-web/client";
|
|
47
|
+
import { createEntityOpsInvoker } from "@savvifi/meridian-aion-web";
|
|
48
|
+
|
|
49
|
+
// invoker over the app's browser tRPC client:
|
|
50
|
+
const invoker = createEntityOpsInvoker(client.entities.ops);
|
|
51
|
+
|
|
52
|
+
<MeridianEntityView
|
|
53
|
+
viewJson={dehydrated.viewJson} // a JSON value, not a string
|
|
54
|
+
initialData={dehydrated.initialData}
|
|
55
|
+
invoker={invoker}
|
|
56
|
+
mode={mode}
|
|
57
|
+
/>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`usePagedRows` (in `@savvifi/meridian-web-react`) seeds page-0 from `initialData`
|
|
61
|
+
and skips the initial refetch, so hydration matches the server render with no flash.
|
|
62
|
+
|
|
63
|
+
## Exports
|
|
64
|
+
|
|
65
|
+
- `@savvifi/meridian-aion-web/server` — `renderMeridianEntityView`, `NotFoundSignal`, `isNotFoundSignal`, `savviSkin`
|
|
66
|
+
- `@savvifi/meridian-aion-web/client` — `MeridianEntityView`, `savviSkin`
|
|
67
|
+
- root — all of the above + `createEntityOpsInvoker`
|
|
68
|
+
|
|
69
|
+
## License
|
|
70
|
+
|
|
71
|
+
UNLICENSED — © savvifi.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export { renderMeridianEntityView, type RenderMeridianEntityViewInput, type RenderMeridianEntityViewResult, type MeridianDehydratedView, type EntityCaller, } from "./render-server.js";
|
|
1
|
+
export { renderMeridianEntityView, renderCrdLayout, type RenderMeridianEntityViewInput, type RenderMeridianEntityViewResult, type MeridianDehydratedView, type EntityCaller, type RenderCrdLayoutInput, type LayoutFetcher, type CrdRpcInvoker, } from "./render-server.js";
|
|
2
2
|
export { MeridianEntityView, type MeridianEntityViewProps } from "./render-client.js";
|
|
3
3
|
export { savviSkin } from "./savvi-skin.js";
|
|
4
4
|
export { NotFoundSignal, isNotFoundSignal } from "./not-found.js";
|
|
5
5
|
export { createEntityOpsInvoker } from "@savvifi/meridian-aion-projection";
|
|
6
|
+
export type { MeridianActionHandler } from "@savvifi/meridian-web-react";
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// pre-fetch page-0 -> render + dehydrate). Client: MeridianEntityView (hydrate).
|
|
5
5
|
// Plus savviSkin (the brand Theme proto) and createEntityOpsInvoker (build a
|
|
6
6
|
// meridian RpcInvoker over a tRPC caller/client's entities.ops).
|
|
7
|
-
export { renderMeridianEntityView, } from "./render-server.js";
|
|
7
|
+
export { renderMeridianEntityView, renderCrdLayout, } from "./render-server.js";
|
|
8
8
|
export { MeridianEntityView } from "./render-client.js";
|
|
9
9
|
export { savviSkin } from "./savvi-skin.js";
|
|
10
10
|
export { NotFoundSignal, isNotFoundSignal } from "./not-found.js";
|
package/dist/render-client.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
2
|
import type { RpcInvoker } from "@savvifi/meridian-schemas/uiview";
|
|
3
|
-
import { type MeridianInitialData } from "@savvifi/meridian-web-react";
|
|
3
|
+
import { type MeridianActionHandler, type MeridianInitialData } from "@savvifi/meridian-web-react";
|
|
4
4
|
export interface MeridianEntityViewProps {
|
|
5
5
|
/** The dehydrated ViewDescriptor JSON value (from the server render). */
|
|
6
6
|
viewJson: unknown;
|
|
@@ -10,5 +10,11 @@ export interface MeridianEntityViewProps {
|
|
|
10
10
|
invoker: RpcInvoker;
|
|
11
11
|
/** Resolved color mode; must match the server render for clean hydration. */
|
|
12
12
|
mode?: "light" | "dark";
|
|
13
|
+
/**
|
|
14
|
+
* Host handler for no-call actions (view_details / edit / create …). The host
|
|
15
|
+
* wires this to its router — e.g. `(actionId, entityType, entityId) => nav(...)`.
|
|
16
|
+
* Client-only (SSR renders the same markup; onClick handlers aren't serialized).
|
|
17
|
+
*/
|
|
18
|
+
onAction?: MeridianActionHandler;
|
|
13
19
|
}
|
|
14
|
-
export declare function MeridianEntityView({ viewJson, initialData, invoker, mode, }: MeridianEntityViewProps): ReactNode;
|
|
20
|
+
export declare function MeridianEntityView({ viewJson, initialData, invoker, mode, onAction, }: MeridianEntityViewProps): ReactNode;
|
package/dist/render-client.js
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
import { createElement } from "react";
|
|
7
7
|
import { fromJson } from "@bufbuild/protobuf";
|
|
8
8
|
import { ViewDescriptorSchema } from "@savvifi/meridian-proto-ts/proto/view_pb.js";
|
|
9
|
-
import { ViewRenderer } from "@savvifi/meridian-web-react";
|
|
9
|
+
import { ViewRenderer, } from "@savvifi/meridian-web-react";
|
|
10
10
|
import { MeridianMuiProvider } from "@savvifi/meridian-mui-kit";
|
|
11
11
|
import { savviSkin } from "./savvi-skin.js";
|
|
12
|
-
export function MeridianEntityView({ viewJson, initialData, invoker, mode = "light", }) {
|
|
12
|
+
export function MeridianEntityView({ viewJson, initialData, invoker, mode = "light", onAction, }) {
|
|
13
13
|
const view = fromJson(ViewDescriptorSchema, viewJson);
|
|
14
|
-
return createElement(MeridianMuiProvider, { invoker, theme: savviSkin, mode }, createElement(ViewRenderer, { view, initialData }));
|
|
14
|
+
return createElement(MeridianMuiProvider, { invoker, theme: savviSkin, mode, onAction }, createElement(ViewRenderer, { view, initialData }));
|
|
15
15
|
}
|
package/dist/render-server.d.ts
CHANGED
|
@@ -33,3 +33,28 @@ export interface RenderMeridianEntityViewResult {
|
|
|
33
33
|
dehydrated: MeridianDehydratedView | null;
|
|
34
34
|
}
|
|
35
35
|
export declare function renderMeridianEntityView(input: RenderMeridianEntityViewInput): Promise<RenderMeridianEntityViewResult>;
|
|
36
|
+
/** The host-supplied transport that fetches a Layout's ViewDescriptor as proto-JSON.
|
|
37
|
+
* Abstracted so the gRPC client (Connect / grpc-js) is a host adapter, not a dep of
|
|
38
|
+
* this package — it calls `LayoutService.GetLayout({ name })` and returns
|
|
39
|
+
* `toJson(ViewDescriptorSchema, layout.viewDescriptor)`. */
|
|
40
|
+
export type LayoutFetcher = (name: string) => Promise<unknown>;
|
|
41
|
+
/** An RpcInvoker (one method) that dispatches a panel's RpcCall. For CRD layouts the
|
|
42
|
+
* RpcCalls target `meridian.k8s.backend.Kubernetes`, so this must reach the K8s API
|
|
43
|
+
* (or the meridian-k8s backend) rather than the tRPC entity-ops root. */
|
|
44
|
+
export interface CrdRpcInvoker {
|
|
45
|
+
invoke(service: string, method: string, request: object): Promise<object>;
|
|
46
|
+
}
|
|
47
|
+
export interface RenderCrdLayoutInput {
|
|
48
|
+
/** CRD kind id (the CRD name), e.g. `instances.aion.savvifi.com`. */
|
|
49
|
+
kind: string;
|
|
50
|
+
/** Which layout to render. Default "detail". */
|
|
51
|
+
view?: "list" | "detail";
|
|
52
|
+
/** Fetches the ViewDescriptor proto-JSON from the meridian-k8s LayoutService. */
|
|
53
|
+
fetchLayout: LayoutFetcher;
|
|
54
|
+
/** Dispatches the layout's RpcCalls (meridian-k8s / K8s backend). */
|
|
55
|
+
invoker: CrdRpcInvoker;
|
|
56
|
+
/** Resolved color mode; default "light". */
|
|
57
|
+
mode?: "light" | "dark";
|
|
58
|
+
}
|
|
59
|
+
/** Fetch a meridian-k8s Layout and render it exactly like a projected entity view. */
|
|
60
|
+
export declare function renderCrdLayout(input: RenderCrdLayoutInput): Promise<RenderMeridianEntityViewResult>;
|
package/dist/render-server.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// The host owns the SSR harness (renderToPipeableStream), the session-bound
|
|
7
7
|
// caller, and the graph; meridian owns everything from the composition down.
|
|
8
8
|
import { createElement } from "react";
|
|
9
|
-
import { toJson } from "@bufbuild/protobuf";
|
|
9
|
+
import { fromJson, toJson } from "@bufbuild/protobuf";
|
|
10
10
|
import { ViewDescriptorSchema } from "@savvifi/meridian-proto-ts/proto/view_pb.js";
|
|
11
11
|
import { createEntityOpsInvoker, projectEntityView, } from "@savvifi/meridian-aion-projection";
|
|
12
12
|
import { buildPageRequest, readPage, ViewRenderer, } from "@savvifi/meridian-web-react";
|
|
@@ -72,3 +72,30 @@ export async function renderMeridianEntityView(input) {
|
|
|
72
72
|
dehydrated: { viewJson: toJson(ViewDescriptorSchema, view), initialData },
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
|
+
/** Fetch a meridian-k8s Layout and render it exactly like a projected entity view. */
|
|
76
|
+
export async function renderCrdLayout(input) {
|
|
77
|
+
const { kind, view = "detail", fetchLayout, invoker, mode = "light" } = input;
|
|
78
|
+
const name = `kinds/${kind}/layouts/${view}`;
|
|
79
|
+
const viewJson = await fetchLayout(name);
|
|
80
|
+
// Normalize through the canonical schema — the same message ViewRenderer consumes,
|
|
81
|
+
// whether it was projected (renderMeridianEntityView) or fetched (here).
|
|
82
|
+
const descriptor = fromJson(ViewDescriptorSchema, viewJson);
|
|
83
|
+
// Pre-fetch page-0 for the first table (non-fatal), identical to the entity path.
|
|
84
|
+
const initialData = {};
|
|
85
|
+
const table = firstTablePanel(descriptor);
|
|
86
|
+
if (table?.populate) {
|
|
87
|
+
try {
|
|
88
|
+
const request = buildPageRequest(table.pagination, { page: 0 });
|
|
89
|
+
const response = await invoker.invoke(table.populate.service, table.populate.method, request);
|
|
90
|
+
initialData[`${table.populate.service}.${table.populate.method}`] = readPage(table.pagination, response, table.rowsField);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// leave unseeded
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const node = createElement(MeridianMuiProvider, { invoker, theme: savviSkin, mode }, createElement(ViewRenderer, { view: descriptor, initialData }));
|
|
97
|
+
return {
|
|
98
|
+
node,
|
|
99
|
+
dehydrated: { viewJson: toJson(ViewDescriptorSchema, descriptor), initialData },
|
|
100
|
+
};
|
|
101
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@savvifi/meridian-aion-web",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The aion/web <-> meridian-ux render seam: renderMeridianEntityView (server — parse route, load composition, project to a ViewDescriptor, pre-fetch page-0, render + dehydrate) + MeridianEntityView (client hydration) + savviSkin. Lets an SSR host (e.g. aion/web on Hono+Vite) render entity routes through meridian instead of the legacy React path.",
|
|
6
6
|
"publishConfig": {
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@savvifi/meridian-aion-projection": "^0.2.2",
|
|
40
|
-
"@savvifi/meridian-mui-kit": "^0.
|
|
41
|
-
"@savvifi/meridian-proto-ts": "^0.
|
|
42
|
-
"@savvifi/meridian-schemas": "^0.
|
|
43
|
-
"@savvifi/meridian-web-react": "^0.
|
|
40
|
+
"@savvifi/meridian-mui-kit": "^0.2.0",
|
|
41
|
+
"@savvifi/meridian-proto-ts": "^0.5.0",
|
|
42
|
+
"@savvifi/meridian-schemas": "^0.5.0",
|
|
43
|
+
"@savvifi/meridian-web-react": "^0.4.0",
|
|
44
44
|
"@bufbuild/protobuf": "2.12.1"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|