@rebasepro/app 0.23.0 → 0.24.1-canary.g56af4a4

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.
Files changed (47) hide show
  1. package/README.md +1 -1
  2. package/dist/auth/api.d.ts +2 -2
  3. package/dist/auth/types.d.ts +57 -4
  4. package/dist/collections/filter-operator-resolution.d.ts +18 -0
  5. package/dist/collections/navigation_utils.d.ts +8 -2
  6. package/dist/collections/property_presentation.d.ts +12 -15
  7. package/dist/components/ConnectionLostBanner.d.ts +31 -0
  8. package/dist/components/LoginView/email-link.d.ts +14 -5
  9. package/dist/components/LoginView/index.d.ts +2 -0
  10. package/dist/components/UserSelectPopover.d.ts +14 -1
  11. package/dist/components/common/useColumnsIds.d.ts +2 -2
  12. package/dist/components/common/useDataTableController.d.ts +15 -1
  13. package/dist/components/common/useScrollRestoration.d.ts +16 -7
  14. package/dist/components/email-change-message.d.ts +6 -0
  15. package/dist/components/index.d.ts +1 -0
  16. package/dist/contexts/CollectionResolverContext.d.ts +11 -1
  17. package/dist/contexts/StorageSourcesContext.d.ts +6 -0
  18. package/dist/de-BcBo287O.js +1434 -0
  19. package/dist/de-BcBo287O.js.map +1 -0
  20. package/dist/debug/ui-reference/ComponentSections.d.ts +16 -0
  21. package/dist/debug.js +909 -519
  22. package/dist/es-D-JXBvsW.js +1442 -0
  23. package/dist/es-D-JXBvsW.js.map +1 -0
  24. package/dist/fr-BKGBSaZM.js +1434 -0
  25. package/dist/fr-BKGBSaZM.js.map +1 -0
  26. package/dist/hi-D1jEYlQ1.js +1434 -0
  27. package/dist/hi-D1jEYlQ1.js.map +1 -0
  28. package/dist/hooks/data/collectionWindow.d.ts +266 -0
  29. package/dist/hooks/data/save.d.ts +11 -2
  30. package/dist/hooks/data/useBatchedFetch.d.ts +21 -0
  31. package/dist/hooks/data/useDataOrder.d.ts +6 -0
  32. package/dist/hooks/data/useFetch.d.ts +12 -4
  33. package/dist/hooks/index.d.ts +2 -1
  34. package/dist/index.d.ts +0 -1
  35. package/dist/index.es.js +2748 -6928
  36. package/dist/index.es.js.map +1 -1
  37. package/dist/it-DLXLsP1j.js +1434 -0
  38. package/dist/it-DLXLsP1j.js.map +1 -0
  39. package/dist/pt-Dd3JWvUZ.js +1439 -0
  40. package/dist/pt-Dd3JWvUZ.js.map +1 -0
  41. package/dist/util/index.d.ts +1 -0
  42. package/dist/util/previews.d.ts +22 -5
  43. package/dist/util/realtime.d.ts +18 -0
  44. package/dist/util/useStorageUploadController.d.ts +6 -2
  45. package/dist/vitePlugin.js +123 -12
  46. package/package.json +11 -7
  47. package/dist/hooks/useBackendStorageSource.d.ts +0 -35
package/README.md CHANGED
@@ -68,7 +68,7 @@ This package is **framework-agnostic** in the sense that it doesn't depend on an
68
68
  | `useDialogsController` | Programmatic dialog management |
69
69
  | `useSnackbarController` | Show snackbar notifications |
70
70
  | `useModeController` / `useBuildModeController` | Light/dark mode |
71
- | `useStorageSource` / `useBackendStorageSource` | Storage source from context |
71
+ | `useStorageSource` | Storage source from context |
72
72
  | `useRebaseClient` | Access the `RebaseClient` instance |
73
73
  | `useLargeLayout` | Responsive layout breakpoint hook |
74
74
  | `useClipboard` | Copy-to-clipboard |
@@ -8,7 +8,7 @@
8
8
  * here is the unauthenticated `/api/auth/config` probe used to detect
9
9
  * bootstrap mode and enabled providers before a session exists.
10
10
  */
11
- import type { AuthAdapterCapabilities } from "@rebasepro/types";
11
+ import type { AuthConfigDocument } from "@rebasepro/types";
12
12
  /**
13
13
  * What `GET /api/auth/config` answers.
14
14
  *
@@ -17,7 +17,7 @@ import type { AuthAdapterCapabilities } from "@rebasepro/types";
17
17
  * this one listed an `emailServiceEnabled` flag no backend sends, and marked as
18
18
  * optional fields every backend always returns.
19
19
  */
