@rebasepro/app 0.13.1-canary.gef9608c → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/collections/property_presentation.d.ts +18 -3
- package/dist/components/LoginView/oauth-redirect-flow.d.ts +86 -0
- package/dist/components/common/useColumnsIds.d.ts +22 -0
- package/dist/core/Rebase.d.ts +1 -1
- package/dist/core/RebaseProps.d.ts +10 -2
- package/dist/hooks/data/collectionQuery.d.ts +45 -0
- package/dist/hooks/data/useRelationSelector.d.ts +12 -1
- package/dist/index.es.js +508 -215
- package/dist/index.es.js.map +1 -1
- package/dist/util/entity_cache.d.ts +19 -3
- package/dist/util/icons.d.ts +5 -1
- package/package.json +7 -7
- package/src/auth/useRebaseAuthController.ts +6 -0
- package/src/collections/property_presentation.ts +39 -4
- package/src/components/LoginView/LoginView.tsx +68 -31
- package/src/components/LoginView/oauth-redirect-flow.ts +184 -0
- package/src/components/common/useColumnsIds.tsx +66 -9
- package/src/components/common/useDataTableController.tsx +10 -14
- package/src/core/Rebase.tsx +1 -1
- package/src/core/RebaseProps.tsx +10 -2
- package/src/hooks/data/collectionQuery.ts +55 -0
- package/src/hooks/data/useCollection.tsx +29 -21
- package/src/hooks/data/useFetch.tsx +17 -2
- package/src/hooks/data/useRelationSelector.tsx +46 -8
- package/src/hooks/useBuildLocalConfigurationPersistence.tsx +20 -12
- package/src/locales/de.ts +1 -0
- package/src/locales/en.ts +1 -0
- package/src/locales/es.ts +1 -0
- package/src/locales/fr.ts +1 -0
- package/src/locales/hi.ts +2 -1
- package/src/locales/it.ts +1 -0
- package/src/locales/pt.ts +1 -0
- package/src/util/entity_cache.ts +34 -31
- package/src/util/icons.tsx +10 -30
|
@@ -10,11 +10,26 @@
|
|
|
10
10
|
* That last one is worth knowing about rather than assuming: the collection editor
|
|
11
11
|
* has a whole Conditions UI, `serializable_utils` persists what it writes, and
|
|
12
12
|
* `BaseProperty.conditions` documents itself as "evaluated at runtime like property
|
|
13
|
-
* builders" — but the evaluator below is reached only from its own tests.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* builders" — but the evaluator below is reached only from its own tests. It lives
|
|
14
|
+
* here because here is where it would be called from once it is wired up.
|
|
15
|
+
*
|
|
16
|
+
* The one part of `conditions` that *is* applied is the literal case:
|
|
17
|
+
* `hidden`/`readOnly`/`disabled` stated as a plain boolean rather than as a rule.
|
|
18
|
+
* A literal needs no context, so `isHidden`/`isReadOnly`/`isDisabled` can answer
|
|
19
|
+
* it directly, and those three gates are consulted everywhere a field is laid
|
|
20
|
+
* out. A *rule* still is not evaluated anywhere in production — the split is
|
|
21
|
+
* deliberate, not an oversight: it is the difference between a condition that
|
|
22
|
+
* needs an entity to be evaluated against and one that does not.
|
|
16
23
|
*/
|
|
17
24
|
import type { ConditionContext, Property } from "@rebasepro/types";
|
|
18
25
|
export declare function isReadOnly(property: Property): boolean;
|
|
19
26
|
export declare function isHidden(property: Property): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Whether the field is disabled by its own declaration, ignoring form state.
|
|
29
|
+
*
|
|
30
|
+
* The `admin.disabled` block and `conditions.disabled: true` say the same thing
|
|
31
|
+
* two ways, so every caller that gated on the first now asks here instead of
|
|
32
|
+
* growing a second check of its own.
|
|
33
|
+
*/
|
|
34
|
+
export declare function isDisabled(property: Property): boolean;
|
|
20
35
|
export declare function applyPropertyConditions(property: Property, context: ConditionContext): Property;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The browser half of the OAuth authorization-code flow.
|
|
3
|
+
*
|
|
4
|
+
* The server only ever sees a `code` and a `redirectUri` taken from the
|
|
5
|
+
* request body, so `state`, PKCE and the binding between "this browser started
|
|
6
|
+
* a login" and "this code came back" are, by construction, the client's job.
|
|
7
|
+
* In the admin login they had been nobody's job: the authorize URL carried no
|
|
8
|
+
* `state`, and the return leg accepted any `?code=` as long as a localStorage
|
|
9
|
+
* marker naming the *provider* was set — a marker written on button click and
|
|
10
|
+
* removed only on a callback that carried a code, so abandoning the flow left
|
|
11
|
+
* it set indefinitely.
|
|
12
|
+
*
|
|
13
|
+
* Everything here is provider-agnostic on purpose. A button that wants a new
|
|
14
|
+
* provider calls {@link startOAuthRedirect} and gets state, expiry and PKCE
|
|
15
|
+
* without deciding anything, which is what stops the next provider shipping
|
|
16
|
+
* without them.
|
|
17
|
+
*/
|
|
18
|
+
export interface OAuthRedirectRequest {
|
|
19
|
+
/** Provider id, as mounted at `POST /auth/<id>`. */
|
|
20
|
+
provider: string;
|
|
21
|
+
/** The provider's authorization endpoint. */
|
|
22
|
+
authorizeUrl: string;
|
|
23
|
+
clientId: string;
|
|
24
|
+
scope: string;
|
|
25
|
+
/** Where the provider sends the browser back. Echoed to the token exchange. */
|
|
26
|
+
redirectUri: string;
|
|
27
|
+
/** Set when the provider implements PKCE. */
|
|
28
|
+
pkce?: boolean;
|
|
29
|
+
/** Extra authorize-endpoint parameters (`response_type`, and so on). */
|
|
30
|
+
params?: Record<string, string>;
|
|
31
|
+
}
|
|
32
|
+
/** What the browser remembers between leaving for the provider and coming back. */
|
|
33
|
+
export interface PendingOAuthRedirect {
|
|
34
|
+
provider: string;
|
|
35
|
+
state: string;
|
|
36
|
+
codeVerifier?: string;
|
|
37
|
+
redirectUri: string;
|
|
38
|
+
startedAt: number;
|
|
39
|
+
}
|
|
40
|
+
export type OAuthCallbackResult =
|
|
41
|
+
/** No OAuth callback in this URL. */
|
|
42
|
+
{
|
|
43
|
+
status: "none";
|
|
44
|
+
}
|
|
45
|
+
/** The provider reported a failure, or the user declined. */
|
|
46
|
+
| {
|
|
47
|
+
status: "error";
|
|
48
|
+
error: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A code arrived that this browser cannot account for: no pending
|
|
52
|
+
* authorization, an expired one, or a `state` that does not match. This is
|
|
53
|
+
* what a login-CSRF / code-injection attempt looks like.
|
|
54
|
+
*/
|
|
55
|
+
| {
|
|
56
|
+
status: "mismatch";
|
|
57
|
+
} | {
|
|
58
|
+
status: "ok";
|
|
59
|
+
provider: string;
|
|
60
|
+
code: string;
|
|
61
|
+
codeVerifier?: string;
|
|
62
|
+
redirectUri: string;
|
|
63
|
+
};
|
|
64
|
+
type Storage = Pick<globalThis.Storage, "getItem" | "setItem" | "removeItem">;
|
|
65
|
+
export declare function readPendingOAuthRedirect(storage?: Storage | null): PendingOAuthRedirect | null;
|
|
66
|
+
export declare function clearPendingOAuthRedirect(storage?: Storage | null): void;
|
|
67
|
+
/**
|
|
68
|
+
* Build the authorize URL and the pending record that will validate its
|
|
69
|
+
* callback. Split out from {@link startOAuthRedirect} so it can be tested
|
|
70
|
+
* without navigating.
|
|
71
|
+
*/
|
|
72
|
+
export declare function buildOAuthAuthorization(request: OAuthRedirectRequest, now?: number): Promise<{
|
|
73
|
+
url: string;
|
|
74
|
+
pending: PendingOAuthRedirect;
|
|
75
|
+
}>;
|
|
76
|
+
/** Remember the authorization and send the browser to the provider. */
|
|
77
|
+
export declare function startOAuthRedirect(request: OAuthRedirectRequest, storage?: Storage | null): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Interpret a return from the provider.
|
|
80
|
+
*
|
|
81
|
+
* Clears the pending record on **every** path that saw a callback — including
|
|
82
|
+
* `?error=` and a state mismatch — so a declined or abandoned consent screen
|
|
83
|
+
* cannot leave the browser primed to accept somebody else's code later.
|
|
84
|
+
*/
|
|
85
|
+
export declare function consumeOAuthCallback(search: string, storage?: Storage | null, now?: number): OAuthCallbackResult;
|
|
86
|
+
export {};
|
|
@@ -5,6 +5,28 @@ export type PropertyColumnConfig = {
|
|
|
5
5
|
disabled: boolean;
|
|
6
6
|
};
|
|
7
7
|
export declare function getSubcollectionColumnId(collection: AdminCollection<any>): string;
|
|
8
|
+
/**
|
|
9
|
+
* The jump-to-tab columns a collection table does not need, because the relation
|
|
10
|
+
* behind them already has a column of its own.
|
|
11
|
+
*
|
|
12
|
+
* Every child view gets a 200px button column that opens its tab. For a relation
|
|
13
|
+
* declared in `relations` that button is the relation's only presence in the
|
|
14
|
+
* table. For one declared as a property it is the second: the property's own
|
|
15
|
+
* column is already there, hydrated by the list fetch's `include: ["*"]`, showing
|
|
16
|
+
* the child rows themselves — and carrying the *same heading*, because the tab
|
|
17
|
+
* takes its name from the declaring property. Two columns called "Applications",
|
|
18
|
+
* one of them a button, and nothing in the header to tell them apart.
|
|
19
|
+
*
|
|
20
|
+
* The property column wins: it shows what the children are, and each chip in it
|
|
21
|
+
* opens one. The tab stays reachable by opening the record, which is what the
|
|
22
|
+
* rest of the table's rows do anyway.
|
|
23
|
+
*
|
|
24
|
+
* Unless the author hid that column — `hideFromCollection` on the property, or a
|
|
25
|
+
* `propertiesOrder` that omits it, is a statement about the column and not about
|
|
26
|
+
* the relation, so the button comes back rather than the relation dropping out
|
|
27
|
+
* of the table altogether.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getRedundantChildViewColumnIds<M extends Record<string, any>>(collection: AdminCollection<M>): Set<string>;
|
|
8
30
|
export declare function useColumnIds<M extends Record<string, any>>(collection: AdminCollection<M>, includeSubcollections: boolean): PropertyColumnConfig[];
|
|
9
31
|
export declare function getColumnKeysForProperty(property: Property, key: string, disabled?: boolean): PropertyColumnConfig[];
|
|
10
32
|
export declare function getFormFieldKeys(collection: AdminCollection): string[];
|
package/dist/core/Rebase.d.ts
CHANGED
|
@@ -12,4 +12,4 @@ import { User } from "@rebasepro/types";
|
|
|
12
12
|
*
|
|
13
13
|
* @group Core
|
|
14
14
|
*/
|
|
15
|
-
export declare function Rebase<USER extends User>(props: RebaseProps<USER>): React.JSX.Element;
|
|
15
|
+
export declare function Rebase<USER extends User, DB = unknown>(props: RebaseProps<USER, DB>): React.JSX.Element;
|
|
@@ -99,7 +99,7 @@ import type { EffectiveRoleController } from "@rebasepro/types";
|
|
|
99
99
|
*
|
|
100
100
|
* @group Models
|
|
101
101
|
*/
|
|
102
|
-
export type RebaseProps<USER extends User> = {
|
|
102
|
+
export type RebaseProps<USER extends User, DB = unknown> = {
|
|
103
103
|
/**
|
|
104
104
|
* The root components of your application. Use RebaseAdmin, RebaseStudio, and RebaseShell.
|
|
105
105
|
* Alternatively, pass a render function that receives { context, loading }.
|
|
@@ -140,8 +140,16 @@ export type RebaseProps<USER extends User> = {
|
|
|
140
140
|
* entry keyed `"(default)"` carries a driver.
|
|
141
141
|
* `client.auth` is subscribed unless `authController` is provided.
|
|
142
142
|
* `client.storage` is used unless `storageSource` is provided.
|
|
143
|
+
*
|
|
144
|
+
* `DB` is inferred from whatever is passed, and defaults to `unknown` for
|
|
145
|
+
* an untyped client. It has to be a parameter rather than a fixed
|
|
146
|
+
* `RebaseClient`: `RebaseClient<unknown>` is not a supertype of
|
|
147
|
+
* `RebaseClient<Database>` — the dynamic branch of `RebaseSdkData` is an
|
|
148
|
+
* index signature that no concrete instantiation satisfies — so pinning it
|
|
149
|
+
* here rejected the client of every project that generates a `Database`
|
|
150
|
+
* type, which is the whole typed-SDK path.
|
|
143
151
|
*/
|
|
144
|
-
client?: RebaseClient
|
|
152
|
+
client?: RebaseClient<DB>;
|
|
145
153
|
/**
|
|
146
154
|
* The data sources of the app. Each entry pairs a
|
|
147
155
|
* {@link DataSourceDefinition} (key, engine, transport) with an optional
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { FindParams, FilterValues, LogicalCondition } from "@rebasepro/types";
|
|
2
|
+
/**
|
|
3
|
+
* The one place a collection read's parameters are assembled.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this exists
|
|
6
|
+
*
|
|
7
|
+
* Four call sites used to build this object by hand — `useCollection` and
|
|
8
|
+
* `useDataTableController`, each in both their `listen` and `find` branches.
|
|
9
|
+
* Every one of them listed the fields it forwarded, so adding an option to
|
|
10
|
+
* `FindParams` meant remembering four places, and forgetting one failed
|
|
11
|
+
* silently: the option simply never reached the server, and the feature it
|
|
12
|
+
* enabled looked broken for reasons nowhere near the code that dropped it.
|
|
13
|
+
*
|
|
14
|
+
* That is not hypothetical. `searchExplain` was added to three of the four and
|
|
15
|
+
* the miss was found by intercepting a WebSocket frame, because nothing else
|
|
16
|
+
* could see it.
|
|
17
|
+
*
|
|
18
|
+
* A field added here reaches every read. `toFindParams` returns `FindParams`
|
|
19
|
+
* itself, so the compiler checks the shape rather than a hand-copied list.
|
|
20
|
+
*/
|
|
21
|
+
export interface CollectionQueryInput<M extends Record<string, unknown>> {
|
|
22
|
+
where?: FilterValues<Extract<keyof M, string>>;
|
|
23
|
+
logical?: LogicalCondition;
|
|
24
|
+
/**
|
|
25
|
+
* Loosened from `FindParams["orderBy"]` on purpose: callers hold the sort
|
|
26
|
+
* as a plain `[string, direction]` read off user config or a URL, where the
|
|
27
|
+
* column is not statically known. Narrowing it here would push a cast onto
|
|
28
|
+
* every call site, which is the kind of friction that sends people back to
|
|
29
|
+
* hand-building the object this exists to replace.
|
|
30
|
+
*/
|
|
31
|
+
orderBy?: [string, "asc" | "desc"];
|
|
32
|
+
limit?: number;
|
|
33
|
+
offset?: number;
|
|
34
|
+
page?: number;
|
|
35
|
+
searchString?: string;
|
|
36
|
+
include?: string[];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Build the parameters for one collection read.
|
|
40
|
+
*
|
|
41
|
+
* Derived options live here rather than at the call sites, so they cannot
|
|
42
|
+
* disagree: `searchExplain` rides along exactly when there is a search string
|
|
43
|
+
* to explain, which is also the only time it costs anything.
|
|
44
|
+
*/
|
|
45
|
+
export declare function toFindParams<M extends Record<string, unknown>>(input: CollectionQueryInput<M>): FindParams<M>;
|
|
@@ -36,6 +36,17 @@ export interface UseRelationSelectorProps<M extends Record<string, any> = any> {
|
|
|
36
36
|
* Property name to use as the secondary display field
|
|
37
37
|
*/
|
|
38
38
|
descriptionProperty?: keyof M;
|
|
39
|
+
/**
|
|
40
|
+
* Whether the list should be fetched at all. Defaults to `true`.
|
|
41
|
+
*
|
|
42
|
+
* A picker that is mounted is not a picker that is open, and the two used
|
|
43
|
+
* to be the same thing here: the fetch ran on mount, so a collection table
|
|
44
|
+
* with a relation column paid for one query — or one realtime subscription
|
|
45
|
+
* — per rendered row before anyone clicked a cell. Pass `false` until the
|
|
46
|
+
* list is actually needed and nothing is requested; flipping it to `true`
|
|
47
|
+
* fetches once, and it never goes back.
|
|
48
|
+
*/
|
|
49
|
+
enabled?: boolean;
|
|
39
50
|
}
|
|
40
51
|
export interface RelationSelectorController {
|
|
41
52
|
items: RelationItem[];
|
|
@@ -49,4 +60,4 @@ export interface RelationSelectorController {
|
|
|
49
60
|
/**
|
|
50
61
|
* Hook to manage relation selection with data fetching from Rebase data source
|
|
51
62
|
*/
|
|
52
|
-
export declare function useRelationSelector<M extends Record<string, any> = any>({ path, collection, fixedFilter, pageSize, getLabelFromEntity, getDescriptionFromEntity, descriptionProperty }: UseRelationSelectorProps<M>): RelationSelectorController;
|
|
63
|
+
export declare function useRelationSelector<M extends Record<string, any> = any>({ path, collection, fixedFilter, pageSize, getLabelFromEntity, getDescriptionFromEntity, descriptionProperty, enabled }: UseRelationSelectorProps<M>): RelationSelectorController;
|