ra-steward 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 catdarick
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,96 @@
1
- # Temporary Holding Version
1
+ # ra-steward
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Pages, tables, forms and actions for [React Admin](https://marmelab.com/react-admin/),
4
+ described by a steward backend. Add it to an existing admin alongside your own resources.
5
+
6
+ ## Install
7
+
8
+ ```sh
9
+ npm install ra-steward react-admin react react-dom @mui/material @mui/icons-material \
10
+ @emotion/react @emotion/styled @tanstack/react-query react-router-dom react-hook-form
11
+ ```
12
+
13
+ Use React Admin 5.15+, React 18 or 19, MUI 7 or 9, React Router 6.28.1+ or 7,
14
+ TanStack Query 5 and React Hook Form 7.66+. Keep a single compatible installation of each
15
+ peer so the library shares the host's contexts. The package contains ESM JavaScript and
16
+ TypeScript declarations; no TypeScript or JSX compilation of the library is needed.
17
+ It targets ES2022 and is intended for React apps using a bundler, such as Vite or webpack.
18
+
19
+ ## A standalone admin
20
+
21
+ ```tsx
22
+ import { Admin, CustomRoutes } from "react-admin";
23
+ import { createSteward } from "ra-steward";
24
+
25
+ // Create once, outside the component, to keep contexts and version state stable.
26
+ const steward = createSteward({ baseUrl: "/admin" });
27
+
28
+ export const App = () => (
29
+ <Admin dataProvider={steward.dataProvider} layout={steward.Layout}>
30
+ <CustomRoutes>{steward.routes}</CustomRoutes>
31
+ </Admin>
32
+ );
33
+ ```
34
+
35
+ The default routes are `/s/:service/:page` and `/s/:service/:page/:key`. Use
36
+ `routePrefix` to place them elsewhere. `baseUrl` points at the backend's service root,
37
+ not the frontend route. A development proxy or your server must route that path to steward.
38
+
39
+ ## An existing React Admin app
40
+
41
+ ```tsx
42
+ import {
43
+ Admin, combineDataProviders, CustomRoutes, Layout, Menu, Resource,
44
+ type LayoutProps,
45
+ } from "react-admin";
46
+ import { createSteward, StewardMenuItems, StewardPanel } from "ra-steward";
47
+ import { authProvider, hostDataProvider, PostList, readToken } from "./host";
48
+
49
+ const steward = createSteward({
50
+ baseUrl: "/admin",
51
+ routePrefix: "/services",
52
+ headers: () => ({ Authorization: `Bearer ${readToken()}` }),
53
+ });
54
+
55
+ const dataProvider = combineDataProviders(resource =>
56
+ resource === "posts" ? hostDataProvider : steward.dataProvider
57
+ );
58
+
59
+ const HostMenu = () => (
60
+ <Menu>
61
+ <Menu.ResourceItem name="posts" />
62
+ <StewardMenuItems />
63
+ </Menu>
64
+ );
65
+
66
+ const HostLayout = (props: LayoutProps) => (
67
+ <steward.Provider>
68
+ <Layout {...props} menu={HostMenu} />
69
+ <StewardPanel />
70
+ </steward.Provider>
71
+ );
72
+
73
+ export const App = () => (
74
+ <Admin dataProvider={dataProvider} authProvider={authProvider} layout={HostLayout}>
75
+ <Resource name="posts" list={PostList} />
76
+ <CustomRoutes>{steward.routes}</CustomRoutes>
77
+ </Admin>
78
+ );
79
+ ```
80
+
81
+ The host owns authentication, theme, query client and routing. `headers` is called for each
82
+ request so it can read the current token. `StewardMenuItems` adds service navigation;
83
+ `StewardPanel` handles record previews and action navigation. Every steward data-provider
84
+ request carries its location in `meta.steward`; select providers so steward resources reach
85
+ `steward.dataProvider`.
86
+
87
+ For custom widgets and cells, pass `components` and `cells` to `createSteward`. Their props
88
+ are described by the exported `WidgetComponent` and `CellComponent` types. `services`
89
+ optionally limits the services shown; otherwise the backend supplies the list. Protocol
90
+ types, `StewardClient`, `StewardPage`, `StewardTable`, navigation hooks and row metadata
91
+ helpers are also exported from `ra-steward`.
92
+
93
+ Steward's backend `docs/protocol.md` defines the HTTP contract. The backend must provide
94
+ the describe, page, widget, picker and action endpoints used by its pages. Browser apps
95
+ using an absolute backend URL also need the backend's CORS configuration to allow the
96
+ frontend origin and authentication headers.
@@ -0,0 +1,38 @@
1
+ import { type ReactNode } from "react";
2
+ import { type RaRecord } from "react-admin";
3
+ import type { ActionDesc, ActionStates, ButtonDesc, FormErrors, Width } from "./protocol.js";
4
+ /** Where a button stands: on the record of its page, in a row, or in a toolbar of no record. */
5
+ export type Origin = "page" | "row" | "toolbar";
6
+ /** Runs an action and what its answer says; resolves to the errors of the form when it is invalid. */
7
+ export declare const useRunAction: (origin: Origin) => (service: string, action: string, key: string | null | undefined, payload: unknown) => Promise<FormErrors | undefined>;
8
+ /** A button of an action or a link, as the server described it, in its place. */
9
+ export declare const ActionButton: ({ service, button, record, states, origin, size, }: {
10
+ service: string;
11
+ button: ButtonDesc;
12
+ record?: RaRecord;
13
+ states?: ActionStates;
14
+ origin: Origin;
15
+ size?: "small" | "medium";
16
+ }) => import("react").JSX.Element | null;
17
+ /** The dialog of a form: the one thing that opens over the panel, for doing, not for looking. */
18
+ export declare const FormDialog: ({ width, title, onClose, children }: {
19
+ width: Width;
20
+ title: ReactNode;
21
+ onClose: () => void;
22
+ children: ReactNode;
23
+ }) => import("react").JSX.Element;
24
+ /** The form of an action, with its initial values; the server's errors go next to the inputs. */
25
+ export declare const ActionForm: ({ service, action, label, recordKey, width, origin, onClose, }: {
26
+ service: string;
27
+ action: ActionDesc;
28
+ label: string;
29
+ recordKey: string | null;
30
+ width: Width;
31
+ origin: Origin;
32
+ onClose: () => void;
33
+ }) => import("react").JSX.Element;
34
+ export declare const SubmitAction: ({ label, icon, busy }: {
35
+ label: string;
36
+ icon?: string;
37
+ busy: boolean;
38
+ }) => import("react").JSX.Element;
@@ -0,0 +1,32 @@
1
+ import type { FormErrors } from "./protocol.js";
2
+ export interface ClientOptions {
3
+ /** The base path of the services: "/admin". */
4
+ baseUrl: string;
5
+ /** Headers of every request: the user's token, for one. */
6
+ headers?: () => Record<string, string>;
7
+ /** Called with the version every answer carries, to read describe again when it changes. */
8
+ onVersion?: (service: string, version: string) => void;
9
+ }
10
+ /** An answer that is not a success: its status, its message and, for 422, the errors of fields. */
11
+ export declare class StewardError extends Error {
12
+ readonly status: number;
13
+ readonly errors?: FormErrors | undefined;
14
+ constructor(status: number, message: string, errors?: FormErrors | undefined);
15
+ }
16
+ export type Query = Record<string, string | number | undefined | null>;
17
+ /** Requests to the services. */
18
+ export declare class StewardClient {
19
+ readonly options: ClientOptions;
20
+ constructor(options: ClientOptions);
21
+ url(service: string | null, path: string[], query?: Query): string;
22
+ request<T>(service: string | null, path: string[], init?: {
23
+ query?: Query;
24
+ body?: unknown;
25
+ method?: string;
26
+ }): Promise<T>;
27
+ /** A request with the headers of the options; the version of the answer is reported. */
28
+ fetch(url: string, init?: RequestInit): Promise<Response>;
29
+ private serviceOf;
30
+ /** Downloads a file an action produced and gives it to the user to save. */
31
+ download(url: string, filename: string): Promise<void>;
32
+ }
@@ -0,0 +1,102 @@
1
+ import { type ComponentType, type ReactNode } from "react";
2
+ import { type RaRecord } from "react-admin";
3
+ import type { StewardClient } from "./client.js";
4
+ import { type PageRef } from "./peek.js";
5
+ import type { Describe, Target } from "./protocol.js";
6
+ export { parsePeek, PEEK, withPeek, type PageRef } from "./peek.js";
7
+ /** A component of the admin's registry drawing a widget: given the data the server loads for it. */
8
+ export type WidgetComponent = ComponentType<{
9
+ data: unknown;
10
+ record?: RaRecord;
11
+ }>;
12
+ /** A component of the admin's registry drawing a value: given the value and the props of its kind. */
13
+ export type CellComponent = ComponentType<{
14
+ value: unknown;
15
+ props: unknown;
16
+ record: RaRecord;
17
+ }>;
18
+ export interface StewardConfig {
19
+ /** The base path of the services: "/admin". */
20
+ baseUrl: string;
21
+ /** Headers of every request. */
22
+ headers?: () => Record<string, string>;
23
+ /** The services shown; by default those the server names. */
24
+ services?: string[];
25
+ /** The widgets of the admin's registry (`component @"SalesChart"`), by name. */
26
+ components?: Record<string, WidgetComponent>;
27
+ /** The cells of the admin's registry (`customKind @"MapPoint"`), by name. */
28
+ cells?: Record<string, CellComponent>;
29
+ /** The routes of the pages: `${routePrefix}/:service/:page/:key?`; "/s" by default. */
30
+ routePrefix?: string;
31
+ }
32
+ /** The version of describe each answer carries, by service. */
33
+ export declare class Versions {
34
+ private versions;
35
+ private listeners;
36
+ set(service: string, version: string): void;
37
+ get: (service: string) => string | undefined;
38
+ subscribe: (l: () => void) => () => boolean;
39
+ }
40
+ interface StewardValue {
41
+ config: StewardConfig;
42
+ client: StewardClient;
43
+ versions: Versions;
44
+ }
45
+ export declare const StewardProvider: ({ config, client, versions, children }: {
46
+ config: StewardConfig;
47
+ client: StewardClient;
48
+ versions: Versions;
49
+ children: ReactNode;
50
+ }) => import("react").JSX.Element;
51
+ export declare const useSteward: () => StewardValue;
52
+ export declare const describeKey: (service: string) => string[];
53
+ /** The describe of a service: read once, and again when an answer carries a newer version. */
54
+ export declare const useDescribe: (service: string) => import("@tanstack/react-query").UseQueryResult<Describe, Error>;
55
+ /** The services shown: those of the config, or those the server names. */
56
+ export declare const useServices: () => string[];
57
+ export declare const pagePath: (prefix: string, service: string, page: string, key?: string | null) => string;
58
+ /** The place a page is shown in: its service, page and key, whether in the panel, how to close it. */
59
+ export interface PagePlace {
60
+ service: string;
61
+ page: string;
62
+ key: string | null;
63
+ inPanel: boolean;
64
+ close: () => void;
65
+ }
66
+ export declare const PagePlaceContext: import("react").Context<PagePlace | null>;
67
+ export declare const usePagePlace: () => PagePlace | null;
68
+ /** The URL of a route of the app, for a link or a new tab: with the hash of a hash router. */
69
+ export declare const routeHref: (path: string) => string;
70
+ /** Whether a click asks for a new tab: with Ctrl, Cmd or Shift, or with the middle button. */
71
+ export declare const wantsNewTab: (e: {
72
+ ctrlKey: boolean;
73
+ metaKey: boolean;
74
+ shiftKey: boolean;
75
+ button: number;
76
+ }) => boolean;
77
+ /**
78
+ * The panel over the page: the pages it has opened, the last one shown, and its moves. Its pages
79
+ * are in the URL, so the browser's Back goes back in it and a reload keeps it; the history entry
80
+ * tells how many of its pages were opened by entries of their own, which its Back and close undo.
81
+ */
82
+ export declare const usePanel: () => {
83
+ pages: PageRef[];
84
+ /** Opens the panel on a page: from the page under it. */
85
+ open: (page: PageRef) => void;
86
+ /** Opens a page in the panel, after the one shown: from the panel. */
87
+ push: (page: PageRef) => void;
88
+ /** Back to the page before in the panel, or closes it when there is none. */
89
+ back: () => void;
90
+ close: () => void;
91
+ /** Opens the page shown as a page of its own, closing the panel. */
92
+ expand: () => void;
93
+ };
94
+ /**
95
+ * Opens a page from a link, a row, a button or an action's result: always in the panel — after the
96
+ * page shown when it is opened from the panel. Only the menu opens pages in place.
97
+ */
98
+ export declare const useOpenPage: () => (service: string, page: string, key: string | null) => void;
99
+ /** The URL of a page, for a link or a new tab. */
100
+ export declare const usePageHref: () => (service: string, page: string, key: string | null) => string;
101
+ /** The key a target opens, from the record of its place. */
102
+ export declare const targetKey: (target: Target, record?: RaRecord) => string | null;
@@ -0,0 +1,41 @@
1
+ import type { DataProvider, RaRecord } from "react-admin";
2
+ import type { StewardClient } from "./client.js";
3
+ import type { ActionStates, RowMeta } from "./protocol.js";
4
+ /**
5
+ * Where a list or a record comes from, passed as React Admin's `meta`: every answer is the
6
+ * projection of one place of a page.
7
+ */
8
+ export type Place = {
9
+ service: string;
10
+ widget: string;
11
+ of?: string;
12
+ } | {
13
+ service: string;
14
+ widget: string;
15
+ filterOptions: string;
16
+ } | {
17
+ service: string;
18
+ picker: {
19
+ action: string;
20
+ field: string;
21
+ key?: string;
22
+ };
23
+ } | {
24
+ service: string;
25
+ page: string;
26
+ };
27
+ /** The meta of a row, kept in the record under this key (no field of a model is named so). */
28
+ export declare const ROW_META = "__steward";
29
+ /** The meta of the record of a record page: its actions, title, global actions and cards. */
30
+ export interface PageRecordMeta extends RowMeta {
31
+ toolbar?: ActionStates;
32
+ cards?: Record<string, RowMeta>;
33
+ }
34
+ export declare const rowMeta: (record: RaRecord | undefined) => PageRecordMeta;
35
+ /**
36
+ * The data provider of steward services. Every call names the place it is for in `meta.steward`:
37
+ * a table (`{service, widget}`), a related table (`of`), a picker of a form, the options of a
38
+ * reference filter, or a record page (`{service, page}`). Creating, changing and deleting are
39
+ * actions.
40
+ */
41
+ export declare const stewardDataProvider: (client: StewardClient) => DataProvider;
@@ -0,0 +1,4 @@
1
+ export declare const QueryError: ({ error, retry }: {
2
+ error: unknown;
3
+ retry: () => unknown;
4
+ }) => import("react").JSX.Element;
@@ -0,0 +1,9 @@
1
+ import type { ActionDesc, FormFieldDesc } from "./protocol.js";
2
+ /** The inputs of the fields of a form (or of a record embedded in it, under @prefix@). */
3
+ export declare const FormInputs: ({ service, action, recordKey, fields, prefix, }: {
4
+ service: string;
5
+ action: ActionDesc;
6
+ recordKey: string | null;
7
+ fields: FormFieldDesc[];
8
+ prefix: string;
9
+ }) => import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import type { ComponentType } from "react";
2
+ import type { SvgIconProps } from "@mui/material";
3
+ type Loader = () => Promise<{
4
+ default: ComponentType<SvgIconProps>;
5
+ }>;
6
+ export declare const iconLoaders: Record<string, Loader>;
7
+ export {};
@@ -0,0 +1,35 @@
1
+ import type { ReactNode } from "react";
2
+ import type { DataProvider, LayoutProps } from "react-admin";
3
+ import { StewardClient } from "./client.js";
4
+ import { type StewardConfig } from "./context.js";
5
+ export interface Steward {
6
+ client: StewardClient;
7
+ /** The data provider of the services; combine it with the admin's own for other resources. */
8
+ dataProvider: DataProvider;
9
+ /** React Admin's layout with the services' menu; or wrap a layout of the admin's own in Provider. */
10
+ Layout: (props: LayoutProps) => ReactNode;
11
+ /** Gives the components of the services their context, around a layout of the admin's own. */
12
+ Provider: (props: {
13
+ children: ReactNode;
14
+ }) => ReactNode;
15
+ /** The routes of the pages, for `<CustomRoutes>`. */
16
+ routes: ReactNode;
17
+ }
18
+ /**
19
+ * The frontend of steward services for a React Admin app:
20
+ *
21
+ * const steward = createSteward({ baseUrl: "/admin", components: { SalesChart } });
22
+ *
23
+ * <Admin dataProvider={steward.dataProvider} layout={steward.Layout}>
24
+ * <CustomRoutes>{steward.routes}</CustomRoutes>
25
+ * </Admin>
26
+ */
27
+ export declare const createSteward: (config: StewardConfig) => Steward;
28
+ export { StewardClient, StewardError, type ClientOptions, type Query } from "./client.js";
29
+ export { pagePath, parsePeek, PEEK, useDescribe, useOpenPage, usePanel, useServices, useSteward, withPeek, type CellComponent, type PageRef, type StewardConfig, type WidgetComponent } from "./context.js";
30
+ export { ROW_META, rowMeta, stewardDataProvider, type PageRecordMeta, type Place } from "./dataProvider.js";
31
+ export { StewardMenu, StewardMenuItems, StewardPanel } from "./layout.js";
32
+ export { StewardPage } from "./page.js";
33
+ export type * from "./protocol.js";
34
+ export { StewardTable } from "./table.js";
35
+ export { Icon } from "./values.js";