20
- export type AuthConfigResponse = AuthAdapterCapabilities;
20
+ export type AuthConfigResponse = AuthConfigDocument;
21
21
  /**
22
22
  * Cache container for `fetchAuthConfig` — holds both the inflight promise
23
23
  * (to deduplicate concurrent calls) and the cached result.
@@ -21,7 +21,14 @@ export type RebaseAuthController = AuthController & {
21
21
  /** Login with email and password */
22
22
  emailPasswordLogin: (email: string, password: string) => Promise<void>;
23
23
  /** Register a new user */
24
- register: (email: string, password: string, displayName?: string) => Promise<void>;
24
+ /**
25
+ * Create an account. `confirmationRequired` when the backend registers
26
+ * confirm-first (`requireEmailVerification`): nobody is signed in until
27
+ * the address is confirmed.
28
+ */
29
+ register: (email: string, password: string, displayName?: string) => Promise<{
30
+ confirmationRequired: boolean;
31
+ }>;
25
32
  /** Skip login (for anonymous access if enabled) */
26
33
  skipLogin: () => void;
27
34
  /** Whether login was skipped */
@@ -36,10 +43,33 @@ export type RebaseAuthController = AuthController & {
36
43
  forgotPassword: (email: string) => Promise<void>;
37
44
  /** Reset password using token from email */
38
45
  resetPassword: (token: string, password: string) => Promise<void>;
39
- /** Confirm an email address using the token from a verification email */
40
- verifyEmail: (token: string) => Promise<void>;
46
+ /**
47
+ * Confirm an email address using the token from a verification email.
48
+ * Refused with `PROOF_REQUIRED` when the account holds a password this
49
+ * call does not prove: pass `password` to keep it (and sign in), or
50
+ * `removeUnproven` to verify without it.
51
+ */
52
+ verifyEmail: (token: string, options?: {
53
+ password?: string;
54
+ removeUnproven?: boolean;
55
+ }) => Promise<{
56
+ passwordRemoved: boolean;
57
+ signedIn: boolean;
58
+ }>;
59
+ /** Sign in with a magic link's token. Absent when the client cannot. */
60
+ magicLinkLogin?: (token: string) => Promise<void>;
41
61
  /** Change password for authenticated user */
42
62
  changePassword: (oldPassword: string, newPassword: string) => Promise<void>;
63
+ /** Ask to move the signed-in account to another address. Absent when the client cannot. */
64
+ changeEmail?: (newEmail: string) => Promise<{
65
+ pendingEmail: string;
66
+ expiresAt: string;
67
+ }>;
68
+ /** Follow an address-change link. Absent when the client cannot. */
69
+ confirmEmailChange?: (token: string) => Promise<{
70
+ email: string;
71
+ removedProviders: string[];
72
+ }>;
43
73
  /** Update user profile */
44
74
  updateProfile: (displayName?: string, photoURL?: string) => Promise<User>;
45
75
  /** Fetch active sessions */
@@ -64,6 +94,8 @@ export type RebaseAuthController = AuthController & {
64
94
  * the client has no MFA support.
65
95
  */
66
96
  verifyMfaChallenge?: (mfaToken: string, challengeId: string, code: string) => Promise<void>;
97
+ /** The scopes the signed-in user holds; `undefined` until known. */
98
+ heldScopes?: string[];
67
99
  };
68
100
  /**
69
101
  * Structural type for the subset of `client.auth` (from `@rebasepro/client`)
@@ -91,8 +123,23 @@ export interface ClientAuth {
91
123
  resetPasswordForEmail(email: string): Promise<unknown>;
92
124
  resetPassword(token: string, password: string): Promise<unknown>;
93
125
  /** Optional so a hand-built auth client need not implement it. */
94
- verifyEmail?(token: string): Promise<unknown>;
126
+ verifyEmail?(token: string, options?: {
127
+ password?: string;
128
+ removeUnproven?: boolean;
129
+ }): Promise<unknown>;
95
130
  changePassword(oldPassword: string, newPassword: string): Promise<unknown>;
131
+ /** Optional so a hand-built auth client need not implement it. */
132
+ verifyMagicLink?(token: string): Promise<unknown>;
133
+ /** Optional so a hand-built auth client need not implement it. */
134
+ changeEmail?(newEmail: string): Promise<{
135
+ pendingEmail: string;
136
+ expiresAt: string;
137
+ }>;
138
+ /** Optional so a hand-built auth client need not implement it. */
139
+ confirmEmailChange?(token: string): Promise<{
140
+ email: string;
141
+ removedProviders: string[];
142
+ }>;
96
143
  updateUser(updates: {
97
144
  displayName?: string;
98
145
  photoURL?: string;
@@ -131,6 +178,12 @@ export interface RebaseAuthControllerProps {
131
178
  setAuthTokenGetter: (getter: () => Promise<string | null>) => void;
132
179
  };
133
180
  auth?: ClientAuth;
181
+ /** Read for the scopes the signed-in user holds (`GET /auth/scopes`). */
182
+ personalKeys?: {
183
+ listScopes(): Promise<{
184
+ held: string[];
185
+ }>;
186
+ };
134
187
  };
135
188
  /** Google OAuth client ID (optional, enables Google login) */
136
189
  googleClientId?: string;
@@ -55,3 +55,21 @@ export interface ResolveFilterOperatorsParams {
55
55
  * @group Models
56
56
  */
57
57
  export declare function resolveFilterOperators({ property, isArray, engine }: ResolveFilterOperatorsParams): WhereFilterOp[];
58
+ /**
59
+ * Whether a property can be filtered at all — the one answer the table header
60
+ * and the filters dialog both give.
61
+ *
62
+ * They used to answer it with a predicate each, and disagreed: the header
63
+ * allowed an array only of strings, numbers, dates, references and relations;
64
+ * the dialog allowed an array of anything, and listed an array of maps with an
65
+ * empty control. The header, for its part, ignored both the developer's
66
+ * `filterOperators: []` and a custom `admin.Filter`.
67
+ *
68
+ * A property with a custom filter field is filterable: the developer owns that
69
+ * field. Otherwise it is filterable when at least one operator survives
70
+ * {@link resolveFilterOperators} — for an array, on its item type. A tuple
71
+ * array (`of: [a, b]`) has no single item type to filter by.
72
+ *
73
+ * @group Models
74
+ */
75
+ export declare function isPropertyFilterable(property: Property, engine?: string): boolean;
@@ -13,8 +13,14 @@ export declare function resolveCollectionPathIds(path: string, allCollections: A
13
13
  */
14
14
  export declare function getCollectionBySlugWithin(slugOrPath: string, collections: AdminCollection[]): AdminCollection | undefined;
15
15
  /**
16
- * Get the subcollection combinations from a path:
17
- * "sites/es/locales" => ["sites/es/locales", "sites"]
16
+ * Every leading run of whole segments of a path, longest first: the
17
+ * candidates for the collection the path starts with.
18
+ * "sites/es/locales" => ["sites/es/locales", "sites/es", "sites"]
19
+ *
20
+ * Every length, not only the odd ones. A slug may contain slashes, so
21
+ * `content/podcasts/abc123/episodes` starts with the two-segment collection
22
+ * `content/podcasts`, and a path's own segment count says nothing about where
23
+ * its first collection ends.
18
24
  * @param subpaths
19
25
  */
20
26
  export declare function getCollectionPathsCombinations(subpaths: string[]): string[];
@@ -3,23 +3,20 @@
3
3
  *
4
4
  * These read a property's admin block — `readOnly`, `disabled.hidden`, the
5
5
  * declarative `conditions` — and lived in `@rebasepro/common`, which is on the
6
- * backend's dependency path. Nothing in core ever called them: `isReadOnly` and
7
- * `isHidden` are used only by the panel, and `applyPropertyConditions` has no
8
- * production caller at all.
6
+ * backend's dependency path. Nothing in core ever called them: `isReadOnly`,
7
+ * `isHidden` and `applyPropertyConditions` are the panel's alone.
9
8
  *
10
- * That last one is worth knowing about rather than assuming: the collection editor
11
- * has a whole Conditions UI, `serializable_utils` persists what it writes, and
12
- * `BaseProperty.conditions` documents itself as "evaluated at runtime like property
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.
9
+ * `applyPropertyConditions` is called by the record form, through
10
+ * `resolveFormProperty`, both where a field is laid out and where the form is
11
+ * judged on save — so a rule the collection editor writes hides, disables and
12
+ * requires what it says it does, and a field it hides is not held against the
13
+ * save. Until that was wired, the evaluator was reached only from its own tests.
15
14
  *
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.
15
+ * A condition may also be a literal: `hidden`/`readOnly`/`disabled` stated as a
16
+ * plain boolean rather than as a rule. A literal needs no context, so
17
+ * `isHidden`/`isReadOnly`/`isDisabled` answer it directly, and those three
18
+ * gates are consulted everywhere a field is laid out — including places with
19
+ * no entity to evaluate a rule against.
23
20
  */
24
21
  import type { ConditionContext, Property } from "@rebasepro/types";
25
22
  export declare function isReadOnly(property: Property): boolean;
@@ -0,0 +1,31 @@
1
+ import React from "react";
2
+ import type { RealtimeConnectionState } from "@rebasepro/types";
3
+ /**
4
+ * The realtime connection's state, re-rendering on every change.
5
+ *
6
+ * `undefined` when there is no client, or it was built without realtime —
7
+ * there is no connection to describe.
8
+ *
9
+ * @group Hooks and utilities
10
+ */
11
+ export declare function useRealtimeConnectionState(): RealtimeConnectionState | undefined;
12
+ export interface ConnectionLostBannerProps {
13
+ className?: string;
14
+ }
15
+ /**
16
+ * Says that live updates have stopped, while they have.
17
+ *
18
+ * Shown once the realtime client reports the connection `disconnected`: the
19
+ * socket has been down for longer than a blip, so tables, boards and open
20
+ * records are showing what they last received and nothing else will arrive
21
+ * until it is back. They keep their data rather than turning into errors — this
22
+ * banner is how the user learns it may be out of date. It disappears by itself
23
+ * when the connection returns, and every live view then refreshes.
24
+ *
25
+ * A brief drop (`reconnecting`) shows nothing: a deploy blips every socket for
26
+ * a second or two, and a banner that flashes on each one teaches people to
27
+ * ignore it.
28
+ *
29
+ * @group Components
30
+ */
31
+ export declare function ConnectionLostBanner({ className }: ConnectionLostBannerProps): React.JSX.Element | null;
@@ -3,9 +3,11 @@
3
3
  *
4
4
  * The server builds them from the configured frontend URL:
5
5
  * `<frontend>/reset-password?token=…` for a forgotten password, an admin's
6
- * "send reset email" and every invitation, and `<frontend>/verify-email?token=…`
7
- * to confirm an address. The frontend URL may carry a base path (`/admin`), so
8
- * the action is the *last* path segment, not the whole path.
6
+ * "send reset email" and every invitation, `<frontend>/verify-email?token=…`
7
+ * to confirm an address, `<frontend>/confirm-email-change?token=…` to move an
8
+ * account to a new one, and `<frontend>/auth/magic-link?token=…` to sign in.
9
+ * The frontend URL may carry a base path (`/admin`), so the action is read off
10
+ * the *end* of the path, not the whole path.
9
11
  */
10
12
  export type EmailLinkAction = {
11
13
  kind: "reset-password";
@@ -13,14 +15,21 @@ export type EmailLinkAction = {
13
15
  } | {
14
16
  kind: "verify-email";
15
17
  token: string;
18
+ } | {
19
+ kind: "confirm-email-change";
20
+ token: string;
21
+ } | {
22
+ kind: "magic-link";
23
+ token: string;
16
24
  };
17
25
  export declare function readEmailLinkAction(location: {
18
26
  pathname: string;
19
27
  search: string;
20
28
  }): EmailLinkAction | null;
21
29
  /**
22
- * Where the app lives: the link's own address without the action segment and
23
- * its token. `/admin/reset-password?token=…` becomes `/admin/`.
30
+ * Where the app lives: the link's own address without the action's path and
31
+ * its token. `/admin/reset-password?token=…` becomes `/admin/`, and
32
+ * `/admin/auth/magic-link?token=…` too.
24
33
  */
25
34
  export declare function appAddressOfEmailLink(location: {
26
35
  origin: string;
@@ -1,2 +1,4 @@
1
1
  export { LoginView } from "./LoginView.js";
2
2
  export type { LoginViewProps } from "./LoginView.js";
3
+ export { readEmailLinkAction } from "./email-link.js";
4
+ export type { EmailLinkAction } from "./email-link.js";
@@ -28,6 +28,19 @@ export interface UserSelectPopoverProps {
28
28
  * Whether users are currently loading.
29
29
  */
30
30
  loading?: boolean;
31
+ /**
32
+ * Called with the search text each time it changes (debounced), for a
33
+ * parent that searches its users on a server rather than passing them all
34
+ * in `users`. The list is still filtered here by the same text.
35
+ */
36
+ onSearchTextChange?: (searchText: string) => void;
37
+ /**
38
+ * For a parent that lists only the first page of a server search: how many
39
+ * users that search matched in all. Leave it out when `users` holds every
40
+ * match. When set, the header counts the listed users against it and a
41
+ * notice says the others are not listed.
42
+ */
43
+ totalCount?: number;
31
44
  /**
32
45
  * The current user (displayed as the "self" option at the top).
33
46
  */
@@ -60,4 +73,4 @@ export interface UserSelectPopoverProps {
60
73
  *
61
74
  * @group Components
62
75
  */
63
- export declare function UserSelectPopover({ selectedUser, onUserSelected, users, loading, currentUser, defaultLabel, renderLimit, className, size }: UserSelectPopoverProps): React.JSX.Element;
76
+ export declare function UserSelectPopover({ selectedUser, onUserSelected, users, loading, onSearchTextChange, totalCount, currentUser, defaultLabel, renderLimit, className, size }: UserSelectPopoverProps): React.JSX.Element;
@@ -12,8 +12,8 @@ export declare function getSubcollectionColumnId(collection: AdminCollection<any
12
12
  * Every child view gets a 200px button column that opens its tab. For a relation
13
13
  * declared in `relations` that button is the relation's only presence in the
14
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
15
+ * column is already there, hydrated by the list fetch's `include`
16
+ * (`getRelationIncludeParams`), showing the child rows themselves — and carrying the *same heading*, because the tab
17
17
  * takes its name from the declaring property. Two columns called "Applications",
18
18
  * one of them a button, and nothing in the header to tell them apart.
19
19
  *
@@ -2,6 +2,12 @@ import { Entity, FilterValues, User } from "@rebasepro/types";
2
2
  import { EntityTableController, AdminCollection } from "@rebasepro/cms-types";
3
3
  import { ScrollRestorationController } from "./useScrollRestoration.js";
4
4
  export declare const DEFAULT_PAGE_SIZE = 50;
5
+ /**
6
+ * Rows held with `pagination: false` past which development builds warn. The
7
+ * docs promise every row, so they are all loaded; but tens of thousands of
8
+ * them sit in memory and re-render on every change, and nothing said so.
9
+ */
10
+ export declare const UNPAGINATED_WARNING_ROWS = 10000;
5
11
  export type DataTableControllerProps<M extends Record<string, any> = any> = {
6
12
  /**
7
13
  * Full path where the data of this table is located
@@ -26,6 +32,14 @@ export type DataTableControllerProps<M extends Record<string, any> = any> = {
26
32
  * When set to true the filters and sort will be updated in the URL
27
33
  */
28
34
  updateUrl?: boolean;
35
+ /**
36
+ * Read rows at all. Defaults to `true`. `false` closes the subscription and
37
+ * reads nothing — for a view that keeps the controller for its search and
38
+ * filters but draws its rows some other way, like the board, whose columns
39
+ * each read their own. The rows already held stay until it is enabled
40
+ * again, which reads afresh.
41
+ */
42
+ enabled?: boolean;
29
43
  };
30
44
  /**
31
45
  * Use this hook to build a controller for the {@link DataCollectionTable}.
@@ -42,4 +56,4 @@ export type DataTableControllerProps<M extends Record<string, any> = any> = {
42
56
  * @param fixedFilterFromProps
43
57
  * @param updateUrl
44
58
  */
45
- export declare function useDataTableController<M extends Record<string, any> = any, USER extends User = User>({ path, collection, scrollRestoration, entitiesDisplayedFirst, lastDeleteTimestamp, fixedFilter: fixedFilterFromProps, updateUrl }: DataTableControllerProps<M>): EntityTableController<M>;
59
+ export declare function useDataTableController<M extends Record<string, any> = any, USER extends User = User>({ path, collection, scrollRestoration, entitiesDisplayedFirst, lastDeleteTimestamp, fixedFilter: fixedFilterFromProps, updateUrl, enabled }: DataTableControllerProps<M>): EntityTableController<M>;
@@ -1,4 +1,18 @@
1
1
  import { Entity, FilterValues } from "@rebasepro/types";
2
+ /**
3
+ * Where a collection view was, and the rows to show when it comes back.
4
+ *
5
+ * `data` is the rows around what was on screen — a page either side, when the
6
+ * view reports what it draws — starting at row `dataOffset` (0 when absent:
7
+ * `data` then starts at the first row). It used to be every row the view held,
8
+ * which a table scrolled to 100,000 rows kept for the rest of the session and
9
+ * read again in full on the way back.
10
+ */
11
+ export type CollectionScrollEntry = {
12
+ scrollOffset: number;
13
+ data: Entity<any>[];
14
+ dataOffset?: number;
15
+ };
2
16
  /**
3
17
  * Forget every collection's scroll position and the rows kept with it.
4
18
  *
@@ -7,15 +21,10 @@ import { Entity, FilterValues } from "@rebasepro/types";
7
21
  */
8
22
  export declare function clearCollectionScrollCache(): void;
9
23
  export type ScrollRestorationController = {
10
- getCollectionScroll: (path: string, filters?: FilterValues<any>) => {
11
- scrollOffset: number;
12
- data: Entity<any>[];
13
- } | undefined;
14
- updateCollectionScroll: (props: {
24
+ getCollectionScroll: (path: string, filters?: FilterValues<any>) => CollectionScrollEntry | undefined;
25
+ updateCollectionScroll: (props: CollectionScrollEntry & {
15
26
  path: string;
16
- scrollOffset: number;
17
27
  filters?: FilterValues<any>;
18
- data: Entity<any>[];
19
28
  }) => void;
20
29
  };
21
30
  export declare function useScrollRestoration(): ScrollRestorationController;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * What the settings screen and the link's screen say about a refused address
3
+ * change: the codes a person can act on get the panel's own, translated words,
4
+ * and anything else keeps the server's message.
5
+ */
6
+ export declare function emailChangeErrorMessage(error: unknown, t: (key: string) => string): string;
@@ -13,3 +13,4 @@ export * from "./UserDisplay.js";
13
13
  export * from "./LoginView/index.js";
14
14
  export * from "./RebaseAuth.js";
15
15
  export * from "./SchemaDriftBanner.js";
16
+ export * from "./ConnectionLostBanner.js";
@@ -5,6 +5,12 @@ import React from "react";
5
5
  export type CollectionResolver = (slug: string) => {
6
6
  properties?: Record<string, unknown>;
7
7
  } | undefined;
8
+ /**
9
+ * Translate the path a collection is addressed by into the path its driver
10
+ * stores it under — `fs_diagnosis` into `diagnosis`, for a Firestore collection
11
+ * that declares `path: "diagnosis"`. See `EntityDataOptions.resolveDataPath`.
12
+ */
13
+ export type DataPathResolver = (path: string) => string | undefined;
8
14
  /**
9
15
  * Lets the layer that owns the collections hand a resolver *up* to the data
10
16
  * layer, which needs a collection's primary keys to give its rows an address.
@@ -15,9 +21,13 @@ export type CollectionResolver = (slug: string) => {
15
21
  * prop without giving every headless BaaS app a collections argument it has no
16
22
  * use for.
17
23
  *
24
+ * The same layer knows where each collection is stored, so it hands up the
25
+ * data-path translation alongside: a direct driver has to be given a
26
+ * collection's declared `path`, and only the registry knows it.
27
+ *
18
28
  * Registration is a ref write, so it is safe to call during render — and it has
19
29
  * to be. The views that fetch rows are *below* the registrar, and child effects
20
30
  * run before parent effects, so registering in an effect would bind after the
21
31
  * first page of rows had already been converted.
22
32
  */
23
- export declare const CollectionResolverRegistrationContext: React.Context<(resolver: CollectionResolver | undefined) => void>;
33
+ export declare const CollectionResolverRegistrationContext: React.Context<(resolver: CollectionResolver | undefined, resolveDataPath?: DataPathResolver) => void>;
@@ -14,6 +14,12 @@ export interface StorageSourcesContextValue {
14
14
  registry: Record<string, StorageSourceDefinition>;
15
15
  /** Built {@link StorageSource} for direct sources, keyed by key. */
16
16
  sources: Record<string, StorageSource>;
17
+ /**
18
+ * A server-routed source for a key not (yet) in `sources` — requests carry
19
+ * `?storageId=<key>`, so the server serves it or refuses naming it, rather
20
+ * than the panel guessing the default. Absent without a client.
21
+ */
22
+ createSource?: (key: string) => StorageSource;
17
23
  }
18
24
  /** @internal */
19
25
  export declare const StorageSourcesContext: React.Context<StorageSourcesContextValue>;