@rebasepro/admin-types 0.10.1-canary.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/LICENSE +21 -0
- package/dist/admin_collection.d.ts +464 -0
- package/dist/augment.d.ts +66 -0
- package/dist/collections.d.ts +226 -0
- package/dist/controllers/analytics_controller.d.ts +7 -0
- package/dist/controllers/auth.d.ts +110 -0
- package/dist/controllers/customization_controller.d.ts +61 -0
- package/dist/controllers/dialogs_controller.d.ts +36 -0
- package/dist/controllers/index.d.ts +10 -0
- package/dist/controllers/local_config_persistence.d.ts +20 -0
- package/dist/controllers/navigation.d.ts +225 -0
- package/dist/controllers/registry.d.ts +80 -0
- package/dist/controllers/side_dialogs_controller.d.ts +67 -0
- package/dist/controllers/side_panel_controller.d.ts +97 -0
- package/dist/controllers/snackbar.d.ts +24 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.es.js +92 -0
- package/dist/index.es.js.map +1 -0
- package/dist/react_component_ref.d.ts +43 -0
- package/dist/rebase_context.d.ts +68 -0
- package/dist/types/breadcrumbs.d.ts +26 -0
- package/dist/types/builders.d.ts +15 -0
- package/dist/types/component_overrides.d.ts +196 -0
- package/dist/types/entity_actions.d.ts +105 -0
- package/dist/types/entity_link_builder.d.ts +7 -0
- package/dist/types/entity_views.d.ts +95 -0
- package/dist/types/export_import.d.ts +21 -0
- package/dist/types/formex.d.ts +40 -0
- package/dist/types/index.d.ts +15 -0
- package/dist/types/locales.d.ts +4 -0
- package/dist/types/modify_collections.d.ts +5 -0
- package/dist/types/plugins.d.ts +277 -0
- package/dist/types/property_config.d.ts +74 -0
- package/dist/types/property_options.d.ts +154 -0
- package/dist/types/slots.d.ts +263 -0
- package/dist/types/translations.d.ts +915 -0
- package/dist/types/user_management_delegate.d.ts +22 -0
- package/package.json +103 -0
- package/src/admin_collection.ts +582 -0
- package/src/augment.ts +60 -0
- package/src/collections.ts +256 -0
- package/src/controllers/analytics_controller.tsx +57 -0
- package/src/controllers/auth.ts +121 -0
- package/src/controllers/customization_controller.tsx +72 -0
- package/src/controllers/dialogs_controller.tsx +37 -0
- package/src/controllers/index.ts +10 -0
- package/src/controllers/local_config_persistence.tsx +22 -0
- package/src/controllers/navigation.ts +264 -0
- package/src/controllers/registry.ts +96 -0
- package/src/controllers/side_dialogs_controller.tsx +82 -0
- package/src/controllers/side_panel_controller.tsx +112 -0
- package/src/controllers/snackbar.ts +29 -0
- package/src/index.ts +20 -0
- package/src/react_component_ref.ts +52 -0
- package/src/rebase_context.ts +81 -0
- package/src/types/breadcrumbs.ts +27 -0
- package/src/types/builders.ts +18 -0
- package/src/types/component_overrides.ts +244 -0
- package/src/types/entity_actions.tsx +127 -0
- package/src/types/entity_link_builder.ts +8 -0
- package/src/types/entity_views.tsx +114 -0
- package/src/types/export_import.ts +26 -0
- package/src/types/formex.ts +45 -0
- package/src/types/index.ts +15 -0
- package/src/types/locales.ts +81 -0
- package/src/types/modify_collections.tsx +6 -0
- package/src/types/plugins.tsx +346 -0
- package/src/types/property_config.tsx +95 -0
- package/src/types/property_options.ts +169 -0
- package/src/types/slots.tsx +309 -0
- package/src/types/translations.ts +1026 -0
- package/src/types/user_management_delegate.ts +23 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { AdminCollection } from "@rebasepro/admin-types";
|
|
3
|
+
/**
|
|
4
|
+
* Controller that handles URL path building and resolution.
|
|
5
|
+
* @group Models
|
|
6
|
+
*/
|
|
7
|
+
export type UrlController = {
|
|
8
|
+
/**
|
|
9
|
+
* Default path under the navigation routes of the CMS will be created.
|
|
10
|
+
* Defaults to '/'. You may want to change this `basepath` to 'admin' for example.
|
|
11
|
+
*/
|
|
12
|
+
basePath: string;
|
|
13
|
+
/**
|
|
14
|
+
* Default path under the collection routes of the CMS will be created.
|
|
15
|
+
* It defaults to '/c'
|
|
16
|
+
*/
|
|
17
|
+
baseCollectionPath: string;
|
|
18
|
+
/**
|
|
19
|
+
* Convert a URL path to a collection or entity path
|
|
20
|
+
* `/c/products` => `products`
|
|
21
|
+
* `/my_cms/c/products/B34SAP8Z` => `products/B34SAP8Z`
|
|
22
|
+
* `/my_cms/my_view` => `my_view`
|
|
23
|
+
* @param cmsPath
|
|
24
|
+
*/
|
|
25
|
+
urlPathToDataPath: (cmsPath: string) => string;
|
|
26
|
+
/**
|
|
27
|
+
* Base url path for the home screen
|
|
28
|
+
*/
|
|
29
|
+
homeUrl: string;
|
|
30
|
+
/**
|
|
31
|
+
* Check if a url path belongs to a collection
|
|
32
|
+
* @param path
|
|
33
|
+
*/
|
|
34
|
+
isUrlCollectionPath: (urlPath: string) => boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Build a URL collection path from a data path
|
|
37
|
+
* `products` => `/c/products`
|
|
38
|
+
* `products/B34SAP8Z` => `/c/products/B34SAP8Z`
|
|
39
|
+
* @param path
|
|
40
|
+
*/
|
|
41
|
+
buildUrlCollectionPath: (path: string) => string;
|
|
42
|
+
/**
|
|
43
|
+
* Build a URL path for the CMS (e.g. for custom views)
|
|
44
|
+
* @param path
|
|
45
|
+
*/
|
|
46
|
+
buildAppUrlPath: (path: string) => string;
|
|
47
|
+
/**
|
|
48
|
+
* Turn a path with collection ids into a resolved path.
|
|
49
|
+
* The ids (typically used in urls) will be replaced with relative paths (typically used in database paths)
|
|
50
|
+
* @param path
|
|
51
|
+
*/
|
|
52
|
+
resolveDatabasePathsFrom: (path: string) => string;
|
|
53
|
+
/**
|
|
54
|
+
* A function to navigate to a specified route or URL.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} to - The target route or URL to navigate to.
|
|
57
|
+
* @param {NavigateOptions} [options] - Optional configuration settings for navigation, such as replace behavior or state data.
|
|
58
|
+
*/
|
|
59
|
+
navigate: (to: string, options?: NavigateOptions) => void;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Controller that manages the state of the navigation menu,
|
|
63
|
+
* including resolved views and top-level grouping.
|
|
64
|
+
* @group Models
|
|
65
|
+
*/
|
|
66
|
+
export type NavigationStateController = {
|
|
67
|
+
/**
|
|
68
|
+
* Custom additional views created by the developer, added to the main
|
|
69
|
+
* navigation
|
|
70
|
+
*/
|
|
71
|
+
views?: AppView[];
|
|
72
|
+
/**
|
|
73
|
+
* Custom additional views created by the developer, added to the admin
|
|
74
|
+
* navigation
|
|
75
|
+
*/
|
|
76
|
+
adminViews?: AppView[];
|
|
77
|
+
/**
|
|
78
|
+
* Configuration for the views that should be displayed at the top
|
|
79
|
+
* level of the navigation (e.g. in the home page or the navigation
|
|
80
|
+
* drawer)
|
|
81
|
+
*/
|
|
82
|
+
topLevelNavigation?: NavigationResult;
|
|
83
|
+
/**
|
|
84
|
+
* Is the navigation loading (the configuration persistence has not
|
|
85
|
+
* loaded yet, or a specified navigation builder has not completed)
|
|
86
|
+
*/
|
|
87
|
+
loading: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Was there an error while loading the navigation data
|
|
90
|
+
*/
|
|
91
|
+
navigationLoadingError?: Error;
|
|
92
|
+
/**
|
|
93
|
+
* Call this method to recalculate the navigation
|
|
94
|
+
*/
|
|
95
|
+
refreshNavigation: () => void;
|
|
96
|
+
};
|
|
97
|
+
export interface NavigateOptions {
|
|
98
|
+
replace?: boolean;
|
|
99
|
+
state?: unknown;
|
|
100
|
+
preventScrollReset?: boolean;
|
|
101
|
+
relative?: "route" | "path";
|
|
102
|
+
flushSync?: boolean;
|
|
103
|
+
viewTransition?: boolean;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Custom additional views created by the developer, added to the main
|
|
107
|
+
* navigation.
|
|
108
|
+
* @group Models
|
|
109
|
+
*/
|
|
110
|
+
export interface AppView {
|
|
111
|
+
/**
|
|
112
|
+
* CMS Path you can reach this view from.
|
|
113
|
+
*/
|
|
114
|
+
slug: string;
|
|
115
|
+
/**
|
|
116
|
+
* Name of this view
|
|
117
|
+
*/
|
|
118
|
+
name: string;
|
|
119
|
+
/**
|
|
120
|
+
* Optional description of this view. You can use Markdown
|
|
121
|
+
*/
|
|
122
|
+
description?: string;
|
|
123
|
+
/**
|
|
124
|
+
* Icon key to use in this view.
|
|
125
|
+
* You can use any of the icons in the Lucide specs:
|
|
126
|
+
* https://lucide.dev/icons/
|
|
127
|
+
* e.g. 'ShoppingCart' or 'User'
|
|
128
|
+
* Find all the icons in https://rebase.pro/docs/icons
|
|
129
|
+
*/
|
|
130
|
+
icon?: string | React.ReactNode;
|
|
131
|
+
/**
|
|
132
|
+
* Should this view be hidden from the main navigation panel.
|
|
133
|
+
* It will still be accessible if you reach the specified path
|
|
134
|
+
*/
|
|
135
|
+
hideFromNavigation?: boolean;
|
|
136
|
+
/**
|
|
137
|
+
* Navigation group for this view.
|
|
138
|
+
* Views sharing the same group name will be visually grouped
|
|
139
|
+
* together in the drawer and home page. If not set, the view
|
|
140
|
+
* falls into the default "Views" group.
|
|
141
|
+
*/
|
|
142
|
+
group?: string;
|
|
143
|
+
/**
|
|
144
|
+
* Component to be rendered. This can be any React component, and can use
|
|
145
|
+
* any of the provided hooks.
|
|
146
|
+
*
|
|
147
|
+
* Pass a `ComponentType` to enable lazy rendering — the component will
|
|
148
|
+
* only be instantiated when the route is visited. This is recommended
|
|
149
|
+
* for dynamic views generated from database data.
|
|
150
|
+
*/
|
|
151
|
+
view: React.ReactNode | React.ComponentType;
|
|
152
|
+
/**
|
|
153
|
+
* If true, a wildcard route (slug/*) is automatically registered
|
|
154
|
+
* alongside the base route, enabling nested navigation within this view.
|
|
155
|
+
*/
|
|
156
|
+
nestedRoutes?: boolean;
|
|
157
|
+
/**
|
|
158
|
+
* Restrict this view to users with at least one of the listed roles.
|
|
159
|
+
* When omitted or empty, the view is visible to all authenticated users.
|
|
160
|
+
* Applied during view resolution — the view is filtered out entirely
|
|
161
|
+
* (not just hidden from nav) if the user lacks a matching role.
|
|
162
|
+
*/
|
|
163
|
+
roles?: string[];
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* A composable section that can be rendered on the home page.
|
|
167
|
+
* Use this to add custom content alongside the auto-generated
|
|
168
|
+
* navigation groups.
|
|
169
|
+
* @group Models
|
|
170
|
+
*/
|
|
171
|
+
export interface HomePageSection {
|
|
172
|
+
/**
|
|
173
|
+
* Unique key for this section.
|
|
174
|
+
*/
|
|
175
|
+
key: string;
|
|
176
|
+
/**
|
|
177
|
+
* Title displayed as the section header.
|
|
178
|
+
*/
|
|
179
|
+
title: string;
|
|
180
|
+
/**
|
|
181
|
+
* Arbitrary React content rendered inside the section.
|
|
182
|
+
*/
|
|
183
|
+
children: React.ReactNode;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Used to group navigation entries in the main navigation.
|
|
187
|
+
*/
|
|
188
|
+
export interface NavigationGroupMapping {
|
|
189
|
+
/**
|
|
190
|
+
* Name of the group, used to display the group header in the UI
|
|
191
|
+
*/
|
|
192
|
+
name: string;
|
|
193
|
+
/**
|
|
194
|
+
* List of collection ids or view paths that belong to this group.
|
|
195
|
+
*/
|
|
196
|
+
entries: string[];
|
|
197
|
+
/**
|
|
198
|
+
* Configure which groups start collapsed.
|
|
199
|
+
* Set to `true` to collapse in both drawer and home page,
|
|
200
|
+
* or use an object to control each independently.
|
|
201
|
+
*
|
|
202
|
+
* @defaultValue false (expanded)
|
|
203
|
+
*/
|
|
204
|
+
collapsedByDefault?: boolean | {
|
|
205
|
+
drawer?: boolean;
|
|
206
|
+
home?: boolean;
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
export interface NavigationEntry {
|
|
210
|
+
id: string;
|
|
211
|
+
url: string;
|
|
212
|
+
name: string;
|
|
213
|
+
slug: string;
|
|
214
|
+
type: "collection" | "view" | "admin";
|
|
215
|
+
collection?: AdminCollection;
|
|
216
|
+
view?: AppView;
|
|
217
|
+
description?: string;
|
|
218
|
+
group: string;
|
|
219
|
+
}
|
|
220
|
+
export type NavigationResult = {
|
|
221
|
+
allowDragAndDrop: boolean;
|
|
222
|
+
navigationEntries: NavigationEntry[];
|
|
223
|
+
groups: string[];
|
|
224
|
+
onNavigationEntriesUpdate: (entries: NavigationGroupMapping[]) => void;
|
|
225
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import type { CollectionConfigsBuilder, AppViewsBuilder } from "../types/builders";
|
|
3
|
+
import type { EntityCustomView } from "../types/entity_views";
|
|
4
|
+
import type { EntityAction } from "../types/entity_actions";
|
|
5
|
+
import type { AppView, NavigationGroupMapping } from "./navigation";
|
|
6
|
+
import type { AdminCollection } from "@rebasepro/admin-types";
|
|
7
|
+
/**
|
|
8
|
+
* Options to enable the built-in collection editor.
|
|
9
|
+
* When provided to `<RebaseAdmin>`, the editor is auto-wired as a native feature.
|
|
10
|
+
*/
|
|
11
|
+
export interface CollectionEditorOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Function that returns an auth token for schema-editor API calls.
|
|
14
|
+
* Falls back to `authController.getAuthToken` when omitted.
|
|
15
|
+
*/
|
|
16
|
+
getAuthToken?: () => Promise<string | null>;
|
|
17
|
+
/** Mark the editor as read-only (disable mutations). */
|
|
18
|
+
readOnly?: boolean;
|
|
19
|
+
/** Suggested base paths shown when creating new collections. */
|
|
20
|
+
pathSuggestions?: string[];
|
|
21
|
+
}
|
|
22
|
+
export interface RebaseAdminConfig<EC extends AdminCollection = AdminCollection> {
|
|
23
|
+
collections?: EC[] | CollectionConfigsBuilder<EC>;
|
|
24
|
+
/**
|
|
25
|
+
* Custom top-level views added to the main navigation.
|
|
26
|
+
* Accepts a static array of views or an async builder function
|
|
27
|
+
* that receives the current user/auth context for role-based views.
|
|
28
|
+
*/
|
|
29
|
+
views?: AppView[] | AppViewsBuilder;
|
|
30
|
+
homePage?: ReactNode;
|
|
31
|
+
entityViews?: EntityCustomView[];
|
|
32
|
+
entityActions?: EntityAction[];
|
|
33
|
+
/**
|
|
34
|
+
* Centralized configuration for how collections and views are grouped
|
|
35
|
+
* in the navigation sidebar and home page.
|
|
36
|
+
* Each mapping defines a named group and the collection/view slugs
|
|
37
|
+
* that belong to it. The array order determines group display order.
|
|
38
|
+
* Entry order within each group determines card order.
|
|
39
|
+
*/
|
|
40
|
+
navigationGroupMappings?: NavigationGroupMapping[];
|
|
41
|
+
/**
|
|
42
|
+
* Enable the built-in visual collection/schema editor.
|
|
43
|
+
* Pass `true` for zero-config, or an options object for fine-grained control.
|
|
44
|
+
* When enabled, the editor slots, provider, and Studio schema view
|
|
45
|
+
* are all auto-wired — no plugin or manual view injection needed.
|
|
46
|
+
*/
|
|
47
|
+
collectionEditor?: boolean | CollectionEditorOptions;
|
|
48
|
+
/**
|
|
49
|
+
* URL path prefix the admin is mounted under, when it does not live at the
|
|
50
|
+
* site root. Set this when the admin is rendered inside a path-prefixed
|
|
51
|
+
* route (e.g. `<Route path="/admin/*">`) so URL⇄collection resolution
|
|
52
|
+
* accounts for the prefix — otherwise the current path won't be recognized
|
|
53
|
+
* as a collection path and views hang on a spinner with no data fetch.
|
|
54
|
+
*
|
|
55
|
+
* Do NOT set this when mounting via a react-router `basename` — react-router
|
|
56
|
+
* already strips the prefix from the location, so the default (`/`) is correct.
|
|
57
|
+
*
|
|
58
|
+
* @default "/"
|
|
59
|
+
*/
|
|
60
|
+
basePath?: string;
|
|
61
|
+
}
|
|
62
|
+
export interface RebaseStudioConfig {
|
|
63
|
+
tools?: ("sql" | "js" | "rls" | "schema" | "storage" | "cron" | "schema-visualizer" | "branches" | "backups" | "api" | "logs" | "api-keys")[];
|
|
64
|
+
homePage?: ReactNode;
|
|
65
|
+
devViews?: AppView[];
|
|
66
|
+
}
|
|
67
|
+
export interface RebaseAuthConfig {
|
|
68
|
+
loginView?: ReactNode;
|
|
69
|
+
}
|
|
70
|
+
export interface RebaseRegistryController {
|
|
71
|
+
cmsConfig: RebaseAdminConfig | null;
|
|
72
|
+
studioConfig: RebaseStudioConfig | null;
|
|
73
|
+
authConfig: RebaseAuthConfig | null;
|
|
74
|
+
registerCMS: (config: RebaseAdminConfig) => void;
|
|
75
|
+
unregisterCMS: () => void;
|
|
76
|
+
registerStudio: (config: RebaseStudioConfig) => void;
|
|
77
|
+
unregisterStudio: () => void;
|
|
78
|
+
registerAuth: (config: RebaseAuthConfig) => void;
|
|
79
|
+
unregisterAuth: () => void;
|
|
80
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Controller to open the side dialog
|
|
4
|
+
* @group Hooks and utilities
|
|
5
|
+
*/
|
|
6
|
+
export interface SideDialogsController {
|
|
7
|
+
/**
|
|
8
|
+
* Close the last panel
|
|
9
|
+
*/
|
|
10
|
+
close: () => void;
|
|
11
|
+
/**
|
|
12
|
+
* List of side panels currently open
|
|
13
|
+
*/
|
|
14
|
+
sidePanels: SideDialogPanelProps[];
|
|
15
|
+
/**
|
|
16
|
+
* Override the current side panels
|
|
17
|
+
* @param panels
|
|
18
|
+
*/
|
|
19
|
+
setSidePanels: (panels: SideDialogPanelProps[]) => void;
|
|
20
|
+
/**
|
|
21
|
+
* Open one or multiple side panels
|
|
22
|
+
* @param props
|
|
23
|
+
*/
|
|
24
|
+
open: (panelProps: SideDialogPanelProps | SideDialogPanelProps[]) => void;
|
|
25
|
+
/**
|
|
26
|
+
* Replace the last open panel with the given one
|
|
27
|
+
* @param props
|
|
28
|
+
*/
|
|
29
|
+
replace: (panelProps: SideDialogPanelProps | SideDialogPanelProps[]) => void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Props used to open a side dialog
|
|
33
|
+
* @group Hooks and utilities
|
|
34
|
+
*/
|
|
35
|
+
export interface SideDialogPanelProps {
|
|
36
|
+
/**
|
|
37
|
+
* A key that identifies this panel
|
|
38
|
+
*/
|
|
39
|
+
key: string;
|
|
40
|
+
/**
|
|
41
|
+
* The component type that will be rendered
|
|
42
|
+
*/
|
|
43
|
+
component: React.ReactNode;
|
|
44
|
+
/**
|
|
45
|
+
* Optional width of the panel
|
|
46
|
+
*/
|
|
47
|
+
width?: string;
|
|
48
|
+
/**
|
|
49
|
+
* When open, change the URL to this path.
|
|
50
|
+
* Note that if you want to restore state from a URL you need to add the
|
|
51
|
+
* logic yourself by listening to URL updates, and probably call `open`.
|
|
52
|
+
*/
|
|
53
|
+
urlPath?: string;
|
|
54
|
+
/**
|
|
55
|
+
* If the navigation stack is empty (you landed in the `urlPath` url), what
|
|
56
|
+
* url path to change to when the panel gets closed.
|
|
57
|
+
*/
|
|
58
|
+
parentUrlPath?: string;
|
|
59
|
+
/**
|
|
60
|
+
* Callback when the panel is closed
|
|
61
|
+
*/
|
|
62
|
+
onClose?: () => void;
|
|
63
|
+
/**
|
|
64
|
+
* Use this prop to store additional data in the panel
|
|
65
|
+
*/
|
|
66
|
+
additional?: unknown;
|
|
67
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { Entity } from "@rebasepro/types";
|
|
2
|
+
import type { AdminCollection } from "@rebasepro/admin-types";
|
|
3
|
+
/**
|
|
4
|
+
* Props used to open a side dialog
|
|
5
|
+
* @group Hooks and utilities
|
|
6
|
+
*/
|
|
7
|
+
export interface SidePanelBindingProps<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
8
|
+
/**
|
|
9
|
+
* Absolute path of the entity
|
|
10
|
+
*/
|
|
11
|
+
path: string;
|
|
12
|
+
/**
|
|
13
|
+
* ID of the entity, if not set, it means we are creating a new entity
|
|
14
|
+
*/
|
|
15
|
+
entityId?: string | number;
|
|
16
|
+
/**
|
|
17
|
+
* Set this flag to true if you want to make a copy of an existing entity
|
|
18
|
+
*/
|
|
19
|
+
copy?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Open the entity with a selected sub-collection view. If the panel for this
|
|
22
|
+
* entity was already open, it is replaced.
|
|
23
|
+
*/
|
|
24
|
+
selectedTab?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Use this prop to override the width of the form view.
|
|
27
|
+
* e.g. "600px"
|
|
28
|
+
*/
|
|
29
|
+
width?: number | string;
|
|
30
|
+
/**
|
|
31
|
+
* Collection representing the entities of this view.
|
|
32
|
+
* If you leave it blank it will be induced by your navigation
|
|
33
|
+
*/
|
|
34
|
+
collection?: AdminCollection<M>;
|
|
35
|
+
/**
|
|
36
|
+
* Should update the URL when opening the dialog.
|
|
37
|
+
* Consider that if the collection that you provide is not defined in the base
|
|
38
|
+
* config of your `Rebase` component, you will not be able to recreate
|
|
39
|
+
* the state if copying the URL to a different window.
|
|
40
|
+
*/
|
|
41
|
+
updateUrl?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Callback when the entity is updated
|
|
44
|
+
* @param params
|
|
45
|
+
*/
|
|
46
|
+
onUpdate?: (params: {
|
|
47
|
+
entity: Entity<M>;
|
|
48
|
+
}) => void;
|
|
49
|
+
/**
|
|
50
|
+
* Callback when the dialog is closed
|
|
51
|
+
*/
|
|
52
|
+
onClose?: () => void;
|
|
53
|
+
/**
|
|
54
|
+
* Should this panel close when saving
|
|
55
|
+
*/
|
|
56
|
+
closeOnSave?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Override some form properties
|
|
59
|
+
*/
|
|
60
|
+
formProps?: Record<string, unknown>;
|
|
61
|
+
/**
|
|
62
|
+
* Allow the user to open the entity fullscreen
|
|
63
|
+
*/
|
|
64
|
+
allowFullScreen?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Pre-populate the form with these values when creating a new entity.
|
|
67
|
+
* Only applied when `entityId` is not set (i.e. the form is in "new" mode).
|
|
68
|
+
* Useful for actions that fetch data from an external source (e.g. a URL)
|
|
69
|
+
* and want to pre-fill the document before the user saves.
|
|
70
|
+
*/
|
|
71
|
+
defaultValues?: Partial<M>;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Controller to open the side dialog displaying entity forms
|
|
75
|
+
* @group Hooks and utilities
|
|
76
|
+
*/
|
|
77
|
+
export interface SidePanelController {
|
|
78
|
+
/**
|
|
79
|
+
* Close the last panel
|
|
80
|
+
*/
|
|
81
|
+
close: () => void;
|
|
82
|
+
/**
|
|
83
|
+
* Open a new entity sideDialog. By default, the collection and configuration
|
|
84
|
+
* of the view is fetched from the collections you have specified in the
|
|
85
|
+
* navigation.
|
|
86
|
+
* At least you need to pass the path of the entity you would like
|
|
87
|
+
* to edit. You can set a entityId if you would like to edit and existing one
|
|
88
|
+
* (or a new one with that id).
|
|
89
|
+
* @param props
|
|
90
|
+
*/
|
|
91
|
+
open: <M extends Record<string, unknown> = Record<string, unknown>>(props: SidePanelBindingProps<M>) => void;
|
|
92
|
+
/**
|
|
93
|
+
* Replace the last open entity panel with the given one.
|
|
94
|
+
* @param props
|
|
95
|
+
*/
|
|
96
|
+
replace: <M extends Record<string, unknown> = Record<string, unknown>>(props: SidePanelBindingProps<M>) => void;
|
|
97
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Possible snackbar types
|
|
3
|
+
* @group Hooks and utilities
|
|
4
|
+
*/
|
|
5
|
+
export type SnackbarMessageType = "success" | "info" | "warning" | "error";
|
|
6
|
+
/**
|
|
7
|
+
* Controller to display snackbars
|
|
8
|
+
* @group Hooks and utilities
|
|
9
|
+
*/
|
|
10
|
+
export interface SnackbarController {
|
|
11
|
+
/**
|
|
12
|
+
* Close the currently open snackbar
|
|
13
|
+
*/
|
|
14
|
+
close: () => void;
|
|
15
|
+
/**
|
|
16
|
+
* Display a new snackbar. You need to specify the type and message.
|
|
17
|
+
* You can optionally specify a title
|
|
18
|
+
*/
|
|
19
|
+
open: (props: {
|
|
20
|
+
type: SnackbarMessageType;
|
|
21
|
+
message: React.ReactNode;
|
|
22
|
+
autoHideDuration?: number;
|
|
23
|
+
}) => void;
|
|
24
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rebasepro/admin-types — the React-flavoured half of the Rebase type surface.
|
|
3
|
+
*
|
|
4
|
+
* Everything here describes the admin panel: what a collection looks like on
|
|
5
|
+
* screen, the controllers the panel runs on, the props a custom field or view
|
|
6
|
+
* receives. It depends on @rebasepro/types and nothing in @rebasepro/types
|
|
7
|
+
* depends on it, which is what lets a BaaS install exist without React.
|
|
8
|
+
*
|
|
9
|
+
* If you are building a backend, you want @rebasepro/types.
|
|
10
|
+
*/
|
|
11
|
+
import "./augment";
|
|
12
|
+
export * from "./types/property_options";
|
|
13
|
+
export * from "./react_component_ref";
|
|
14
|
+
export * from "./collections";
|
|
15
|
+
export * from "./admin_collection";
|
|
16
|
+
export * from "./rebase_context";
|
|
17
|
+
export * from "./types";
|
|
18
|
+
export * from "./controllers";
|
package/dist/index.es.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { ADMIN_COLLECTION_KEYS as ADMIN_COLLECTION_KEYS$1 } from "@rebasepro/types";
|
|
2
|
+
//#region src/react_component_ref.ts
|
|
3
|
+
/**
|
|
4
|
+
* The `ComponentLike` contract, as a signature the compiler has to keep true.
|
|
5
|
+
*
|
|
6
|
+
* The split rests on one claim: **every form a React component takes is
|
|
7
|
+
* assignable to `ComponentLike`** — function components, class components,
|
|
8
|
+
* `memo`, `forwardRef`. If that stopped holding, core's `ComponentRef` would
|
|
9
|
+
* quietly begin rejecting real components, and the failure would surface far away
|
|
10
|
+
* in whichever collection file happened to use the broken form.
|
|
11
|
+
*
|
|
12
|
+
* So the claim is not left to a test that someone has to run. This function's
|
|
13
|
+
* parameter and return types state it, and `pnpm typecheck` enforces it on every
|
|
14
|
+
* commit. It is also useful on its own: an explicit widening at the point where
|
|
15
|
+
* an authored component enters a collection config.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* import { MyField } from "./MyField";
|
|
19
|
+
* admin: { Field: asComponentRef(MyField) }
|
|
20
|
+
*/
|
|
21
|
+
function asComponentRef(component) {
|
|
22
|
+
return component;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The same contract in the other direction: a `ComponentLike` is only renderable
|
|
26
|
+
* once narrowed, and this is the single sanctioned place that narrowing is
|
|
27
|
+
* spelled out. `resolveComponentRef` in `@rebasepro/app` does the runtime half.
|
|
28
|
+
*/
|
|
29
|
+
function asReactComponent(component) {
|
|
30
|
+
return component;
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/admin_collection.ts
|
|
34
|
+
/** Identity at runtime; the overloads above are the whole point. @group Builder */
|
|
35
|
+
function defineCollection(collection) {
|
|
36
|
+
return collection;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Core's list, re-exported through a `satisfies` clause that is the agreement
|
|
40
|
+
* check: a key core names that is not an option here fails to compile, and
|
|
41
|
+
* `satisfies` keeps the literal tuple type rather than widening it to `string[]`.
|
|
42
|
+
*
|
|
43
|
+
* The reverse direction — an option missing from core's list — has no type-level
|
|
44
|
+
* expression, since there is no exhaustiveness check over an optional-property
|
|
45
|
+
* keyof. `test/admin_collection.test.ts` counts them instead.
|
|
46
|
+
*/
|
|
47
|
+
var ADMIN_COLLECTION_KEYS = ADMIN_COLLECTION_KEYS$1;
|
|
48
|
+
/**
|
|
49
|
+
* Flatten a collection's `admin` block onto it, producing the panel's view model.
|
|
50
|
+
*
|
|
51
|
+
* Shallow by design: the block's fields are independent, so a deep merge would
|
|
52
|
+
* only create opportunities for a nested object to be half from one source and
|
|
53
|
+
* half from the other. `admin` survives on the result.
|
|
54
|
+
*
|
|
55
|
+
* Idempotent — flattening an already-flat collection returns an equivalent one —
|
|
56
|
+
* because the panel resolves collections at more than one entry point (the
|
|
57
|
+
* registry, `<Rebase collections>`, a plugin's `modifyCollection`) and they must
|
|
58
|
+
* not fight over which has run.
|
|
59
|
+
*/
|
|
60
|
+
function resolveAdminCollection(collection) {
|
|
61
|
+
const block = collection.admin;
|
|
62
|
+
if (!block) return collection;
|
|
63
|
+
return {
|
|
64
|
+
...collection,
|
|
65
|
+
...block,
|
|
66
|
+
admin: block
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The inverse: lift flattened admin fields back into the block.
|
|
71
|
+
*
|
|
72
|
+
* Used on the way out — persisting from the collection editor, or handing a
|
|
73
|
+
* collection to anything that expects the authoring shape. Any key in
|
|
74
|
+
* {@link ADMIN_COLLECTION_KEYS} found at the top level is moved down, so a
|
|
75
|
+
* round trip through the panel does not leave the file flat.
|
|
76
|
+
*/
|
|
77
|
+
function toAdminCollectionConfig(collection) {
|
|
78
|
+
const source = collection;
|
|
79
|
+
const top = {};
|
|
80
|
+
const block = { ...source.admin ?? {} };
|
|
81
|
+
for (const [key, value] of Object.entries(source)) {
|
|
82
|
+
if (key === "admin") continue;
|
|
83
|
+
if (ADMIN_COLLECTION_KEYS.includes(key)) block[key] = value;
|
|
84
|
+
else top[key] = value;
|
|
85
|
+
}
|
|
86
|
+
if (Object.keys(block).length > 0) top.admin = block;
|
|
87
|
+
return top;
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
export { ADMIN_COLLECTION_KEYS, asComponentRef, asReactComponent, defineCollection, resolveAdminCollection, toAdminCollectionConfig };
|
|
91
|
+
|
|
92
|
+
//# sourceMappingURL=index.es.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.es.js","names":[],"sources":["../src/react_component_ref.ts","../src/admin_collection.ts"],"sourcesContent":["import type React from \"react\";\nimport type { ComponentLike, ComponentRef, LazyComponentRef } from \"@rebasepro/types\";\n\n/**\n * `ComponentRef`, narrowed to real React types.\n *\n * Core's {@link ComponentRef} describes a component structurally\n * ({@link ComponentLike}) so that `properties.ts` — and therefore the whole\n * property model the backend reads — can live without React. The trade is that\n * the return type is `unknown`, so a function returning something React cannot\n * render type-checks there.\n *\n * Use this type wherever React genuinely exists: authoring a collection's admin\n * options, and inside the admin packages. Assignments flow into core unchanged,\n * because every member of this union is a member of that one.\n */\nexport type ReactComponentRef<P = any> =\n | string\n | LazyComponentRef<P>\n | (() => Promise<{ default: React.ComponentType<P> }>)\n | React.ComponentType<P>;\n\n/**\n * The `ComponentLike` contract, as a signature the compiler has to keep true.\n *\n * The split rests on one claim: **every form a React component takes is\n * assignable to `ComponentLike`** — function components, class components,\n * `memo`, `forwardRef`. If that stopped holding, core's `ComponentRef` would\n * quietly begin rejecting real components, and the failure would surface far away\n * in whichever collection file happened to use the broken form.\n *\n * So the claim is not left to a test that someone has to run. This function's\n * parameter and return types state it, and `pnpm typecheck` enforces it on every\n * commit. It is also useful on its own: an explicit widening at the point where\n * an authored component enters a collection config.\n *\n * @example\n * import { MyField } from \"./MyField\";\n * admin: { Field: asComponentRef(MyField) }\n */\nexport function asComponentRef<P>(component: React.ComponentType<P>): ComponentRef<P> {\n return component;\n}\n\n/**\n * The same contract in the other direction: a `ComponentLike` is only renderable\n * once narrowed, and this is the single sanctioned place that narrowing is\n * spelled out. `resolveComponentRef` in `@rebasepro/app` does the runtime half.\n */\nexport function asReactComponent<P>(component: ComponentLike<P>): React.ComponentType<P> {\n return component as React.ComponentType<P>;\n}\n","/**\n * The typed admin block, and the type you author a collection against.\n *\n * A collection is one file. Schema, security rules and callbacks sit at the top\n * level, where the backend reads them; everything the admin panel renders sits\n * under `admin`. `@rebasepro/types` does not declare that field at all — naming a\n * kanban column definition would drag `React.ReactNode` back into the BaaS\n * contract, and a server has no use for one. `augment.ts` in this package declares\n * it, by declaration merging, onto core's `CollectionConfig`. So this is the other\n * side of that boundary: the 38 fields, fully typed, in the package where React\n * exists, and reachable only by a program that has opted in.\n *\n * Each field is declared exactly once, here. Core does not carry a React-free\n * skeleton of the same shape; two definitions that agree only by luck is the\n * `WhereFilterOp` mistake, and this block is far bigger than one union.\n */\nimport type React from \"react\";\nimport type {\n CollectionConfig,\n ComponentRef,\n FilterPreset,\n FilterValues,\n FirebaseCollectionConfig,\n FirebaseProperties,\n InferEntityType,\n MongoDBCollectionConfig,\n MongoProperties,\n OrderByTuple,\n PostgresCollectionConfig,\n PostgresProperties,\n User\n} from \"@rebasepro/types\";\n// A value, not a type: the runtime list core owns.\nimport { ADMIN_COLLECTION_KEYS as CORE_ADMIN_COLLECTION_KEYS } from \"@rebasepro/types\";\n\nimport type {\n AdditionalFieldDelegate,\n CollectionActionsProps,\n CollectionSize,\n DefaultSelectedViewBuilder,\n KanbanConfig,\n SelectionController,\n ViewMode\n} from \"./collections\";\nimport type { EntityCustomView, FormViewConfig } from \"./types/entity_views\";\nimport type { EntityAction } from \"./types/entity_actions\";\nimport type { ExportConfig } from \"./types/export_import\";\nimport type { CollectionComponentOverrideMap } from \"./types/component_overrides\";\n\n/**\n * Admin-panel presentation and behaviour for a collection.\n *\n * A `type` rather than an `interface`, and that is load-bearing: TypeScript gives\n * an implicit index signature to an object *type alias* but not to an interface.\n * `toAdminCollectionConfig` has to widen a collection carrying this block to\n * `Record<string, unknown>` in order to move the flattened keys back under\n * `admin`, and as an interface that conversion is an error (TS2352, \"index\n * signature for type 'string' is missing\"). Flipping it and running\n * `pnpm typecheck` reproduces that in one line.\n *\n * Declaration merging is not wanted here anyway; a plugin adding fields to the\n * block would have nothing reading them.\n *\n * @group Models\n */\nexport type AdminCollectionOptions<\n M extends Record<string, unknown> = Record<string, unknown>,\n USER extends User = User\n> = {\n /**\n * Icon for the navigation sidebar or cards.\n *\n * Either a Lucide icon name (`\"FileText\"`, `\"ShoppingCart\"`) or a rendered\n * element. Prefer the name: it survives serialization, so the collection file\n * stays loadable by the backend and by `rebase generate-sdk`, and it is what\n * the schema editor writes back.\n */\n icon?: string | React.ReactNode;\n\n /**\n * Navigation group for this collection.\n * Collections sharing the same group name will be visually grouped\n * together in the drawer and home page. If not set, the collection\n * falls into the default \"Views\" group.\n */\n group?: string;\n\n /**\n * Array of entity views that this collection has.\n * Can be an array of `EntityCustomView` or a string representing the key of a global `EntityCustomView`.\n */\n entityViews?: (string | EntityCustomView<Record<string, unknown>>)[];\n\n /**\n * Default preview properties displayed when this collection is referenced to.\n */\n previewProperties?: string[];\n\n /**\n * Properties to display as columns in the list view.\n * If not specified, the list view uses a smart default (Title, Status, Date).\n */\n listProperties?: string[];\n\n /**\n * Title property of the entity. This is the property that will be used\n * as the title in entity related views and references.\n * If not specified, the first property simple text property will be used.\n */\n readonly titleProperty?: Extract<keyof M, string> | (string & {});\n\n /**\n * When editing a entity, you can choose to open the entity in a side dialog\n * or in a full screen dialog. Defaults to `full_screen`.\n */\n openEntityMode?: \"side_panel\" | \"full_screen\" | \"split\" | \"dialog\";\n\n /**\n * Controls what happens when a user clicks on a entity in the collection view.\n * - `\"edit\"` (default): Opens the entity in the edit form.\n * - `\"view\"`: Opens a read-only detail view with an \"Edit\" button.\n */\n defaultEntityAction?: \"view\" | \"edit\";\n\n /**\n * Replace the default entity form with a custom component.\n * The Builder receives the same props as entity view tabs\n * (entity, formContext, collection, etc.) and has full control over the UI.\n *\n * Works in both edit mode and read-only mode (when `defaultEntityAction`\n * is `\"view\"`). In read-only mode, `formContext.readOnly` will be `true`.\n */\n formView?: FormViewConfig;\n\n /**\n * Prevent default actions from being displayed or executed on this collection.\n */\n disableDefaultActions?: (\"edit\" | \"copy\" | \"delete\")[];\n\n /**\n * Order in which the properties are displayed.\n * If you are specifying your collection as code, the order is the same as the\n * one you define in `properties`. Additional columns are added at the\n * end of the list, if the order is not specified.\n *\n * You can use this prop to hide some properties from the table view.\n * Note that if you set this prop, other ways to hide fields, like\n * `hidden` in the property definition, will be ignored.\n * `propertiesOrder` has precedence over `hidden`.\n *\n * Supported entry formats:\n * - For properties, use the property key.\n * - For additional fields, use the field key.\n * - Child collections (Firestore subcollections, or Postgres relations\n * with `many` cardinality) each get a column with id\n * `subcollection:<slug>`, e.g. `subcollection:orders`.\n */\n propertiesOrder?: (Extract<keyof M, string> | (string & {}) | string | `subcollection:${string}`)[];\n\n /**\n * If enabled, content is loaded in batches. If `false` all entities in the\n * collection are loaded. This means that when reaching the end of the\n * collection, the CMS will load more entities.\n * You can specify a number to specify the pagination size (50 by default)\n * Defaults to `true`\n */\n pagination?: boolean | number;\n\n selectionEnabled?: boolean;\n\n /**\n * Pass your own selection controller if you want to control selected\n * entities externally.\n * @see useSelectionController\n */\n selectionController?: SelectionController<M>;\n\n /**\n * Force a filter in this view. If applied, the rest of the filters will\n * be disabled. Filters applied with this prop cannot be changed.\n * e.g. `fixedFilter: { age: [\">\", 18] }`\n * e.g. `fixedFilter: { related_user: [\"==\", new EntityReference(\"sdc43dsw2\", \"users\")] }`\n */\n readonly fixedFilter?: FilterValues<Extract<keyof M, string> | (string & {})>;\n\n /**\n * Initial filters applied to the collection this collection is related to.\n * Defaults to none. Filters applied with this prop can be changed.\n * e.g. `defaultFilter: { age: [\">\", 18] }`\n * e.g. `defaultFilter: { related_user: [\"==\", new EntityReference(\"sdc43dsw2\", \"users\")] }`\n */\n readonly defaultFilter?: FilterValues<Extract<keyof M, string> | (string & {})>; // setting FilterValues<M> can break defining collections by code\n\n /**\n * Pre-defined filter presets that appear as quick-access options in the\n * collection toolbar. Each preset applies a set of filters (and\n * optionally a sort order) with a single click.\n *\n * ```ts\n * filterPresets: [\n * {\n * label: \"Shipped this month\",\n * filterValues: {\n * status: [\"==\", \"shipped\"],\n * order_date: [\">=\", new Date(Date.now() - 30 * 86400000)]\n * }\n * }\n * ]\n * ```\n */\n readonly filterPresets?: FilterPreset<Extract<keyof M, string> | (string & {})>[];\n\n /**\n * Default sort applied to this collection.\n * When setting this prop, entities will have a default order\n * applied in the collection.\n * e.g. `sort: [\"order\", \"asc\"]`\n */\n readonly sort?: OrderByTuple<Extract<keyof M, string> | (string & {})>;\n\n /**\n * You can add additional fields to the collection view by implementing\n * an additional field delegate.\n */\n readonly additionalFields?: AdditionalFieldDelegate<M, USER>[];\n\n /**\n * Default size of the rendered collection\n */\n defaultSize?: CollectionSize;\n\n /**\n * Can the elements in this collection be edited inline in the collection\n * view. Even when inline editing is disabled, entities can still be\n * edited in the side panel (subject to `securityRules`).\n */\n inlineEditing?: boolean;\n\n /**\n * Should this collection be hidden from the main navigation panel, if\n * it is at the root level, or in the entity side panel if it's a\n * subcollection.\n * It will still be accessible if you reach the specified path.\n * You can also use this collection as a reference target.\n */\n hideFromNavigation?: boolean;\n\n /**\n * If you want to open custom views or subcollections by default when opening the edit\n * view of a entity, you can specify the path to the view here.\n * The path is relative to the current collection. For example if you have a collection\n * that has a custom view as well as a subcollection that refers to another entity, you can\n * either specify the path to the custom view or the path to the subcollection.\n */\n defaultSelectedView?: string | DefaultSelectedViewBuilder;\n\n /**\n * Should the ID of this collection be hidden from the form view.\n */\n hideIdFromForm?: boolean;\n\n /**\n * Should the ID of this collection be hidden from the grid view.\n */\n hideIdFromCollection?: boolean;\n\n /**\n * If set to true, the form will be auto-saved when the user changes\n * the value of a field.\n * Defaults to false.\n * When a new entity is created, this property can be updated to generated a new ID\n */\n formAutoSave?: boolean;\n\n /**\n *\n */\n exportable?: boolean | ExportConfig<USER>;\n\n /**\n * Width of the side dialog (in pixels) when opening a entity in this collection.\n */\n sideDialogWidth?: number | string;\n\n /**\n * If set to true, the default values of the properties will be applied\n * to the entity every time the entity is updated (not only when created).\n * Defaults to false.\n */\n alwaysApplyDefaultValues?: boolean;\n\n /**\n * If set to true, a tab including the JSON representation of the entity will be included.\n */\n includeJsonView?: boolean;\n\n /**\n * Should local changes be backed up in local storage, to prevent data loss on\n * accidental navigations.\n * - `manual_apply`: When the user navigates back to a entity with local changes,\n * they will be prompted to restore the changes.\n * - `auto_apply`: When the user navigates back to a entity with local changes,\n * the changes will be automatically applied.\n * - `false`: Local changes will not be backed up.\n * Defaults to `manual_apply`.\n */\n localChangesBackup?: \"manual_apply\" | \"auto_apply\" | false;\n\n /**\n * Default view mode for displaying this collection.\n * - \"table\": Display entities in a table with inline editing (default)\n * - \"cards\": Display entities as a grid of cards with thumbnails\n * - \"kanban\": Display entities in a Kanban board grouped by a property\n * Defaults to \"table\".\n */\n defaultViewMode?: ViewMode;\n\n /**\n * Which view modes are available for this collection.\n * Possible values: \"table\", \"cards\", \"kanban\".\n * Defaults to all three: [\"table\", \"cards\", \"kanban\"].\n * Note: \"kanban\" will only be available if the collection has at least\n * one string property with `enum` defined, regardless of this setting.\n */\n enabledViews?: ViewMode[];\n\n /**\n * Configuration for Kanban board view mode.\n * When set, the Kanban view mode becomes available.\n */\n kanban?: KanbanConfig<M>;\n\n /**\n * Property key to use for ordering items.\n * Must reference a string/text property. When items are reordered,\n * this property will be updated with lexicographic sort keys\n * (e.g. \"a0\", \"a1\", \"a0V\") using string-based fractional indexing.\n * Used by Kanban view for ordering within columns\n * and can be used for general ordering purposes.\n */\n readonly orderProperty?: Extract<keyof M, string> | (string & {});\n\n /**\n * Actions that can be performed on the entities in this collection.\n */\n entityActions?: EntityAction<M, USER>[];\n\n /**\n * Builder for the collection actions rendered in the toolbar\n */\n Actions?: ComponentRef<CollectionActionsProps>[];\n\n /**\n * Collection-scoped component overrides. These take precedence over\n * global overrides set on `<Rebase>`, but only within this collection's\n * views (entity form, detail view, table, empty state, etc.).\n *\n * Only collection-scoped components (like `Entity.Form`, `Collection.EmptyState`,\n * `Collection.Card`, etc.) can be overridden here. App-level components\n * (like `Shell.AppBar`, `HomePage`) can only be overridden at the `<Rebase>` level.\n *\n * @example\n * ```tsx\n * const productsCollection: PostgresCollectionConfig = {\n * name: \"Products\",\n * slug: \"products\",\n * table: \"products\",\n * components: {\n * \"Entity.Form\": { Component: ProductCustomForm },\n * \"Collection.Card\": { Component: ProductCard },\n * },\n * properties: { ... }\n * };\n * ```\n */\n components?: CollectionComponentOverrideMap;};\n\n/**\n * There is deliberately no `AdminCollectionConfig` here any more.\n *\n * It used to be `Omit<CollectionConfig, \"admin\"> & { admin?: AdminCollectionOptions }`,\n * a wrapper that existed because core typed the block opaquely. Now that `augment.ts`\n * declares `admin` directly on `BaseCollectionConfig`, `CollectionConfig` *is* the\n * authoring type — the wrapper would be an alias of it, and a second name for one thing\n * is what this whole refactor has been removing.\n *\n * A project opts its program in with one line, once:\n *\n * ```ts\n * /// <reference types=\"@rebasepro/admin-types\" />\n * ```\n *\n * after which `admin` is typed on every collection and every property. Without it,\n * writing one is an error — which is the guarantee a BaaS install depends on.\n */\n\n/**\n * Define a collection with the admin block type-checked.\n *\n * The same identity function as `defineCollection` in `@rebasepro/common` — which\n * is what a BaaS or headless project uses, and where `admin` does not exist at all\n * — with one difference: importing this one brings the augmentation with it, so\n * `admin: { icon, listProperties, kanban }` gets completion and a typo is an\n * error. See {@link AdminCollectionOptions}.\n *\n * Import it from the layer you are in. A project with an admin panel wants this\n * one; a project without one has no `admin` block to check.\n *\n * `const P` captures the literal property types, which is what gives\n * `admin.titleProperty`, `admin.sort` and `admin.propertiesOrder` completion over\n * the collection's own property keys rather than plain `string`.\n *\n * @example\n * export default defineCollection({\n * slug: \"posts\",\n * table: \"posts\",\n * properties: {\n * title: { name: \"Title\", type: \"string\" },\n * status: { name: \"Status\", type: \"string\" }\n * },\n * admin: {\n * icon: \"FileText\",\n * titleProperty: \"title\", // completion: \"title\" | \"status\"\n * listProperties: [\"title\", \"status\"]\n * }\n * });\n *\n * @group Builder\n */\nexport function defineCollection<\n const P extends PostgresProperties,\n USER extends User = User\n>(\n collection: Omit<PostgresCollectionConfig<InferEntityType<P>, USER>, \"properties\">\n & { properties: P }\n): PostgresCollectionConfig<InferEntityType<P>, USER> & { properties: P };\n\n/** Define a Firestore-backed collection with the admin block checked. @group Builder */\nexport function defineCollection<\n const P extends FirebaseProperties,\n USER extends User = User\n>(\n collection: Omit<FirebaseCollectionConfig<InferEntityType<P>, USER>, \"properties\">\n & { properties: P }\n): FirebaseCollectionConfig<InferEntityType<P>, USER> & { properties: P };\n\n/** Define a MongoDB-backed collection with the admin block checked. @group Builder */\nexport function defineCollection<\n const P extends MongoProperties,\n USER extends User = User\n>(\n collection: Omit<MongoDBCollectionConfig<InferEntityType<P>, USER>, \"properties\">\n & { properties: P }\n): MongoDBCollectionConfig<InferEntityType<P>, USER> & { properties: P };\n\n/** Identity at runtime; the overloads above are the whole point. @group Builder */\nexport function defineCollection(collection: CollectionConfig): CollectionConfig {\n return collection;\n}\n\n/**\n * Re-exported from `@rebasepro/types`, where the list has to live: the ts-morph\n * schema editor in `@rebasepro/server` needs it to know which keys go inside the\n * block when it rewrites a collection file, and a core package may not import\n * this one. The list is plain data, so core is a fine home for it.\n *\n * What core *cannot* do is check the list against the type. That happens here.\n *\n * @group Models\n */\nexport type { AdminCollectionKey } from \"@rebasepro/types\";\n\n/**\n * Core's list, re-exported through a `satisfies` clause that is the agreement\n * check: a key core names that is not an option here fails to compile, and\n * `satisfies` keeps the literal tuple type rather than widening it to `string[]`.\n *\n * The reverse direction — an option missing from core's list — has no type-level\n * expression, since there is no exhaustiveness check over an optional-property\n * keyof. `test/admin_collection.test.ts` counts them instead.\n */\nexport const ADMIN_COLLECTION_KEYS = CORE_ADMIN_COLLECTION_KEYS satisfies readonly (keyof AdminCollectionOptions)[];\n\n\n/**\n * A collection as the admin panel works with it: the contract with the `admin`\n * block flattened onto the top level.\n *\n * The panel reads presentation fields in a few hundred places, and threading\n * `collection.admin?.propertiesOrder` through all of them would be noise that\n * buys nothing — the panel has already resolved the collection by then, merging\n * the declared config with the user's per-collection overrides from local\n * storage. So the panel gets a flat *view model*, exactly as it already does for\n * entities (`Entity` is an admin view model over flat rows, not a wire type).\n *\n * The distinction that matters is direction:\n *\n * - **Reading** a resolved collection → `AdminCollection` (flat, convenient).\n * - **Authoring or persisting** one → core's `CollectionConfig`, with the `admin`\n * block this package augments onto it (nested, which is what the file on disk\n * and the wire both look like).\n *\n * `admin` is kept alongside the flattened fields so the collection editor can\n * still see the block it has to write back.\n *\n * @group Models\n */\nexport type AdminCollection<\n M extends Record<string, unknown> = Record<string, unknown>,\n USER extends User = User\n> = WithFlatAdmin<CollectionConfig<M, USER>, M, USER>;\n\n/**\n * Flatten the admin block onto one member of the collection union at a time.\n *\n * `CollectionConfig` is a union discriminated on `engine`\n * (Postgres | Firestore | MongoDB), and a bare `Omit<Union, \"admin\">` collapses it\n * into a single object type with the discriminant widened. The result stops being\n * assignable back to `CollectionConfig`, so every call that hands a resolved\n * collection to a core function fails — which is exactly what happened. The\n * `C extends unknown` clause makes the mapping distributive, so each member keeps\n * its literal `engine` and stays assignable to its counterpart.\n */\ntype WithFlatAdmin<C, M extends Record<string, unknown>, USER extends User> =\n C extends unknown\n ? Omit<C, \"admin\"> & AdminCollectionOptions<M, USER> & { admin?: AdminCollectionOptions<M, USER> }\n : never;\n\n/** {@link AdminCollection} for a Postgres collection. @group Models */\nexport type AdminPostgresCollection<\n M extends Record<string, unknown> = Record<string, unknown>,\n USER extends User = User\n> = Omit<PostgresCollectionConfig<M, USER>, \"admin\">\n & AdminCollectionOptions<M, USER>\n & { admin?: AdminCollectionOptions<M, USER> };\n\n/**\n * Flatten a collection's `admin` block onto it, producing the panel's view model.\n *\n * Shallow by design: the block's fields are independent, so a deep merge would\n * only create opportunities for a nested object to be half from one source and\n * half from the other. `admin` survives on the result.\n *\n * Idempotent — flattening an already-flat collection returns an equivalent one —\n * because the panel resolves collections at more than one entry point (the\n * registry, `<Rebase collections>`, a plugin's `modifyCollection`) and they must\n * not fight over which has run.\n */\nexport function resolveAdminCollection<\n M extends Record<string, unknown> = Record<string, unknown>,\n USER extends User = User\n>(collection: CollectionConfig<M, USER> | AdminCollection<M, USER>): AdminCollection<M, USER> {\n const block = (collection as { admin?: AdminCollectionOptions<M, USER> }).admin;\n if (!block) return collection as AdminCollection<M, USER>;\n return { ...(collection as AdminCollection<M, USER>), ...block, admin: block };\n}\n\n/**\n * The inverse: lift flattened admin fields back into the block.\n *\n * Used on the way out — persisting from the collection editor, or handing a\n * collection to anything that expects the authoring shape. Any key in\n * {@link ADMIN_COLLECTION_KEYS} found at the top level is moved down, so a\n * round trip through the panel does not leave the file flat.\n */\nexport function toAdminCollectionConfig<\n M extends Record<string, unknown> = Record<string, unknown>,\n USER extends User = User\n>(collection: AdminCollection<M, USER> | CollectionConfig<M, USER>): CollectionConfig<M, USER> {\n const source = collection as Record<string, unknown>;\n const top: Record<string, unknown> = {};\n const block: Record<string, unknown> = { ...(source.admin as object ?? {}) };\n\n for (const [key, value] of Object.entries(source)) {\n if (key === \"admin\") continue;\n if ((ADMIN_COLLECTION_KEYS as readonly string[]).includes(key)) block[key] = value;\n else top[key] = value;\n }\n\n if (Object.keys(block).length > 0) top.admin = block;\n return top as unknown as CollectionConfig<M, USER>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAwCA,SAAgB,eAAkB,WAAoD;CAClF,OAAO;AACX;;;;;;AAOA,SAAgB,iBAAoB,WAAqD;CACrF,OAAO;AACX;;;;ACqZA,SAAgB,iBAAiB,YAAgD;CAC7E,OAAO;AACX;;;;;;;;;;AAuBA,IAAa,wBAAwB;;;;;;;;;;;;;AAmErC,SAAgB,uBAGd,YAA4F;CAC1F,MAAM,QAAS,WAA2D;CAC1E,IAAI,CAAC,OAAO,OAAO;CACnB,OAAO;EAAE,GAAI;EAAyC,GAAG;EAAO,OAAO;CAAM;AACjF;;;;;;;;;AAUA,SAAgB,wBAGd,YAA6F;CAC3F,MAAM,SAAS;CACf,MAAM,MAA+B,CAAC;CACtC,MAAM,QAAiC,EAAE,GAAI,OAAO,SAAmB,CAAC,EAAG;CAE3E,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAAG;EAC/C,IAAI,QAAQ,SAAS;EACrB,IAAK,sBAA4C,SAAS,GAAG,GAAG,MAAM,OAAO;OACxE,IAAI,OAAO;CACpB;CAEA,IAAI,OAAO,KAAK,KAAK,EAAE,SAAS,GAAG,IAAI,QAAQ;CAC/C,OAAO;AACX"}
|