@venizia/ardor-admin 0.4.8-1 → 0.4.8-10
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 +9 -5
- package/dist/common/locales/en.d.ts +1 -0
- package/dist/common/locales/en.js +1 -0
- package/dist/common/locales/vi.d.ts +1 -0
- package/dist/common/locales/vi.js +1 -0
- package/dist/common/types.d.ts +39 -6
- package/dist/components/application.d.ts +8 -0
- package/dist/components/application.js +37 -2
- package/dist/components/resolve.d.ts +16 -5
- package/dist/components/resolve.js +3 -6
- package/dist/features/feature.d.ts +4 -0
- package/dist/features/feature.js +1 -0
- package/dist/hooks/use-document-title.d.ts +5 -0
- package/dist/hooks/use-notify-error.d.ts +4 -0
- package/dist/hooks/use-notify-error.js +4 -0
- package/dist/hooks/use-refresh-token.d.ts +4 -0
- package/dist/hooks/use-refresh-token.js +4 -0
- package/dist/hooks/use-request-header-locale.d.ts +9 -1
- package/dist/hooks/use-request-header-locale.js +4 -0
- package/dist/hooks/use-translate.d.ts +15 -0
- package/dist/providers/auth.d.ts +67 -3
- package/dist/providers/auth.js +95 -7
- package/dist/providers/count-rest-data.d.ts +8 -0
- package/dist/providers/count-rest-data.js +8 -0
- package/dist/providers/i18n.d.ts +22 -2
- package/dist/providers/i18n.js +22 -2
- package/dist/providers/list-filter.d.ts +5 -1
- package/dist/providers/list-filter.js +2 -1
- package/dist/providers/rest-data.d.ts +65 -3
- package/dist/providers/rest-data.js +83 -3
- package/package.json +13 -14
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ The [react-admin](https://marmelab.com/react-admin/) adapter of
|
|
|
4
4
|
[ARDOR](https://github.com/VENIZIA-AI/ardor): every place the framework touches `ra-core`.
|
|
5
5
|
|
|
6
6
|
```bash
|
|
7
|
-
bun add @venizia/ardor-admin @venizia/ardor-kernel @venizia/ardor-react @venizia/ignis-inversion @venizia/ignis-filter @venizia/ignis-kernel @venizia/ignis-helpers reflect-metadata ra-core ra-i18n-polyglot
|
|
7
|
+
bun add @venizia/ardor-admin @venizia/ardor-kernel @venizia/ardor-react @venizia/ignis-inversion @venizia/ignis-filter @venizia/ignis-kernel @venizia/ignis-helpers reflect-metadata ra-core ra-i18n-polyglot react react-dom react-redux react-router-dom @reduxjs/toolkit @tanstack/react-query
|
|
8
8
|
```
|
|
9
9
|
|
|
10
10
|
Most applications install [`@venizia/ardor`](https://www.npmjs.com/package/@venizia/ardor) instead,
|
|
@@ -15,11 +15,15 @@ which re-exports this package.
|
|
|
15
15
|
| Area | Exports |
|
|
16
16
|
|---|---|
|
|
17
17
|
| Data | `DefaultRestDataProvider`, `CountRestDataProvider` |
|
|
18
|
-
| Auth | `DefaultAuthProvider` |
|
|
18
|
+
| Auth | `DefaultAuthProvider`, `SessionClearReasons`, `IAuthRejection` |
|
|
19
19
|
| i18n | `DefaultI18nProvider`, `englishMessages`, `vietnameseMessages` |
|
|
20
|
-
| Component | `ArdorApplication` - the `CoreAdmin` root, wired from the container |
|
|
21
|
-
|
|
|
22
|
-
|
|
|
20
|
+
| Component | `ArdorApplication` - the `CoreAdmin` root, wired from the container; `ApplicationProviders` |
|
|
21
|
+
| Features | `Features`, `TFeature`, `useFeatures` |
|
|
22
|
+
| Lists | `ListFilterBuilder` |
|
|
23
|
+
| Hooks | `useTranslate`, `useUntypedTranslate`, `useNotifyError`, `useRefreshToken`, `useRequestHeaderLocale`, `useFeatures`, `useDocumentTitle`, `useHtmlLang` |
|
|
24
|
+
| Types | `IDataProvider`, `IAuthProvider`, `II18nProviderOptions`, `IApplication`, `TUseTranslateKeys`, `TI18nSource` |
|
|
25
|
+
|
|
26
|
+
Every export, with its signature, is in the [references](https://ardor.venizia.ai/references/).
|
|
23
27
|
|
|
24
28
|
The data provider speaks the `@venizia/ignis-filter` vocabulary - react-admin pagination, sort and
|
|
25
29
|
filter are mapped onto `{ where, order, limit, skip, fields, include }`.
|
package/dist/common/types.d.ts
CHANGED
|
@@ -5,30 +5,46 @@ import type { TExtraRequest } from '@venizia/ignis-kernel/repository';
|
|
|
5
5
|
import type { AuthProvider, CoreAdminProps, DataProvider, Locale, ResourceProps } from 'ra-core';
|
|
6
6
|
import type { ReactNode } from 'react';
|
|
7
7
|
import type { RouteProps } from 'react-router-dom';
|
|
8
|
-
import type { DefaultNetworkRequestService, IDataSourceAuth,
|
|
8
|
+
import type { DefaultNetworkRequestService, IDataSourceAuth, INetworkResponse, ISendParams, RequestCountData, TConstValue, TRequestMethod, TRequestType } from '@venizia/ardor-kernel';
|
|
9
9
|
import type { TFeature } from '../features/feature.js';
|
|
10
10
|
/** What `DefaultRestDataProvider.request` sends: every react-admin method goes through it. */
|
|
11
11
|
export interface IRestRequestOptions<TResource extends string = string> {
|
|
12
|
+
/** The first path segment, unless `paths` is set. */
|
|
12
13
|
resource: TResource;
|
|
14
|
+
/** What the response is read as, and what the request is logged as. */
|
|
13
15
|
type: TRequestType;
|
|
16
|
+
/** The HTTP method. */
|
|
14
17
|
method: TRequestMethod;
|
|
15
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* The path segments of the request.
|
|
20
|
+
* @default [resource]
|
|
21
|
+
*/
|
|
16
22
|
paths?: Array<string>;
|
|
23
|
+
/** The request body; encoded by `bodyType`. */
|
|
17
24
|
body?: unknown;
|
|
18
25
|
bodyType?: ISendParams['bodyType'];
|
|
26
|
+
/** The query string. */
|
|
19
27
|
query?: Record<string, unknown>;
|
|
28
|
+
/** The list extras, sent in a header. */
|
|
20
29
|
extra?: TExtraRequest;
|
|
21
30
|
/** Win over the computed headers: a caller that names a header means it. */
|
|
22
31
|
headers?: HeadersInit;
|
|
32
|
+
/**
|
|
33
|
+
* What the request asks the server to count.
|
|
34
|
+
* @default RequestCountData.DATA_ONLY
|
|
35
|
+
*/
|
|
23
36
|
requestCountData?: TConstValue<typeof RequestCountData>;
|
|
24
37
|
}
|
|
25
38
|
/** The nine data methods of react-admin, as ra-core declares them. */
|
|
26
39
|
export type IReactAdminDataProvider<TResource extends string = string> = Pick<DataProvider<TResource>, 'getList' | 'getOne' | 'getMany' | 'getManyReference' | 'update' | 'updateMany' | 'create' | 'delete' | 'deleteMany'>;
|
|
40
|
+
/** react-admin's data provider with a free-form `send` and access to the network service and its auth. */
|
|
27
41
|
export interface IDataProvider<TResource extends string = string> extends IReactAdminDataProvider<TResource> {
|
|
42
|
+
/** A request outside react-admin's methods: the same headers and auth, the method and body of `params`. */
|
|
28
43
|
send: <TResult = unknown>(opts: {
|
|
29
44
|
resource: TResource;
|
|
30
45
|
params: ISendParams;
|
|
31
|
-
}) => Promise<
|
|
46
|
+
}) => Promise<INetworkResponse<TResult>>;
|
|
47
|
+
/** The network service every request goes through. */
|
|
32
48
|
getNetworkService(): DefaultNetworkRequestService;
|
|
33
49
|
/** Spread into an `HttpDataSource`: the repository then sends what this provider sends. */
|
|
34
50
|
getDataSourceAuth(): IDataSourceAuth;
|
|
@@ -45,7 +61,12 @@ export interface IAuthRejection {
|
|
|
45
61
|
logoutUser?: boolean;
|
|
46
62
|
message?: string | false;
|
|
47
63
|
}
|
|
64
|
+
/** The auth provider of an ARDOR application: react-admin's methods, the user's roles and a token refresh. */
|
|
48
65
|
export interface IAuthProvider extends IReactAdminAuthProvider {
|
|
66
|
+
/**
|
|
67
|
+
* The roles of the signed-in user, as the browser holds them: they only show or hide interface, and
|
|
68
|
+
* the backend must check every permission on every request.
|
|
69
|
+
*/
|
|
49
70
|
getRoles: (params?: unknown) => Promise<Set<string>>;
|
|
50
71
|
/** `true` when the session was refreshed, `false` when there is no refresh to make. */
|
|
51
72
|
refreshToken: () => Promise<boolean>;
|
|
@@ -56,19 +77,31 @@ export interface IAuthProvider extends IReactAdminAuthProvider {
|
|
|
56
77
|
* is the messages). A loader that fails leaves that locale on the English messages.
|
|
57
78
|
*/
|
|
58
79
|
export type TI18nSource = object | (() => Promise<object>);
|
|
80
|
+
/** What `DefaultI18nProvider` is built from: the message sources and the languages on offer. */
|
|
59
81
|
export interface II18nProviderOptions {
|
|
60
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* Messages by locale: the application's own over the features', English for a locale with no source or whose loader failed.
|
|
84
|
+
* @default { en: englishMessages }
|
|
85
|
+
*/
|
|
61
86
|
i18nSources?: Record<string, TI18nSource>;
|
|
87
|
+
/**
|
|
88
|
+
* The locales the app offers.
|
|
89
|
+
* @default [{ locale: 'en', name: 'English' }]
|
|
90
|
+
*/
|
|
62
91
|
languages?: Array<Locale>;
|
|
63
92
|
}
|
|
93
|
+
/** The props of `ArdorApplication`: react-admin's `CoreAdmin` props except `children`, plus the ARDOR ones. */
|
|
64
94
|
export interface IApplication extends Omit<CoreAdminProps, 'children'> {
|
|
95
|
+
/** The container the providers and the features are resolved from. */
|
|
65
96
|
container: Container;
|
|
66
97
|
/**
|
|
67
|
-
* The logger `useApplicationLogger()` returns. Absent, a console logger named
|
|
98
|
+
* The logger `useApplicationLogger()` returns. Absent, a console logger named `ArdorApplication`
|
|
68
99
|
* (IGNIS's, so a provider registered with `LoggerFactory.use` is the one behind it).
|
|
69
100
|
*/
|
|
70
101
|
logger?: ILogger;
|
|
71
|
-
|
|
102
|
+
/** The store the Redux provider of the shell serves. Absent, the shell mounts no Redux provider. */
|
|
103
|
+
reduxStore?: Store;
|
|
104
|
+
/** Rendered as the `Suspense` fallback and while feature messages for the locale load. */
|
|
72
105
|
suspense: ReactNode;
|
|
73
106
|
/** The application's own resources; each feature's are mounted after them. */
|
|
74
107
|
resources?: Array<ResourceProps>;
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { IApplication } from '../common/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* The application shell. It resolves the data, auth and i18n providers from `container`, mounts the
|
|
5
|
+
* `resources` and `routes` of the application and of each feature inside react-admin, and wraps it
|
|
6
|
+
* all in `ApplicationContext.Provider` (`container`, `logger`), the Redux provider (only when
|
|
7
|
+
* `reduxStore` is passed) and `Suspense`. While a feature's lazy messages for the current locale
|
|
8
|
+
* load, it renders `suspense` instead of react-admin; a failed load is thrown from render. Remaining
|
|
9
|
+
* props go to react-admin's `CoreAdmin`.
|
|
10
|
+
*/
|
|
3
11
|
export declare const ArdorApplication: React.FC<IApplication>;
|
|
4
12
|
//# sourceMappingURL=application.d.ts.map
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { LoggerFactory } from '@venizia/ignis-helpers/core';
|
|
4
|
+
import { QueryClient } from '@tanstack/react-query';
|
|
4
5
|
import { CoreAdmin, CustomRoutes, Resource } from 'ra-core';
|
|
5
6
|
import { Provider as ReduxProvider } from 'react-redux';
|
|
6
7
|
import { Route } from 'react-router-dom';
|
|
7
8
|
import { Features } from '../features/feature.js';
|
|
9
|
+
import { CoreBindings } from '@venizia/ardor-kernel';
|
|
8
10
|
import { ApplicationContext } from '@venizia/ardor-react';
|
|
9
11
|
import { ApplicationProviders } from './resolve.js';
|
|
10
12
|
const Wrapper = ({ applicationName = 'ArdorApplication', container, reduxStore, suspense, logger, children, }) => {
|
|
@@ -12,7 +14,8 @@ const Wrapper = ({ applicationName = 'ArdorApplication', container, reduxStore,
|
|
|
12
14
|
const applicationLogger = React.useMemo(() => {
|
|
13
15
|
return logger ?? LoggerFactory.getLogger([applicationName]);
|
|
14
16
|
}, [logger, applicationName]);
|
|
15
|
-
|
|
17
|
+
const content = _jsx(React.Suspense, { fallback: suspense, children: children });
|
|
18
|
+
return (_jsx(ApplicationContext.Provider, { value: { container, logger: applicationLogger }, children: reduxStore ? _jsx(ReduxProvider, { store: reduxStore, children: content }) : content }));
|
|
16
19
|
};
|
|
17
20
|
/**
|
|
18
21
|
* The i18n provider is built with the initial locale's eager messages only; when a feature loads
|
|
@@ -49,6 +52,14 @@ const useFeatureMessagesReady = (opts) => {
|
|
|
49
52
|
}
|
|
50
53
|
return state.isReady;
|
|
51
54
|
};
|
|
55
|
+
/**
|
|
56
|
+
* The application shell. It resolves the data, auth and i18n providers from `container`, mounts the
|
|
57
|
+
* `resources` and `routes` of the application and of each feature inside react-admin, and wraps it
|
|
58
|
+
* all in `ApplicationContext.Provider` (`container`, `logger`), the Redux provider (only when
|
|
59
|
+
* `reduxStore` is passed) and `Suspense`. While a feature's lazy messages for the current locale
|
|
60
|
+
* load, it renders `suspense` instead of react-admin; a failed load is thrown from render. Remaining
|
|
61
|
+
* props go to react-admin's `CoreAdmin`.
|
|
62
|
+
*/
|
|
52
63
|
export const ArdorApplication = (props) => {
|
|
53
64
|
const { container, reduxStore, suspense, logger, resources, routes, features: selected, ...reactAdminProps } = props;
|
|
54
65
|
// The list the shell passed, else every feature the application bound.
|
|
@@ -63,7 +74,31 @@ export const ArdorApplication = (props) => {
|
|
|
63
74
|
const providers = React.useMemo(() => {
|
|
64
75
|
return ApplicationProviders.resolve({ container });
|
|
65
76
|
}, [container]);
|
|
66
|
-
|
|
77
|
+
// The cache is owned here, so it can be emptied when the session ends by any route (a sign-out
|
|
78
|
+
// button empties it itself; a failed refresh, or code that calls `cleanUp`, does not).
|
|
79
|
+
const queryClient = React.useMemo(() => {
|
|
80
|
+
return reactAdminProps.queryClient ?? new QueryClient();
|
|
81
|
+
}, [reactAdminProps.queryClient]);
|
|
82
|
+
React.useEffect(() => {
|
|
83
|
+
const authService = container.get({
|
|
84
|
+
key: CoreBindings.DEFAULT_AUTH_SERVICE,
|
|
85
|
+
isOptional: true,
|
|
86
|
+
});
|
|
87
|
+
return authService?.subscribe({
|
|
88
|
+
listener: () => {
|
|
89
|
+
if (!authService.getAuth()?.value) {
|
|
90
|
+
// After the redirect to the sign-in page, as react-admin's own sign-out does.
|
|
91
|
+
setTimeout(() => {
|
|
92
|
+
// Not when the next user signed in meanwhile.
|
|
93
|
+
if (!authService.getAuth()?.value) {
|
|
94
|
+
queryClient.clear();
|
|
95
|
+
}
|
|
96
|
+
}, 0);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}, [container, queryClient]);
|
|
101
|
+
const adminProps = { ...providers, ...reactAdminProps, queryClient };
|
|
67
102
|
const isMessagesReady = useFeatureMessagesReady({
|
|
68
103
|
features,
|
|
69
104
|
i18nProvider: providers.i18nProvider,
|
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
import type { Container } from '@venizia/ignis-inversion';
|
|
2
2
|
import type { I18nProvider } from 'ra-core';
|
|
3
3
|
import type { IAuthProvider, IDataProvider } from '../common/index.js';
|
|
4
|
+
interface IResolvedProviders {
|
|
5
|
+
dataProvider: IDataProvider;
|
|
6
|
+
authProvider?: IAuthProvider;
|
|
7
|
+
i18nProvider: I18nProvider;
|
|
8
|
+
}
|
|
9
|
+
/** Resolves the providers `ArdorApplication` hands to react-admin. */
|
|
4
10
|
export declare class ApplicationProviders {
|
|
5
11
|
/**
|
|
6
12
|
* What `ArdorApplication` resolves from the container when it mounts: the one place, so a test of
|
|
7
|
-
* the boot resolves exactly what the app does.
|
|
13
|
+
* the boot resolves exactly what the app does. With `isAuthRequired: true`, for an application that
|
|
14
|
+
* always binds a sign-in, `authProvider` is typed as present and an unbound one throws.
|
|
8
15
|
*/
|
|
9
16
|
static resolve(opts: {
|
|
10
17
|
container: Container;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
authProvider
|
|
14
|
-
i18nProvider: I18nProvider;
|
|
18
|
+
isAuthRequired: true;
|
|
19
|
+
}): IResolvedProviders & {
|
|
20
|
+
authProvider: IAuthProvider;
|
|
15
21
|
};
|
|
22
|
+
static resolve(opts: {
|
|
23
|
+
container: Container;
|
|
24
|
+
isAuthRequired?: boolean;
|
|
25
|
+
}): IResolvedProviders;
|
|
16
26
|
}
|
|
27
|
+
export {};
|
|
17
28
|
//# sourceMappingURL=resolve.d.ts.map
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { CoreBindings } from '@venizia/ardor-kernel';
|
|
2
|
+
/** Resolves the providers `ArdorApplication` hands to react-admin. */
|
|
2
3
|
export class ApplicationProviders {
|
|
3
|
-
/**
|
|
4
|
-
* What `ArdorApplication` resolves from the container when it mounts: the one place, so a test of
|
|
5
|
-
* the boot resolves exactly what the app does.
|
|
6
|
-
*/
|
|
7
4
|
static resolve(opts) {
|
|
8
|
-
const { container } = opts;
|
|
5
|
+
const { container, isAuthRequired } = opts;
|
|
9
6
|
const dataProvider = container.get({
|
|
10
7
|
key: CoreBindings.DEFAULT_REST_DATA_PROVIDER,
|
|
11
8
|
});
|
|
@@ -13,7 +10,7 @@ export class ApplicationProviders {
|
|
|
13
10
|
// without an auth provider - every page is public.
|
|
14
11
|
const authProvider = container.get({
|
|
15
12
|
key: CoreBindings.DEFAULT_AUTH_PROVIDER,
|
|
16
|
-
isOptional:
|
|
13
|
+
isOptional: !isAuthRequired,
|
|
17
14
|
});
|
|
18
15
|
const i18nProvider = container.get({
|
|
19
16
|
key: CoreBindings.DEFAULT_I18N_PROVIDER,
|
|
@@ -15,6 +15,7 @@ export type TFeature<TExtension extends object = {}> = IFeatureBase & TExtension
|
|
|
15
15
|
/** The application's own `i18nSources` override these. */
|
|
16
16
|
messages?: TFeatureMessages;
|
|
17
17
|
};
|
|
18
|
+
/** Defines, reads, mounts and loads the messages of the features an application binds. */
|
|
18
19
|
export declare class Features {
|
|
19
20
|
/** Types a feature, keeping the shell's extension. */
|
|
20
21
|
static define<TExtension extends object = {}>(feature: TFeature<TExtension>): TFeature<TExtension>;
|
|
@@ -28,7 +29,9 @@ export declare class Features {
|
|
|
28
29
|
*/
|
|
29
30
|
static mount(opts: {
|
|
30
31
|
features: Array<TFeature>;
|
|
32
|
+
/** @default [] */
|
|
31
33
|
resources?: Array<ResourceProps>;
|
|
34
|
+
/** @default [] */
|
|
32
35
|
routes?: Array<RouteProps>;
|
|
33
36
|
}): {
|
|
34
37
|
resources: Array<ResourceProps>;
|
|
@@ -46,6 +49,7 @@ export declare class Features {
|
|
|
46
49
|
static loadMessages(opts: {
|
|
47
50
|
features: Array<TFeature>;
|
|
48
51
|
i18nProvider: I18nProvider;
|
|
52
|
+
/** The locale to load. @default i18nProvider.getLocale() */
|
|
49
53
|
locale?: string;
|
|
50
54
|
}): Promise<void>;
|
|
51
55
|
}
|
package/dist/features/feature.js
CHANGED
|
@@ -2,6 +2,7 @@ import { getError } from '@venizia/ignis-inversion';
|
|
|
2
2
|
import { CoreBindings } from '@venizia/ardor-kernel';
|
|
3
3
|
import { LazyLocales } from '../providers/i18n-sources.js';
|
|
4
4
|
import { FeatureMessages } from './messages.js';
|
|
5
|
+
/** Defines, reads, mounts and loads the messages of the features an application binds. */
|
|
5
6
|
export class Features {
|
|
6
7
|
/** Types a feature, keeping the shell's extension. */
|
|
7
8
|
static define(feature) {
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
type TDocumentTitleSegment = string | false | null | undefined;
|
|
2
|
+
/** What `useDocumentTitle` writes into the tab title, and when. */
|
|
2
3
|
export interface IUseDocumentTitleOptions {
|
|
4
|
+
/** The screen title, or a list of segments in order; `false`, `null`, `undefined` and empty segments are dropped. */
|
|
3
5
|
title: TDocumentTitleSegment | Array<TDocumentTitleSegment>;
|
|
6
|
+
/** The last segment of the tab title. */
|
|
4
7
|
appTitle: string;
|
|
8
|
+
/** Joins the segments. @default ' · ' */
|
|
5
9
|
separator?: string;
|
|
10
|
+
/** When `false` the call writes no title. @default true */
|
|
6
11
|
enabled?: boolean;
|
|
7
12
|
/**
|
|
8
13
|
* A base title for a layout. It shows only while no other call is mounted, so a screen's title
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { ApplicationError } from '@venizia/ignis-inversion';
|
|
2
2
|
import type { NotificationOptions, NotificationType } from 'ra-core';
|
|
3
|
+
/**
|
|
4
|
+
* Returns a stable function that shows an error notification for an `ApplicationError`: the message
|
|
5
|
+
* is its normalized `code`, with its normalized `args` as message arguments. `options` override both.
|
|
6
|
+
*/
|
|
3
7
|
export declare const useNotifyError: () => (error: ApplicationError, options?: NotificationOptions & {
|
|
4
8
|
type?: NotificationType;
|
|
5
9
|
}) => void;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { useNotify } from 'ra-core';
|
|
3
|
+
/**
|
|
4
|
+
* Returns a stable function that shows an error notification for an `ApplicationError`: the message
|
|
5
|
+
* is its normalized `code`, with its normalized `args` as message arguments. `options` override both.
|
|
6
|
+
*/
|
|
3
7
|
export const useNotifyError = () => {
|
|
4
8
|
const notify = useNotify();
|
|
5
9
|
return React.useCallback((error, options) => {
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a function that refreshes the session through the auth provider's `refreshToken` and, once
|
|
3
|
+
* that resolves, invalidates the cached permissions. It rejects when `refreshToken` rejects.
|
|
4
|
+
*/
|
|
1
5
|
export declare const useRefreshToken: () => TRefreshToken;
|
|
2
6
|
/** `true` when the session was refreshed, `false` when there is no auth provider or no refresh to make. */
|
|
3
7
|
type TRefreshToken = () => Promise<boolean>;
|
|
@@ -3,6 +3,10 @@ import { useQueryClient } from '@tanstack/react-query';
|
|
|
3
3
|
import { useAuthProvider } from 'ra-core';
|
|
4
4
|
import { LoggerFactory } from '@venizia/ignis-helpers/core';
|
|
5
5
|
const logger = LoggerFactory.getLogger(['useRefreshToken']);
|
|
6
|
+
/**
|
|
7
|
+
* Returns a function that refreshes the session through the auth provider's `refreshToken` and, once
|
|
8
|
+
* that resolves, invalidates the cached permissions. It rejects when `refreshToken` rejects.
|
|
9
|
+
*/
|
|
6
10
|
export const useRefreshToken = () => {
|
|
7
11
|
const authProvider = useAuthProvider();
|
|
8
12
|
const queryClient = useQueryClient();
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
/** The header `useRequestHeaderLocale` sends the locale in. */
|
|
1
2
|
export interface IUseRequestHeaderLocaleOptions {
|
|
2
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* The header the locale is sent in.
|
|
5
|
+
* @default 'x-locale'
|
|
6
|
+
*/
|
|
3
7
|
key?: string;
|
|
4
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Keeps the current react-admin locale in a header of the default REST data provider's network
|
|
11
|
+
* service, re-set on each locale change. `opts.key` defaults to `x-locale`; an empty key falls back to it.
|
|
12
|
+
*/
|
|
5
13
|
export declare const useRequestHeaderLocale: (opts?: IUseRequestHeaderLocaleOptions) => void;
|
|
6
14
|
//# sourceMappingURL=use-request-header-locale.d.ts.map
|
|
@@ -2,6 +2,10 @@ import React from 'react';
|
|
|
2
2
|
import { useLocaleState } from 'ra-core';
|
|
3
3
|
import { CoreBindings, HeaderConstants } from '@venizia/ardor-kernel';
|
|
4
4
|
import { useInjectable } from '@venizia/ardor-react';
|
|
5
|
+
/**
|
|
6
|
+
* Keeps the current react-admin locale in a header of the default REST data provider's network
|
|
7
|
+
* service, re-set on each locale change. `opts.key` defaults to `x-locale`; an empty key falls back to it.
|
|
8
|
+
*/
|
|
5
9
|
export const useRequestHeaderLocale = (opts) => {
|
|
6
10
|
const key = opts?.key?.length ? opts.key : HeaderConstants.X_LOCALE;
|
|
7
11
|
const [locale] = useLocaleState();
|
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
import { type I18nProvider, type TranslateFunction } from 'ra-core';
|
|
2
2
|
import type { TFullPaths } from '@venizia/ardor-kernel';
|
|
3
3
|
import type { englishMessages } from '../common/locales/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* Augment this interface to add keys to `TUseTranslateKeys` (messages an app adds to the defaults):
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* declare module '@venizia/ardor' {
|
|
9
|
+
* interface IUseTranslateKeysOverrides {
|
|
10
|
+
* 'app.orders.title': unknown;
|
|
11
|
+
* }
|
|
12
|
+
* }
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
4
15
|
export interface IUseTranslateKeysOverrides {
|
|
5
16
|
}
|
|
17
|
+
/** Every dotted path to a message in the built-in English messages. */
|
|
6
18
|
export type TUseTranslateKeysDefault = TFullPaths<typeof englishMessages>;
|
|
19
|
+
/** The keys `useTranslate` accepts: the built-in message paths plus the keys added through `IUseTranslateKeysOverrides`. */
|
|
7
20
|
export type TUseTranslateKeys = TUseTranslateKeysDefault | keyof IUseTranslateKeysOverrides;
|
|
8
21
|
/** The options react-admin's `translate` takes (`smart_count`, `_` for a default text, ...). */
|
|
9
22
|
export type TUseTranslateOptions = Parameters<I18nProvider['translate']>[1];
|
|
23
|
+
/** `translate` with the key narrowed to `TUseTranslateKeys`. */
|
|
10
24
|
export type TUseTranslateFn = (key: TUseTranslateKeys, options?: TUseTranslateOptions) => string;
|
|
25
|
+
/** `translate` as react-admin types it, taking any string key. */
|
|
11
26
|
export type TUseUntypedTranslateFn = TranslateFunction;
|
|
12
27
|
/** For a key that exists only at run time (`errors.${code}`); a key written in code belongs to `useTranslate`. */
|
|
13
28
|
export declare const useUntypedTranslate: () => TUseUntypedTranslateFn;
|
package/dist/providers/auth.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export declare class SessionClearReasons {
|
|
|
18
18
|
/** `checkAuth` rejected: no token is stored, or the server answered with no data. */
|
|
19
19
|
static readonly REJECTED = "rejected";
|
|
20
20
|
}
|
|
21
|
+
/** One of the `SessionClearReasons` values. */
|
|
21
22
|
export type TSessionClearReason = TStringConstValue<typeof SessionClearReasons>;
|
|
22
23
|
/**
|
|
23
24
|
* The options hold data (`paths`); behaviour is in the protected hooks around `login` and the page
|
|
@@ -28,6 +29,27 @@ export type TSessionClearReason = TStringConstValue<typeof SessionClearReasons>;
|
|
|
28
29
|
* hook that throws, or does not settle in `sessionHookTimeout`, is logged and does not stop the call
|
|
29
30
|
* that fired it. A hook does not call this provider's methods: they wait behind it. Build one provider
|
|
30
31
|
* per auth service: a second one listening to the same service calls every hook again.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* import { type BaseArdorApplication, configuration, CoreBindings, DefaultAuthProvider, inject, provide } from '@venizia/ardor';
|
|
36
|
+
*
|
|
37
|
+
* class AppAuthProvider extends DefaultAuthProvider {
|
|
38
|
+
* protected override get afterLoginPath() {
|
|
39
|
+
* return '/products';
|
|
40
|
+
* }
|
|
41
|
+
* }
|
|
42
|
+
*
|
|
43
|
+
* @configuration()
|
|
44
|
+
* export class AuthConfiguration {
|
|
45
|
+
* constructor(@inject({ key: CoreBindings.APPLICATION_INSTANCE }) private readonly application: BaseArdorApplication) {}
|
|
46
|
+
*
|
|
47
|
+
* @provide({ key: CoreBindings.DEFAULT_AUTH_PROVIDER })
|
|
48
|
+
* authProvider() {
|
|
49
|
+
* return this.application.instantiate(AppAuthProvider).value(this.application);
|
|
50
|
+
* }
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
31
53
|
*/
|
|
32
54
|
export declare class DefaultAuthProvider<TResource extends string = string> extends BaseProvider<IAuthProvider> {
|
|
33
55
|
protected restDataProvider: IDataProvider<TResource>;
|
|
@@ -46,8 +68,8 @@ export declare class DefaultAuthProvider<TResource extends string = string> exte
|
|
|
46
68
|
private hookQueue;
|
|
47
69
|
/** Where `login` posts: `paths.signIn`, else the IGNIS auth controller's route. */
|
|
48
70
|
protected get signInPath(): string;
|
|
49
|
-
/** Where the admin goes once `login` resolves: `paths.afterLogin`, else
|
|
50
|
-
protected get afterLoginPath(): string;
|
|
71
|
+
/** Where the admin goes once `login` resolves: `paths.afterLogin`, else `/`; `false` stays on the page. */
|
|
72
|
+
protected get afterLoginPath(): string | false;
|
|
51
73
|
/** The route of the login page, which `checkAuth` and a `401` redirect to. */
|
|
52
74
|
protected get loginPagePath(): string;
|
|
53
75
|
/** The route a `403` redirects to; the application mounts it. */
|
|
@@ -149,18 +171,60 @@ export declare class DefaultAuthProvider<TResource extends string = string> exte
|
|
|
149
171
|
private clearSession;
|
|
150
172
|
/** Announces the session in the store, when there is one. */
|
|
151
173
|
private startStoredSession;
|
|
152
|
-
/**
|
|
174
|
+
/**
|
|
175
|
+
* Runs when the session is gone, however it ended (sign-out, a `401`, a failed refresh, other
|
|
176
|
+
* code calling `cleanUp`): removes what the user stored under the store prefix of this
|
|
177
|
+
* application (`@app/store/`, or `@app/store/<id>/` with an application `id`) in `localStorage` and `sessionStorage`, so the next user does not see it.
|
|
178
|
+
* Override it to keep preferences, or put them under another prefix.
|
|
179
|
+
*/
|
|
180
|
+
protected clearStoredData(): void;
|
|
181
|
+
/**
|
|
182
|
+
* Posts the sign-in body to `signInPath`, stores the session and resolves `{ ...response,
|
|
183
|
+
* redirectTo }`, where `redirectTo` is `afterLoginPath` (`false` when it is `false`). Rejects when the request fails, when the
|
|
184
|
+
* response carries no token or user id, or when `beforeSaveSession` throws; no session is stored
|
|
185
|
+
* then. Typed `Promise<unknown>` so that a subclass override of `login` keeps compiling.
|
|
186
|
+
*/
|
|
153
187
|
login(params: ILoginParams): Promise<unknown>;
|
|
188
|
+
/** Clears the session and resolves. */
|
|
154
189
|
logout(_params?: unknown): Promise<void>;
|
|
190
|
+
/**
|
|
191
|
+
* Resolves when a token is stored and, if `paths.checkAuth` is set, the server answers with data.
|
|
192
|
+
* Otherwise it ends the session (reason `rejected`) and rejects with `unauthenticatedRejection`;
|
|
193
|
+
* it rejects with the request's error when the check request fails.
|
|
194
|
+
*/
|
|
155
195
|
checkAuth(_params?: unknown): Promise<void>;
|
|
196
|
+
/**
|
|
197
|
+
* Resolves for every error but 401 and 403. A 401 clears the session (unless
|
|
198
|
+
* `unauthenticatedRejection` says `logoutUser: false`) and rejects with that rejection; a 403
|
|
199
|
+
* rejects with `{ redirectTo: unauthorizedPagePath, logoutUser: false }`.
|
|
200
|
+
*/
|
|
156
201
|
checkError(error: unknown): Promise<void>;
|
|
202
|
+
/** Resolves the stored user; rejects when there is no `userId`. */
|
|
157
203
|
getIdentity(_params?: unknown): Promise<any>;
|
|
204
|
+
/** Resolves with nothing; the roles come from `getRoles`. */
|
|
158
205
|
getPermissions(_params?: unknown): Promise<void>;
|
|
206
|
+
/**
|
|
207
|
+
* Resolves the roles of the stored session. They are read from browser storage, which the user can
|
|
208
|
+
* edit: use them to show or hide interface only, and let the backend check every permission.
|
|
209
|
+
*/
|
|
159
210
|
getRoles(_params?: unknown): Promise<Set<string>>;
|
|
160
211
|
/**
|
|
161
212
|
* Refreshes the session through the network service's `refresh`, the one place a refresh is made:
|
|
162
213
|
* `true` when it was refreshed, `false` when the service has no refresh to make. The `401`
|
|
163
214
|
* recovery calls the same method, so one burst of 401s and this call share one refresh.
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```tsx
|
|
218
|
+
* import { useRefreshToken } from '@venizia/ardor';
|
|
219
|
+
*
|
|
220
|
+
* // The data provider options name the refresh endpoint:
|
|
221
|
+
* // { url: '/api', authRecovery: { refreshTokenPath: '/auth/refresh-token' } }
|
|
222
|
+
* export const RefreshButton = () => {
|
|
223
|
+
* const refreshToken = useRefreshToken();
|
|
224
|
+
*
|
|
225
|
+
* return <button onClick={() => refreshToken()}>Refresh</button>;
|
|
226
|
+
* };
|
|
227
|
+
* ```
|
|
164
228
|
*/
|
|
165
229
|
refreshToken(): Promise<boolean>;
|
|
166
230
|
value(container: Container): IAuthProvider;
|
package/dist/providers/auth.js
CHANGED
|
@@ -13,7 +13,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
13
13
|
var DefaultAuthProvider_1;
|
|
14
14
|
import { getError, inject } from '@venizia/ignis-inversion';
|
|
15
15
|
import { removeDoubleSlashes } from 'ra-core';
|
|
16
|
-
import { BaseProvider, CoreBindings, RequestMethods, } from '@venizia/ardor-kernel';
|
|
16
|
+
import { BaseProvider, BrowserStorage, CoreBindings, LocalStorageKeys, RequestMethods, StorageAreas, } from '@venizia/ardor-kernel';
|
|
17
17
|
/** Why `onSessionCleared` was called. */
|
|
18
18
|
export class SessionClearReasons {
|
|
19
19
|
/** `logout` ran. */
|
|
@@ -42,6 +42,27 @@ const readStatus = (error) => {
|
|
|
42
42
|
* hook that throws, or does not settle in `sessionHookTimeout`, is logged and does not stop the call
|
|
43
43
|
* that fired it. A hook does not call this provider's methods: they wait behind it. Build one provider
|
|
44
44
|
* per auth service: a second one listening to the same service calls every hook again.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* import { type BaseArdorApplication, configuration, CoreBindings, DefaultAuthProvider, inject, provide } from '@venizia/ardor';
|
|
49
|
+
*
|
|
50
|
+
* class AppAuthProvider extends DefaultAuthProvider {
|
|
51
|
+
* protected override get afterLoginPath() {
|
|
52
|
+
* return '/products';
|
|
53
|
+
* }
|
|
54
|
+
* }
|
|
55
|
+
*
|
|
56
|
+
* @configuration()
|
|
57
|
+
* export class AuthConfiguration {
|
|
58
|
+
* constructor(@inject({ key: CoreBindings.APPLICATION_INSTANCE }) private readonly application: BaseArdorApplication) {}
|
|
59
|
+
*
|
|
60
|
+
* @provide({ key: CoreBindings.DEFAULT_AUTH_PROVIDER })
|
|
61
|
+
* authProvider() {
|
|
62
|
+
* return this.application.instantiate(AppAuthProvider).value(this.application);
|
|
63
|
+
* }
|
|
64
|
+
* }
|
|
65
|
+
* ```
|
|
45
66
|
*/
|
|
46
67
|
let DefaultAuthProvider = DefaultAuthProvider_1 = class DefaultAuthProvider extends BaseProvider {
|
|
47
68
|
constructor(restDataProvider, authProviderOptions, authService, permissionProvider) {
|
|
@@ -63,7 +84,7 @@ let DefaultAuthProvider = DefaultAuthProvider_1 = class DefaultAuthProvider exte
|
|
|
63
84
|
get signInPath() {
|
|
64
85
|
return this.authProviderOptions.paths?.signIn ?? '/auth/sign-in';
|
|
65
86
|
}
|
|
66
|
-
/** Where the admin goes once `login` resolves: `paths.afterLogin`, else
|
|
87
|
+
/** Where the admin goes once `login` resolves: `paths.afterLogin`, else `/`; `false` stays on the page. */
|
|
67
88
|
get afterLoginPath() {
|
|
68
89
|
return this.authProviderOptions.paths?.afterLogin ?? '/';
|
|
69
90
|
}
|
|
@@ -272,9 +293,17 @@ let DefaultAuthProvider = DefaultAuthProvider_1 = class DefaultAuthProvider exte
|
|
|
272
293
|
}
|
|
273
294
|
/** Announces or ends the session after the service saved or cleared it. */
|
|
274
295
|
syncSession() {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
296
|
+
if (this.authService.getAuth()?.value) {
|
|
297
|
+
return this.startStoredSession();
|
|
298
|
+
}
|
|
299
|
+
try {
|
|
300
|
+
this.clearStoredData();
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
// An override that throws must not stop the session from ending.
|
|
304
|
+
this.logger.error('[clearStoredData] failed, the session ends anyway | error: %o', error);
|
|
305
|
+
}
|
|
306
|
+
return this.endSession({ reason: this.clearing ?? SessionClearReasons.EXTERNAL });
|
|
278
307
|
}
|
|
279
308
|
async clearSession(opts) {
|
|
280
309
|
this.clearing = opts.reason;
|
|
@@ -294,8 +323,24 @@ let DefaultAuthProvider = DefaultAuthProvider_1 = class DefaultAuthProvider exte
|
|
|
294
323
|
}
|
|
295
324
|
return this.startSession({ token, user: this.authService.getUser() });
|
|
296
325
|
}
|
|
326
|
+
/**
|
|
327
|
+
* Runs when the session is gone, however it ended (sign-out, a `401`, a failed refresh, other
|
|
328
|
+
* code calling `cleanUp`): removes what the user stored under the store prefix of this
|
|
329
|
+
* application (`@app/store/`, or `@app/store/<id>/` with an application `id`) in `localStorage` and `sessionStorage`, so the next user does not see it.
|
|
330
|
+
* Override it to keep preferences, or put them under another prefix.
|
|
331
|
+
*/
|
|
332
|
+
clearStoredData() {
|
|
333
|
+
const prefix = this.authService.keys.storePrefix;
|
|
334
|
+
BrowserStorage.removeWhere({ storage: StorageAreas.LOCAL, prefix });
|
|
335
|
+
BrowserStorage.removeWhere({ storage: StorageAreas.SESSION, prefix });
|
|
336
|
+
}
|
|
297
337
|
// --- react-admin's auth provider -------------------------------------------------------------
|
|
298
|
-
/**
|
|
338
|
+
/**
|
|
339
|
+
* Posts the sign-in body to `signInPath`, stores the session and resolves `{ ...response,
|
|
340
|
+
* redirectTo }`, where `redirectTo` is `afterLoginPath` (`false` when it is `false`). Rejects when the request fails, when the
|
|
341
|
+
* response carries no token or user id, or when `beforeSaveSession` throws; no session is stored
|
|
342
|
+
* then. Typed `Promise<unknown>` so that a subclass override of `login` keeps compiling.
|
|
343
|
+
*/
|
|
299
344
|
async login(params) {
|
|
300
345
|
const response = await this.restDataProvider.send({
|
|
301
346
|
resource: this.signInPath,
|
|
@@ -311,14 +356,21 @@ let DefaultAuthProvider = DefaultAuthProvider_1 = class DefaultAuthProvider exte
|
|
|
311
356
|
await this.startSession(stored?.value === session.token.value
|
|
312
357
|
? { token: stored, user: this.authService.getUser() }
|
|
313
358
|
: { token: session.token, user: { userId: session.userId, username } });
|
|
359
|
+
const afterLogin = this.afterLoginPath;
|
|
314
360
|
return {
|
|
315
361
|
...response,
|
|
316
|
-
redirectTo: removeDoubleSlashes(`/${
|
|
362
|
+
redirectTo: afterLogin === false ? false : removeDoubleSlashes(`/${afterLogin}`),
|
|
317
363
|
};
|
|
318
364
|
}
|
|
365
|
+
/** Clears the session and resolves. */
|
|
319
366
|
async logout(_params) {
|
|
320
367
|
await this.clearSession({ reason: SessionClearReasons.LOGOUT });
|
|
321
368
|
}
|
|
369
|
+
/**
|
|
370
|
+
* Resolves when a token is stored and, if `paths.checkAuth` is set, the server answers with data.
|
|
371
|
+
* Otherwise it ends the session (reason `rejected`) and rejects with `unauthenticatedRejection`;
|
|
372
|
+
* it rejects with the request's error when the check request fails.
|
|
373
|
+
*/
|
|
322
374
|
async checkAuth(_params) {
|
|
323
375
|
const token = this.authService.getAuth();
|
|
324
376
|
if (!token?.value) {
|
|
@@ -344,6 +396,11 @@ let DefaultAuthProvider = DefaultAuthProvider_1 = class DefaultAuthProvider exte
|
|
|
344
396
|
await this.startStoredSession();
|
|
345
397
|
return Promise.resolve();
|
|
346
398
|
}
|
|
399
|
+
/**
|
|
400
|
+
* Resolves for every error but 401 and 403. A 401 clears the session (unless
|
|
401
|
+
* `unauthenticatedRejection` says `logoutUser: false`) and rejects with that rejection; a 403
|
|
402
|
+
* rejects with `{ redirectTo: unauthorizedPagePath, logoutUser: false }`.
|
|
403
|
+
*/
|
|
347
404
|
async checkError(error) {
|
|
348
405
|
const status = readStatus(error);
|
|
349
406
|
if (status === 401) {
|
|
@@ -361,6 +418,7 @@ let DefaultAuthProvider = DefaultAuthProvider_1 = class DefaultAuthProvider exte
|
|
|
361
418
|
}
|
|
362
419
|
return Promise.resolve();
|
|
363
420
|
}
|
|
421
|
+
/** Resolves the stored user; rejects when there is no `userId`. */
|
|
364
422
|
getIdentity(_params) {
|
|
365
423
|
const user = this.authService.getUser();
|
|
366
424
|
if (!user?.userId) {
|
|
@@ -368,9 +426,14 @@ let DefaultAuthProvider = DefaultAuthProvider_1 = class DefaultAuthProvider exte
|
|
|
368
426
|
}
|
|
369
427
|
return Promise.resolve(user);
|
|
370
428
|
}
|
|
429
|
+
/** Resolves with nothing; the roles come from `getRoles`. */
|
|
371
430
|
getPermissions(_params) {
|
|
372
431
|
return Promise.resolve();
|
|
373
432
|
}
|
|
433
|
+
/**
|
|
434
|
+
* Resolves the roles of the stored session. They are read from browser storage, which the user can
|
|
435
|
+
* edit: use them to show or hide interface only, and let the backend check every permission.
|
|
436
|
+
*/
|
|
374
437
|
getRoles(_params) {
|
|
375
438
|
return Promise.resolve(this.authService.getRoles());
|
|
376
439
|
}
|
|
@@ -378,6 +441,19 @@ let DefaultAuthProvider = DefaultAuthProvider_1 = class DefaultAuthProvider exte
|
|
|
378
441
|
* Refreshes the session through the network service's `refresh`, the one place a refresh is made:
|
|
379
442
|
* `true` when it was refreshed, `false` when the service has no refresh to make. The `401`
|
|
380
443
|
* recovery calls the same method, so one burst of 401s and this call share one refresh.
|
|
444
|
+
*
|
|
445
|
+
* @example
|
|
446
|
+
* ```tsx
|
|
447
|
+
* import { useRefreshToken } from '@venizia/ardor';
|
|
448
|
+
*
|
|
449
|
+
* // The data provider options name the refresh endpoint:
|
|
450
|
+
* // { url: '/api', authRecovery: { refreshTokenPath: '/auth/refresh-token' } }
|
|
451
|
+
* export const RefreshButton = () => {
|
|
452
|
+
* const refreshToken = useRefreshToken();
|
|
453
|
+
*
|
|
454
|
+
* return <button onClick={() => refreshToken()}>Refresh</button>;
|
|
455
|
+
* };
|
|
456
|
+
* ```
|
|
381
457
|
*/
|
|
382
458
|
async refreshToken() {
|
|
383
459
|
const isRefreshed = await this.restDataProvider.getNetworkService().refresh();
|
|
@@ -387,6 +463,18 @@ let DefaultAuthProvider = DefaultAuthProvider_1 = class DefaultAuthProvider exte
|
|
|
387
463
|
return isRefreshed;
|
|
388
464
|
}
|
|
389
465
|
value(container) {
|
|
466
|
+
// A subclass of DefaultAuthService with its own constructor must forward the application info to
|
|
467
|
+
// super(info): without it the service keeps the unscoped session, silently shared with every other
|
|
468
|
+
// application of the origin. Refuse that here, where the application info is at hand.
|
|
469
|
+
const id = container.get({
|
|
470
|
+
key: CoreBindings.APPLICATION_INFO,
|
|
471
|
+
isOptional: true,
|
|
472
|
+
})?.id;
|
|
473
|
+
if (id !== undefined && this.authService.keys.token !== LocalStorageKeys.scoped({ id }).token) {
|
|
474
|
+
throw getError({
|
|
475
|
+
message: `[DefaultAuthProvider] The auth service does not use the application id '${id}': a subclass of DefaultAuthService with its own constructor passes the application info to super(info)`,
|
|
476
|
+
});
|
|
477
|
+
}
|
|
390
478
|
// A subclass with its own constructor rarely forwards the optional 4th argument to `super`.
|
|
391
479
|
const permissionProvider = this.permissionProvider ??
|
|
392
480
|
container.get({
|
|
@@ -3,7 +3,15 @@ import { type TRequestType } from '@venizia/ardor-kernel';
|
|
|
3
3
|
import type { TExtraRequest } from '@venizia/ignis-kernel/repository';
|
|
4
4
|
import type { IListFilter } from './list-filter.js';
|
|
5
5
|
import { DefaultRestDataProvider } from './rest-data.js';
|
|
6
|
+
/**
|
|
7
|
+
* A `DefaultRestDataProvider` whose list reads take their `total` from the count route
|
|
8
|
+
* (`GET /{resource}/count` with the same `where`) instead of the `Content-Range` header.
|
|
9
|
+
*/
|
|
6
10
|
export declare class CountRestDataProvider<TResource extends string = string> extends DefaultRestDataProvider<TResource> {
|
|
11
|
+
/**
|
|
12
|
+
* Sends the list GET and the count GET together; `total` is the count's `count`, or `0` when the
|
|
13
|
+
* count answers none. Rejects when either request rejects.
|
|
14
|
+
*/
|
|
7
15
|
protected requestList<RecordType extends RaRecord = RaRecord>(opts: {
|
|
8
16
|
resource: TResource;
|
|
9
17
|
type: TRequestType;
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { RequestMethods, RequestTypes } from '@venizia/ardor-kernel';
|
|
2
2
|
import { DefaultRestDataProvider } from './rest-data.js';
|
|
3
|
+
/**
|
|
4
|
+
* A `DefaultRestDataProvider` whose list reads take their `total` from the count route
|
|
5
|
+
* (`GET /{resource}/count` with the same `where`) instead of the `Content-Range` header.
|
|
6
|
+
*/
|
|
3
7
|
export class CountRestDataProvider extends DefaultRestDataProvider {
|
|
8
|
+
/**
|
|
9
|
+
* Sends the list GET and the count GET together; `total` is the count's `count`, or `0` when the
|
|
10
|
+
* count answers none. Rejects when either request rejects.
|
|
11
|
+
*/
|
|
4
12
|
requestList(opts) {
|
|
5
13
|
const { resource, queryKey, filter } = opts;
|
|
6
14
|
// The base class reads the rows and keeps the route's list extras as `meta.extra`.
|
package/dist/providers/i18n.d.ts
CHANGED
|
@@ -7,6 +7,26 @@ import { LocaleSources } from './i18n-sources.js';
|
|
|
7
7
|
/**
|
|
8
8
|
* The options hold data (`i18nSources`, `languages`); the hooks are `resolveInitialLocale` and
|
|
9
9
|
* `composeMessages`, each taking one `opts` object.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { configuration, CoreBindings, englishMessages, provide, vietnameseMessages } from '@venizia/ardor';
|
|
14
|
+
*
|
|
15
|
+
* @configuration()
|
|
16
|
+
* export class I18nConfiguration {
|
|
17
|
+
* @provide({ key: CoreBindings.I18N_PROVIDER_OPTIONS })
|
|
18
|
+
* options() {
|
|
19
|
+
* return {
|
|
20
|
+
* languages: [{ locale: 'en', name: 'English' }, { locale: 'vi', name: 'Tieng Viet' }],
|
|
21
|
+
* i18nSources: {
|
|
22
|
+
* en: englishMessages,
|
|
23
|
+
* // A loader runs the first time the locale is used, such as () => import('./vi').
|
|
24
|
+
* vi: async () => vietnameseMessages,
|
|
25
|
+
* },
|
|
26
|
+
* };
|
|
27
|
+
* }
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
10
30
|
*/
|
|
11
31
|
export declare class DefaultI18nProvider extends BaseProvider<I18nProvider> {
|
|
12
32
|
protected i18nProviderOptions: II18nProviderOptions;
|
|
@@ -15,8 +35,8 @@ export declare class DefaultI18nProvider extends BaseProvider<I18nProvider> {
|
|
|
15
35
|
constructor(i18nProviderOptions: II18nProviderOptions, features?: Array<TFeature>);
|
|
16
36
|
/**
|
|
17
37
|
* The locale the admin starts in: the browser's language when it is one of `locales`, else `en`.
|
|
18
|
-
* Read when the provider is built, not when the module is imported
|
|
19
|
-
*
|
|
38
|
+
* Read when the provider is built, not when the module is imported. Without a `navigator` the
|
|
39
|
+
* language is `en-US`.
|
|
20
40
|
*/
|
|
21
41
|
protected resolveInitialLocale(opts: {
|
|
22
42
|
locales: Array<string>;
|
package/dist/providers/i18n.js
CHANGED
|
@@ -21,6 +21,26 @@ import { LazyLocales, LocaleSources } from './i18n-sources.js';
|
|
|
21
21
|
/**
|
|
22
22
|
* The options hold data (`i18nSources`, `languages`); the hooks are `resolveInitialLocale` and
|
|
23
23
|
* `composeMessages`, each taking one `opts` object.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* import { configuration, CoreBindings, englishMessages, provide, vietnameseMessages } from '@venizia/ardor';
|
|
28
|
+
*
|
|
29
|
+
* @configuration()
|
|
30
|
+
* export class I18nConfiguration {
|
|
31
|
+
* @provide({ key: CoreBindings.I18N_PROVIDER_OPTIONS })
|
|
32
|
+
* options() {
|
|
33
|
+
* return {
|
|
34
|
+
* languages: [{ locale: 'en', name: 'English' }, { locale: 'vi', name: 'Tieng Viet' }],
|
|
35
|
+
* i18nSources: {
|
|
36
|
+
* en: englishMessages,
|
|
37
|
+
* // A loader runs the first time the locale is used, such as () => import('./vi').
|
|
38
|
+
* vi: async () => vietnameseMessages,
|
|
39
|
+
* },
|
|
40
|
+
* };
|
|
41
|
+
* }
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
24
44
|
*/
|
|
25
45
|
let DefaultI18nProvider = DefaultI18nProvider_1 = class DefaultI18nProvider extends BaseProvider {
|
|
26
46
|
constructor(i18nProviderOptions, features = []) {
|
|
@@ -34,8 +54,8 @@ let DefaultI18nProvider = DefaultI18nProvider_1 = class DefaultI18nProvider exte
|
|
|
34
54
|
}
|
|
35
55
|
/**
|
|
36
56
|
* The locale the admin starts in: the browser's language when it is one of `locales`, else `en`.
|
|
37
|
-
* Read when the provider is built, not when the module is imported
|
|
38
|
-
*
|
|
57
|
+
* Read when the provider is built, not when the module is imported. Without a `navigator` the
|
|
58
|
+
* language is `en-US`.
|
|
39
59
|
*/
|
|
40
60
|
resolveInitialLocale(opts) {
|
|
41
61
|
const browserLanguage = typeof navigator === 'undefined' ? undefined : navigator.language;
|
|
@@ -4,16 +4,20 @@ import { type ICustomParams } from '@venizia/ardor-kernel';
|
|
|
4
4
|
/** The IGNIS filter a list request sends. Any other key of the list params is a filter key too. */
|
|
5
5
|
export interface IListFilter {
|
|
6
6
|
where: Record<string, unknown>;
|
|
7
|
+
/** Each entry is `'<field> <ASC|DESC>'`. */
|
|
7
8
|
order?: Array<string>;
|
|
8
9
|
limit?: number;
|
|
10
|
+
/** IGNIS's offset; `ListFilterBuilder.build` sets this one. */
|
|
9
11
|
skip?: number;
|
|
10
|
-
offset?: number;
|
|
11
12
|
include?: unknown;
|
|
12
13
|
fields?: unknown;
|
|
14
|
+
/** Not part of the filter sent: `ListFilterBuilder.build` moves it into the query string. */
|
|
13
15
|
params?: Record<string, unknown>;
|
|
16
|
+
/** Lifts `limit` and `skip`: the list is not paginated. */
|
|
14
17
|
noLimit?: boolean;
|
|
15
18
|
[key: string]: unknown;
|
|
16
19
|
}
|
|
20
|
+
/** Builds the filter, query and extras of a list request from react-admin's list params. */
|
|
17
21
|
export declare class ListFilterBuilder {
|
|
18
22
|
/** `filter.params` as the keys of the query string, as a copy. */
|
|
19
23
|
static extractQueryParams(opts: {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ValueUtility } from '@venizia/ardor-kernel';
|
|
2
|
+
/** Builds the filter, query and extras of a list request from react-admin's list params. */
|
|
2
3
|
export class ListFilterBuilder {
|
|
3
4
|
/** `filter.params` as the keys of the query string, as a copy. */
|
|
4
5
|
static extractQueryParams(opts) {
|
|
@@ -36,7 +37,7 @@ export class ListFilterBuilder {
|
|
|
36
37
|
}
|
|
37
38
|
else {
|
|
38
39
|
const { page = 1, perPage = 0 } = pagination ?? {};
|
|
39
|
-
// No `perPage` is no limit. `skip` is IGNIS's offset
|
|
40
|
+
// No `perPage` is no limit. `skip` is IGNIS's offset.
|
|
40
41
|
if (perPage > 0) {
|
|
41
42
|
filter['limit'] = perPage;
|
|
42
43
|
filter['skip'] = (Math.max(page, 1) - 1) * perPage;
|
|
@@ -1,14 +1,42 @@
|
|
|
1
1
|
import { type Container } from '@venizia/ignis-inversion';
|
|
2
2
|
import type { CreateParams, CreateResult, DeleteManyParams, DeleteManyResult, DeleteParams, DeleteResult, GetListParams, GetListResult, GetManyParams, GetManyReferenceParams, GetManyReferenceResult, GetManyResult, GetOneParams, GetOneResult, Identifier, QueryFunctionContext, RaRecord, UpdateManyParams, UpdateManyResult, UpdateParams, UpdateResult } from 'ra-core';
|
|
3
|
-
import { BaseProvider, DefaultNetworkRequestService, type IApplicationInfo, type INetworkResponse, type ICustomParams, type IRestDataProviderOptions, type ISendParams, type
|
|
3
|
+
import { BaseProvider, type DefaultAuthService, DefaultNetworkRequestService, type IApplicationInfo, type INetworkResponse, type ICustomParams, type IRestDataProviderOptions, type ISendParams, type TRequestType } from '@venizia/ardor-kernel';
|
|
4
4
|
import type { TExtraRequest } from '@venizia/ignis-kernel/repository';
|
|
5
5
|
import type { IDataProvider, IRestRequestOptions } from '../common/index.js';
|
|
6
6
|
import { type IListFilter } from './list-filter.js';
|
|
7
|
+
/**
|
|
8
|
+
* The react-admin data provider over an IGNIS REST API. Each method takes one `opts` object
|
|
9
|
+
* (`{ resource, params }`) and resolves the result react-admin expects; it rejects with the error
|
|
10
|
+
* the network service throws (a non-2xx response, an empty base URL, an auth failure that cannot be
|
|
11
|
+
* recovered). A subclass overrides the protected hooks (`createNetworkService`, `buildListFilter`,
|
|
12
|
+
* `requestList`).
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { type BaseArdorApplication, configuration, CoreBindings, DefaultRestDataProvider, inject, provide } from '@venizia/ardor';
|
|
17
|
+
*
|
|
18
|
+
* @configuration()
|
|
19
|
+
* export class DataConfiguration {
|
|
20
|
+
* constructor(@inject({ key: CoreBindings.APPLICATION_INSTANCE }) private readonly application: BaseArdorApplication) {}
|
|
21
|
+
*
|
|
22
|
+
* @provide({ key: CoreBindings.REST_DATA_PROVIDER_OPTIONS })
|
|
23
|
+
* options() {
|
|
24
|
+
* return { url: '/api', noAuthPaths: ['/auth/sign-in'] };
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* @provide({ key: CoreBindings.DEFAULT_REST_DATA_PROVIDER })
|
|
28
|
+
* dataProvider() {
|
|
29
|
+
* return this.application.instantiate(DefaultRestDataProvider).value(this.application);
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
7
34
|
export declare class DefaultRestDataProvider<TResource extends string = string> extends BaseProvider<IDataProvider<TResource>> {
|
|
8
35
|
protected restDataProviderOptions: IRestDataProviderOptions;
|
|
9
36
|
protected applicationInfo: IApplicationInfo;
|
|
10
37
|
private cachedNetworkService?;
|
|
11
|
-
|
|
38
|
+
/** The auth service of the container, set when `value` builds the provider; pass it to the network service you return from `createNetworkService`. */
|
|
39
|
+
protected authService?: DefaultAuthService;
|
|
12
40
|
constructor(restDataProviderOptions: IRestDataProviderOptions, applicationInfo: IApplicationInfo);
|
|
13
41
|
/**
|
|
14
42
|
* Builds the network service this provider sends through. Override it to return a subclass of
|
|
@@ -17,6 +45,7 @@ export declare class DefaultRestDataProvider<TResource extends string = string>
|
|
|
17
45
|
*/
|
|
18
46
|
protected createNetworkService(): DefaultNetworkRequestService;
|
|
19
47
|
protected get networkService(): DefaultNetworkRequestService;
|
|
48
|
+
/** The network service every request of this provider goes through; built on first use. */
|
|
20
49
|
getNetworkService(): DefaultNetworkRequestService;
|
|
21
50
|
/**
|
|
22
51
|
* The ids a bulk write touched, read from the affected rows the server answers (not the ids asked
|
|
@@ -70,48 +99,81 @@ export declare class DefaultRestDataProvider<TResource extends string = string>
|
|
|
70
99
|
filename?: string;
|
|
71
100
|
contentDisposition?: string;
|
|
72
101
|
}>;
|
|
102
|
+
/**
|
|
103
|
+
* `GET /{resource}` with the filter built from the list params in the query. Resolves the rows and
|
|
104
|
+
* `total` (the `Content-Range` total, else the page's row count), plus `meta.extra` when a route
|
|
105
|
+
* answered extras.
|
|
106
|
+
*/
|
|
73
107
|
getList<RecordType extends RaRecord = RaRecord>(opts: {
|
|
74
108
|
resource: TResource;
|
|
75
109
|
params: GetListParams & QueryFunctionContext & ICustomParams;
|
|
76
110
|
}): Promise<GetListResult<RecordType>>;
|
|
111
|
+
/**
|
|
112
|
+
* `GET /{resource}/{id}`, with `params.meta.filter` as the query filter. Resolves `{ data }`.
|
|
113
|
+
*/
|
|
77
114
|
getOne<RecordType extends RaRecord = RaRecord>(opts: {
|
|
78
115
|
resource: TResource;
|
|
79
116
|
params: GetOneParams<RecordType> & QueryFunctionContext & ICustomParams;
|
|
80
117
|
}): Promise<GetOneResult<RecordType>>;
|
|
118
|
+
/**
|
|
119
|
+
* `GET /{resource}` with the ids in `filter.where.id.inq`, merged into `params.meta.filter`.
|
|
120
|
+
* Resolves `{ data }`.
|
|
121
|
+
*/
|
|
81
122
|
getMany<RecordType extends RaRecord = RaRecord>(opts: {
|
|
82
123
|
resource: TResource;
|
|
83
124
|
params: GetManyParams<RecordType> & QueryFunctionContext & ICustomParams;
|
|
84
125
|
}): Promise<GetManyResult<RecordType>>;
|
|
126
|
+
/**
|
|
127
|
+
* As `getList`, with `filter.where[target] = id`. Resolves the rows and `total`.
|
|
128
|
+
*/
|
|
85
129
|
getManyReference<RecordType extends RaRecord = RaRecord>(opts: {
|
|
86
130
|
resource: TResource;
|
|
87
131
|
params: GetManyReferenceParams & QueryFunctionContext & ICustomParams;
|
|
88
132
|
}): Promise<GetManyReferenceResult<RecordType>>;
|
|
133
|
+
/** `PATCH /{resource}/{id}` with `params.data` as the body. Resolves `{ data }`. */
|
|
89
134
|
update<RecordType extends RaRecord = RaRecord>(opts: {
|
|
90
135
|
resource: TResource;
|
|
91
136
|
params: UpdateParams;
|
|
92
137
|
}): Promise<UpdateResult<RecordType>>;
|
|
138
|
+
/**
|
|
139
|
+
* `PATCH /{resource}` with `{ ...params.data, where: { id: { inq: ids } } }` as the body. Resolves
|
|
140
|
+
* `{ data }`, the ids of the rows the server answered (`undefined` when the answer is not a row
|
|
141
|
+
* array). Rejects when `ids` is empty or `params.data` has a `where` field, before any request.
|
|
142
|
+
*/
|
|
93
143
|
updateMany<RecordType extends RaRecord = RaRecord>(opts: {
|
|
94
144
|
resource: TResource;
|
|
95
145
|
params: UpdateManyParams;
|
|
96
146
|
}): Promise<UpdateManyResult<RecordType>>;
|
|
147
|
+
/** `POST /{resource}` with `params.data` as the body. Resolves `{ data }`, the created record. */
|
|
97
148
|
create<RecordType extends Omit<RaRecord, 'id'> = Omit<RaRecord, 'id'>, ResultRecordType extends RaRecord = RecordType & {
|
|
98
149
|
id: Identifier;
|
|
99
150
|
}>(opts: {
|
|
100
151
|
resource: TResource;
|
|
101
152
|
params: CreateParams;
|
|
102
153
|
}): Promise<CreateResult<ResultRecordType>>;
|
|
154
|
+
/** `DELETE /{resource}/{id}`. Resolves `{ data }`, the record the server answered. */
|
|
103
155
|
delete<RecordType extends RaRecord = RaRecord>(opts: {
|
|
104
156
|
resource: TResource;
|
|
105
157
|
params: DeleteParams<RecordType>;
|
|
106
158
|
}): Promise<DeleteResult<RecordType>>;
|
|
159
|
+
/**
|
|
160
|
+
* `DELETE /{resource}` with `{ where: { id: { inq: ids } } }` as the body. Resolves `{ data }`, the
|
|
161
|
+
* ids of the rows the server answered (`undefined` when the answer is not a row array). Rejects
|
|
162
|
+
* when `ids` is empty, before any request.
|
|
163
|
+
*/
|
|
107
164
|
deleteMany<RecordType extends RaRecord = RaRecord>(opts: {
|
|
108
165
|
resource: TResource;
|
|
109
166
|
params: DeleteManyParams<RecordType>;
|
|
110
167
|
}): Promise<DeleteManyResult<RecordType>>;
|
|
168
|
+
/**
|
|
169
|
+
* Sends `params.method` to `/{resource}` with the provider's headers and auth; `requestType`
|
|
170
|
+
* defaults to `RequestTypes.SEND`. Resolves the response read by that type. Rejects when
|
|
171
|
+
* `params.method` is missing, before any request.
|
|
172
|
+
*/
|
|
111
173
|
send<TResult = unknown>(opts: {
|
|
112
174
|
resource: TResource;
|
|
113
175
|
params: ISendParams;
|
|
114
|
-
}): Promise<
|
|
176
|
+
}): Promise<INetworkResponse<TResult>>;
|
|
115
177
|
/**
|
|
116
178
|
* react-admin's data provider contract takes positional arguments, `(resource, params)`: the one
|
|
117
179
|
* place this class does not take an `opts` object. Each method hands them to the `opts` method.
|
|
@@ -14,6 +14,52 @@ var DefaultRestDataProvider_1;
|
|
|
14
14
|
import { getError, inject } from '@venizia/ignis-inversion';
|
|
15
15
|
import { BaseProvider, CoreBindings, DefaultNetworkRequestService, ValueUtility, NetworkHeaders, RequestCountData, RequestMethods, RequestTypes, } from '@venizia/ardor-kernel';
|
|
16
16
|
import { ListFilterBuilder } from './list-filter.js';
|
|
17
|
+
/**
|
|
18
|
+
* The path of one record: the id is a single path segment, whatever characters it holds. An empty
|
|
19
|
+
* id, `.` and `..` (dot segments even when written `%2E`) and a lone surrogate (which cannot be
|
|
20
|
+
* encoded) are refused, without naming the id.
|
|
21
|
+
*/
|
|
22
|
+
const recordPaths = (opts) => {
|
|
23
|
+
let segment = '';
|
|
24
|
+
try {
|
|
25
|
+
segment = encodeURIComponent(`${opts.id}`);
|
|
26
|
+
// eslint-disable-next-line venizia/no-silent-catch -- a lone surrogate cannot be encoded: the empty segment below refuses it
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
segment = '';
|
|
30
|
+
}
|
|
31
|
+
if (segment === '' || segment === '.' || segment === '..') {
|
|
32
|
+
throw getError({ message: '[recordPaths] The record id is not a valid path segment' });
|
|
33
|
+
}
|
|
34
|
+
return [opts.resource, segment];
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* The react-admin data provider over an IGNIS REST API. Each method takes one `opts` object
|
|
38
|
+
* (`{ resource, params }`) and resolves the result react-admin expects; it rejects with the error
|
|
39
|
+
* the network service throws (a non-2xx response, an empty base URL, an auth failure that cannot be
|
|
40
|
+
* recovered). A subclass overrides the protected hooks (`createNetworkService`, `buildListFilter`,
|
|
41
|
+
* `requestList`).
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* import { type BaseArdorApplication, configuration, CoreBindings, DefaultRestDataProvider, inject, provide } from '@venizia/ardor';
|
|
46
|
+
*
|
|
47
|
+
* @configuration()
|
|
48
|
+
* export class DataConfiguration {
|
|
49
|
+
* constructor(@inject({ key: CoreBindings.APPLICATION_INSTANCE }) private readonly application: BaseArdorApplication) {}
|
|
50
|
+
*
|
|
51
|
+
* @provide({ key: CoreBindings.REST_DATA_PROVIDER_OPTIONS })
|
|
52
|
+
* options() {
|
|
53
|
+
* return { url: '/api', noAuthPaths: ['/auth/sign-in'] };
|
|
54
|
+
* }
|
|
55
|
+
*
|
|
56
|
+
* @provide({ key: CoreBindings.DEFAULT_REST_DATA_PROVIDER })
|
|
57
|
+
* dataProvider() {
|
|
58
|
+
* return this.application.instantiate(DefaultRestDataProvider).value(this.application);
|
|
59
|
+
* }
|
|
60
|
+
* }
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
17
63
|
let DefaultRestDataProvider = DefaultRestDataProvider_1 = class DefaultRestDataProvider extends BaseProvider {
|
|
18
64
|
constructor(restDataProviderOptions, applicationInfo) {
|
|
19
65
|
super({ scope: DefaultRestDataProvider_1.name });
|
|
@@ -40,6 +86,7 @@ let DefaultRestDataProvider = DefaultRestDataProvider_1 = class DefaultRestDataP
|
|
|
40
86
|
this.cachedNetworkService ??= this.createNetworkService();
|
|
41
87
|
return this.cachedNetworkService;
|
|
42
88
|
}
|
|
89
|
+
/** The network service every request of this provider goes through; built on first use. */
|
|
43
90
|
getNetworkService() {
|
|
44
91
|
return this.networkService;
|
|
45
92
|
}
|
|
@@ -112,6 +159,11 @@ let DefaultRestDataProvider = DefaultRestDataProvider_1 = class DefaultRestDataP
|
|
|
112
159
|
return answered ? { ...rest, meta: { extra: answered } } : rest;
|
|
113
160
|
});
|
|
114
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* `GET /{resource}` with the filter built from the list params in the query. Resolves the rows and
|
|
164
|
+
* `total` (the `Content-Range` total, else the page's row count), plus `meta.extra` when a route
|
|
165
|
+
* answered extras.
|
|
166
|
+
*/
|
|
115
167
|
async getList(opts) {
|
|
116
168
|
const { resource, params } = opts;
|
|
117
169
|
const { filter, queryKey, extra } = this.buildListFilter({ params });
|
|
@@ -123,6 +175,9 @@ let DefaultRestDataProvider = DefaultRestDataProvider_1 = class DefaultRestDataP
|
|
|
123
175
|
extra,
|
|
124
176
|
});
|
|
125
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* `GET /{resource}/{id}`, with `params.meta.filter` as the query filter. Resolves `{ data }`.
|
|
180
|
+
*/
|
|
126
181
|
async getOne(opts) {
|
|
127
182
|
const { resource, params } = opts;
|
|
128
183
|
const { params: filterParams, ...filter } = params?.meta?.filter ?? {};
|
|
@@ -131,10 +186,14 @@ let DefaultRestDataProvider = DefaultRestDataProvider_1 = class DefaultRestDataP
|
|
|
131
186
|
resource,
|
|
132
187
|
type: RequestTypes.GET_ONE,
|
|
133
188
|
method: RequestMethods.GET,
|
|
134
|
-
paths:
|
|
189
|
+
paths: recordPaths({ resource, id: params.id }),
|
|
135
190
|
query: { ...queryKey, filter },
|
|
136
191
|
});
|
|
137
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* `GET /{resource}` with the ids in `filter.where.id.inq`, merged into `params.meta.filter`.
|
|
195
|
+
* Resolves `{ data }`.
|
|
196
|
+
*/
|
|
138
197
|
async getMany(opts) {
|
|
139
198
|
const { resource, params } = opts;
|
|
140
199
|
const { params: filterParams, ...filter } = params?.meta?.filter ?? {};
|
|
@@ -154,6 +213,9 @@ let DefaultRestDataProvider = DefaultRestDataProvider_1 = class DefaultRestDataP
|
|
|
154
213
|
},
|
|
155
214
|
});
|
|
156
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* As `getList`, with `filter.where[target] = id`. Resolves the rows and `total`.
|
|
218
|
+
*/
|
|
157
219
|
async getManyReference(opts) {
|
|
158
220
|
const { resource, params } = opts;
|
|
159
221
|
const { target, id, ...listParams } = params;
|
|
@@ -167,16 +229,22 @@ let DefaultRestDataProvider = DefaultRestDataProvider_1 = class DefaultRestDataP
|
|
|
167
229
|
extra,
|
|
168
230
|
});
|
|
169
231
|
}
|
|
232
|
+
/** `PATCH /{resource}/{id}` with `params.data` as the body. Resolves `{ data }`. */
|
|
170
233
|
async update(opts) {
|
|
171
234
|
const { resource, params } = opts;
|
|
172
235
|
return this.request({
|
|
173
236
|
resource,
|
|
174
237
|
type: RequestTypes.UPDATE,
|
|
175
238
|
method: RequestMethods.PATCH,
|
|
176
|
-
paths:
|
|
239
|
+
paths: recordPaths({ resource, id: params.id }),
|
|
177
240
|
body: params.data,
|
|
178
241
|
});
|
|
179
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* `PATCH /{resource}` with `{ ...params.data, where: { id: { inq: ids } } }` as the body. Resolves
|
|
245
|
+
* `{ data }`, the ids of the rows the server answered (`undefined` when the answer is not a row
|
|
246
|
+
* array). Rejects when `ids` is empty or `params.data` has a `where` field, before any request.
|
|
247
|
+
*/
|
|
180
248
|
async updateMany(opts) {
|
|
181
249
|
const { resource, params } = opts;
|
|
182
250
|
const { ids, data } = params;
|
|
@@ -199,6 +267,7 @@ let DefaultRestDataProvider = DefaultRestDataProvider_1 = class DefaultRestDataP
|
|
|
199
267
|
return { data: this.toAffectedIds({ rows: result.data }) };
|
|
200
268
|
});
|
|
201
269
|
}
|
|
270
|
+
/** `POST /{resource}` with `params.data` as the body. Resolves `{ data }`, the created record. */
|
|
202
271
|
async create(opts) {
|
|
203
272
|
const { resource, params } = opts;
|
|
204
273
|
return this.request({
|
|
@@ -208,15 +277,21 @@ let DefaultRestDataProvider = DefaultRestDataProvider_1 = class DefaultRestDataP
|
|
|
208
277
|
body: params.data,
|
|
209
278
|
});
|
|
210
279
|
}
|
|
280
|
+
/** `DELETE /{resource}/{id}`. Resolves `{ data }`, the record the server answered. */
|
|
211
281
|
async delete(opts) {
|
|
212
282
|
const { resource, params } = opts;
|
|
213
283
|
return this.request({
|
|
214
284
|
resource,
|
|
215
285
|
type: RequestTypes.DELETE,
|
|
216
286
|
method: RequestMethods.DELETE,
|
|
217
|
-
paths:
|
|
287
|
+
paths: recordPaths({ resource, id: params.id }),
|
|
218
288
|
});
|
|
219
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* `DELETE /{resource}` with `{ where: { id: { inq: ids } } }` as the body. Resolves `{ data }`, the
|
|
292
|
+
* ids of the rows the server answered (`undefined` when the answer is not a row array). Rejects
|
|
293
|
+
* when `ids` is empty, before any request.
|
|
294
|
+
*/
|
|
220
295
|
async deleteMany(opts) {
|
|
221
296
|
const { resource, params } = opts;
|
|
222
297
|
const { ids } = params;
|
|
@@ -233,6 +308,11 @@ let DefaultRestDataProvider = DefaultRestDataProvider_1 = class DefaultRestDataP
|
|
|
233
308
|
return { data: this.toAffectedIds({ rows: result.data }) };
|
|
234
309
|
});
|
|
235
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* Sends `params.method` to `/{resource}` with the provider's headers and auth; `requestType`
|
|
313
|
+
* defaults to `RequestTypes.SEND`. Resolves the response read by that type. Rejects when
|
|
314
|
+
* `params.method` is missing, before any request.
|
|
315
|
+
*/
|
|
236
316
|
async send(opts) {
|
|
237
317
|
const { resource, params } = opts;
|
|
238
318
|
if (!params?.method) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@venizia/ardor-admin",
|
|
3
|
-
"version": "0.4.8-
|
|
3
|
+
"version": "0.4.8-10",
|
|
4
4
|
"description": "ARDOR react-admin adapter - the REST data provider, auth provider and i18n provider wired through IGNIS inversion, the ArdorApplication root component, the translate and notify hooks, and the English and Vietnamese message bundles.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"admin",
|
|
@@ -57,12 +57,11 @@
|
|
|
57
57
|
"access": "public"
|
|
58
58
|
},
|
|
59
59
|
"scripts": {
|
|
60
|
-
"force-update": "sh ../../scripts/build/force-update.sh",
|
|
61
60
|
"prepublishOnly": "make rebuild"
|
|
62
61
|
},
|
|
63
62
|
"dependencies": {
|
|
64
|
-
"@venizia/ardor-kernel": "0.4.8-
|
|
65
|
-
"@venizia/ardor-react": "0.4.8-
|
|
63
|
+
"@venizia/ardor-kernel": "0.4.8-10",
|
|
64
|
+
"@venizia/ardor-react": "0.4.8-10"
|
|
66
65
|
},
|
|
67
66
|
"devDependencies": {
|
|
68
67
|
"@happy-dom/global-registrator": "^21.0.4",
|
|
@@ -72,11 +71,11 @@
|
|
|
72
71
|
"@testing-library/react": "^16.3.3",
|
|
73
72
|
"@types/bun": "^1.4.3",
|
|
74
73
|
"@types/react": "^19",
|
|
75
|
-
"@venizia/dev-configs": "^0.4.8-
|
|
76
|
-
"@venizia/ignis-filter": "^0.4.7-
|
|
77
|
-
"@venizia/ignis-helpers": "^0.4.8-
|
|
78
|
-
"@venizia/ignis-inversion": "^0.4.7-
|
|
79
|
-
"@venizia/ignis-kernel": "^0.4.8-
|
|
74
|
+
"@venizia/dev-configs": "^0.4.8-5",
|
|
75
|
+
"@venizia/ignis-filter": "^0.4.7-4",
|
|
76
|
+
"@venizia/ignis-helpers": "^0.4.8-8",
|
|
77
|
+
"@venizia/ignis-inversion": "^0.4.7-3",
|
|
78
|
+
"@venizia/ignis-kernel": "^0.4.8-9",
|
|
80
79
|
"eslint": "^10.13.0",
|
|
81
80
|
"oxlint": "1.89.0",
|
|
82
81
|
"oxlint-tsgolint": "7.0.2004",
|
|
@@ -94,10 +93,10 @@
|
|
|
94
93
|
"peerDependencies": {
|
|
95
94
|
"@reduxjs/toolkit": ">=2.13.0",
|
|
96
95
|
"@tanstack/react-query": ">=5.104.5",
|
|
97
|
-
"@venizia/ignis-filter": "^0.4.7-
|
|
98
|
-
"@venizia/ignis-helpers": "^0.4.8-
|
|
99
|
-
"@venizia/ignis-inversion": "^0.4.7-
|
|
100
|
-
"@venizia/ignis-kernel": "^0.4.8-
|
|
96
|
+
"@venizia/ignis-filter": "^0.4.7-4",
|
|
97
|
+
"@venizia/ignis-helpers": "^0.4.8-8",
|
|
98
|
+
"@venizia/ignis-inversion": "^0.4.7-3",
|
|
99
|
+
"@venizia/ignis-kernel": "^0.4.8-9",
|
|
101
100
|
"ra-core": ">=5.15.5",
|
|
102
101
|
"ra-i18n-polyglot": ">=5.15.5",
|
|
103
102
|
"react": ">=19.3.1",
|
|
@@ -124,7 +123,7 @@
|
|
|
124
123
|
"react-redux",
|
|
125
124
|
"react-router-dom"
|
|
126
125
|
],
|
|
127
|
-
"limit": "
|
|
126
|
+
"limit": "11200 B"
|
|
128
127
|
}
|
|
129
128
|
],
|
|
130
129
|
"engines": {
|