@rebasepro/core 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +178 -110
  3. package/dist/components/LoginView/LoginView.d.ts +1 -6
  4. package/dist/contexts/SnackbarProvider.d.ts +1 -1
  5. package/dist/hooks/data/save.d.ts +2 -2
  6. package/dist/hooks/data/useEntityFetch.d.ts +5 -0
  7. package/dist/hooks/useResolvedComponent.d.ts +1 -1
  8. package/dist/index.es.js +386 -309
  9. package/dist/index.es.js.map +1 -1
  10. package/dist/index.umd.js +436 -358
  11. package/dist/index.umd.js.map +1 -1
  12. package/package.json +22 -23
  13. package/src/components/AIIcon.tsx +1 -1
  14. package/src/components/Debug/UIReferenceView.tsx +52 -3
  15. package/src/components/Debug/UIStyleGuide.tsx +1 -2
  16. package/src/components/ErrorView.tsx +1 -2
  17. package/src/components/LanguageToggle.tsx +1 -9
  18. package/src/components/LoginView/LoginView.tsx +21 -12
  19. package/src/components/UserDisplay.tsx +1 -2
  20. package/src/components/UserSelectPopover.tsx +13 -2
  21. package/src/components/UserSettingsView.tsx +12 -3
  22. package/src/components/common/useDataTableController.tsx +2 -2
  23. package/src/contexts/SnackbarProvider.tsx +2 -1
  24. package/src/core/PluginLifecycleManager.tsx +0 -1
  25. package/src/core/Rebase.tsx +1 -1
  26. package/src/hooks/data/save.ts +4 -4
  27. package/src/hooks/data/useCollectionFetch.tsx +4 -4
  28. package/src/hooks/data/useEntityFetch.tsx +13 -3
  29. package/src/hooks/data/useRelationSelector.tsx +2 -2
  30. package/src/hooks/useResolvedComponent.tsx +7 -6
  31. package/src/hooks/useStudioBridge.tsx +0 -1
  32. package/src/locales/en.ts +8 -0
  33. package/src/util/icons.tsx +16 -8
  34. package/src/util/previews.ts +1 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/core",
3
3
  "type": "module",
4
- "version": "0.1.0",
4
+ "version": "0.2.1",
5
5
  "description": "Rebase core — framework-agnostic runtime for data-driven admin panels",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/rebaseco"
@@ -33,30 +33,30 @@
33
33
  "exports": {
34
34
  ".": {
35
35
  "types": "./dist/index.d.ts",
36
- "development": "./src/index.ts",
36
+ "development": "./dist/index.es.js",
37
37
  "import": "./dist/index.es.js",
38
38
  "require": "./dist/index.umd.js"
39
39
  },
40
40
  "./vitePlugin": {
41
41
  "types": "./dist/vitePlugin.d.ts",
42
- "development": "./src/vitePlugin.ts",
42
+ "development": "./dist/vitePlugin.js",
43
43
  "import": "./dist/vitePlugin.js",
44
44
  "require": "./dist/vitePlugin.js"
45
45
  },
46
46
  "./package.json": "./package.json"
47
47
  },
48
48
  "dependencies": {
49
- "compressorjs": "^1.2.1",
49
+ "compressorjs": "^1.3.0",
50
50
  "fast-equals": "6.0.0",
51
- "fuse.js": "^7.1.0",
52
- "i18next": "^23.16.4",
51
+ "fuse.js": "^7.3.0",
52
+ "i18next": "^23.16.8",
53
53
  "notistack": "^3.0.2",
54
54
  "react-i18next": "^14.1.3",
55
- "@rebasepro/utils": "0.1.0",
56
- "@rebasepro/common": "0.1.0",
57
- "@rebasepro/formex": "0.1.0",
58
- "@rebasepro/types": "0.1.0",
59
- "@rebasepro/ui": "0.1.0"
55
+ "@rebasepro/common": "0.2.1",
56
+ "@rebasepro/types": "0.2.1",
57
+ "@rebasepro/utils": "0.2.1",
58
+ "@rebasepro/ui": "0.2.1",
59
+ "@rebasepro/formex": "0.2.1"
60
60
  },
61
61
  "peerDependencies": {
62
62
  "react": ">=19.0.0",
@@ -65,26 +65,25 @@
65
65
  "react-router-dom": "^7.0.0"
66
66
  },
67
67
  "devDependencies": {
68
- "@jest/globals": "^30.2.0",
69
- "@testing-library/react": "^16.3.0",
68
+ "@jest/globals": "^30.4.1",
69
+ "@testing-library/react": "^16.3.2",
70
70
  "@testing-library/user-event": "^14.6.1",
71
71
  "@types/jest": "^29.5.14",
72
- "@types/node": "^20.19.17",
73
- "@types/react": "^19.0.8",
74
- "@types/react-dom": "^19.0.3",
72
+ "@types/node": "^20.19.41",
73
+ "@types/react": "^19.2.15",
74
+ "@types/react-dom": "^19.2.3",
75
75
  "@types/react-measure": "^2.0.12",
76
- "@vitejs/plugin-react": "^4.3.4",
76
+ "@vitejs/plugin-react": "^4.7.0",
77
77
  "babel-plugin-react-compiler": "beta",
78
78
  "cross-env": "^7.0.3",
79
- "eslint-plugin-react-compiler": "^19.1.0-rc.2",
79
+ "esbuild": "^0.25.12",
80
80
  "jest": "^29.7.0",
81
- "jest-environment-jsdom": "^30.2.0",
82
- "npm-run-all": "^4.1.5",
83
- "react-router-dom": "^7.0.0",
84
- "ts-jest": "^29.4.5",
81
+ "jest-environment-jsdom": "^30.4.1",
82
+ "react-router-dom": "^7.15.1",
83
+ "ts-jest": "^29.4.10",
85
84
  "tsd": "^0.31.2",
86
85
  "typescript": "^5.9.3",
87
- "vite": "^7.2.4"
86
+ "vite": "^7.3.3"
88
87
  },
89
88
  "files": [
90
89
  "dist",
@@ -1,4 +1,4 @@
1
- import { Wand2Icon } from "lucide-react";
1
+ import { Wand2Icon } from "@rebasepro/ui";
2
2
  import React from "react";
3
3
 
4
4
  export interface AIIconProps {
@@ -11,8 +11,57 @@
11
11
  * DrawerNavigationGroup.tsx, UsersView.tsx, RolesView.tsx
12
12
  */
13
13
  import React, { useState } from "react";
14
- import { Alert, Avatar, BooleanSwitch, Button, Checkbox, Chip, CircularProgress, cls, defaultBorderMixin, IconButton, LoadingButton, Menu, MenuItem, MultiSelect, MultiSelectItem, SearchBar, Select, SelectItem, Separator, Skeleton, Tab, Table, TableBody, TableCell, TableHeader, TableRow, Tabs, TextField, Tooltip, Typography , iconSize } from "@rebasepro/ui";
15
- import { SunMoonIcon, ChevronsLeftIcon, ChevronsRightIcon, LogOutIcon, TagIcon, PlusIcon, MoonIcon, Trash2Icon, PencilIcon, ChevronDownIcon, FilterIcon, SunIcon, SettingsIcon, KanbanIcon, LayoutGridIcon, ListIcon, FolderIcon, UserIcon } from "lucide-react";
14
+ import {
15
+ Alert,
16
+ Avatar,
17
+ BooleanSwitch,
18
+ Button,
19
+ Checkbox,
20
+ ChevronDownIcon,
21
+ ChevronsLeftIcon,
22
+ ChevronsRightIcon,
23
+ Chip,
24
+ CircularProgress,
25
+ cls,
26
+ defaultBorderMixin,
27
+ FilterIcon,
28
+ FolderIcon,
29
+ IconButton,
30
+ iconSize,
31
+ KanbanIcon,
32
+ LayoutGridIcon,
33
+ ListIcon,
34
+ LoadingButton,
35
+ LogOutIcon,
36
+ Menu,
37
+ MenuItem,
38
+ MoonIcon,
39
+ MultiSelect,
40
+ MultiSelectItem,
41
+ PencilIcon,
42
+ PlusIcon,
43
+ SearchBar,
44
+ Select,
45
+ SelectItem,
46
+ Separator,
47
+ SettingsIcon,
48
+ Skeleton,
49
+ SunIcon,
50
+ SunMoonIcon,
51
+ Tab,
52
+ Table,
53
+ TableBody,
54
+ TableCell,
55
+ TableHeader,
56
+ TableRow,
57
+ Tabs,
58
+ TagIcon,
59
+ TextField,
60
+ Tooltip,
61
+ Trash2Icon,
62
+ Typography,
63
+ UserIcon
64
+ } from "@rebasepro/ui";
16
65
  import { RebaseLogo } from "../RebaseLogo";
17
66
 
18
67
  const SECTIONS = [
@@ -147,7 +196,7 @@ block: "start" });
147
196
  <div className="mt-3 flex-grow overflow-hidden">
148
197
  <div className="my-2 mx-2 flex flex-col">
149
198
  <div className="overflow-hidden rounded-lg bg-surface-50 dark:bg-surface-950/30">
150
- {[<FolderIcon size={iconSize.small}/>, <UserIcon size={iconSize.small}/>, <TagIcon size={iconSize.small}/>].map((icon, i) => (
199
+ {[<FolderIcon key="folder" size={iconSize.small}/>, <UserIcon key="user" size={iconSize.small}/>, <TagIcon key="tag" size={iconSize.small}/>].map((icon, i) => (
151
200
  <div key={i} className="rounded-lg truncate hover:bg-surface-accent-300/75 dark:hover:bg-surface-accent-800/75 flex flex-row items-center h-10">
152
201
  <div className="shrink-0 flex items-center justify-center w-[56px] h-[40px] text-text-secondary dark:text-text-secondary-dark">
153
202
  {icon}
@@ -1,7 +1,6 @@
1
1
 
2
2
  import React from "react";
3
- import { Typography, Button, IconButton, Paper, Container, Separator, cls } from "@rebasepro/ui";
4
- import { Trash2Icon } from "lucide-react";
3
+ import { Button, cls, Container, IconButton, Paper, Separator, Trash2Icon, Typography } from "@rebasepro/ui";
5
4
 
6
5
  export const UIStyleGuide = () => {
7
6
  const typographyVariants = [
@@ -1,7 +1,6 @@
1
1
  import React from "react";
2
2
  import { ErrorTooltip } from "./ErrorTooltip";
3
- import { Typography, Button } from "@rebasepro/ui";
4
- import { AlertTriangleIcon } from "lucide-react";
3
+ import { AlertTriangleIcon, Button, Typography } from "@rebasepro/ui";
5
4
 
6
5
  /**
7
6
  * @group Components
@@ -1,6 +1,5 @@
1
1
  import React from "react";
2
- import { IconButton, Menu, MenuItem, Typography , iconSize } from "@rebasepro/ui";
3
- import { CheckIcon, LanguagesIcon } from "lucide-react";
2
+ import { CheckIcon, IconButton, iconSize, LanguagesIcon, Menu, MenuItem, Typography } from "@rebasepro/ui";
4
3
  import { useTranslation } from "../hooks";
5
4
 
6
5
  export function LanguageToggle() {
@@ -15,49 +14,42 @@ export function LanguageToggle() {
15
14
  </IconButton>}>
16
15
  <MenuItem onClick={() => i18n.changeLanguage("en")}>
17
16
  <div className="flex w-full items-center justify-between gap-4">
18
- {/* eslint-disable-next-line i18next/no-literal-string */}
19
17
  <Typography variant="body2" className={i18n.language === "en" ? "font-bold" : ""}>English</Typography>
20
18
  {i18n.language === "en" && <CheckIcon size={iconSize.small}/>}
21
19
  </div>
22
20
  </MenuItem>
23
21
  <MenuItem onClick={() => i18n.changeLanguage("es")}>
24
22
  <div className="flex w-full items-center justify-between gap-4">
25
- {/* eslint-disable-next-line i18next/no-literal-string */}
26
23
  <Typography variant="body2" className={i18n.language === "es" ? "font-bold" : ""}>Español</Typography>
27
24
  {i18n.language === "es" && <CheckIcon size={iconSize.small}/>}
28
25
  </div>
29
26
  </MenuItem>
30
27
  <MenuItem onClick={() => i18n.changeLanguage("de")}>
31
28
  <div className="flex w-full items-center justify-between gap-4">
32
- {/* eslint-disable-next-line i18next/no-literal-string */}
33
29
  <Typography variant="body2" className={i18n.language === "de" ? "font-bold" : ""}>Deutsch</Typography>
34
30
  {i18n.language === "de" && <CheckIcon size={iconSize.small}/>}
35
31
  </div>
36
32
  </MenuItem>
37
33
  <MenuItem onClick={() => i18n.changeLanguage("fr")}>
38
34
  <div className="flex w-full items-center justify-between gap-4">
39
- {/* eslint-disable-next-line i18next/no-literal-string */}
40
35
  <Typography variant="body2" className={i18n.language === "fr" ? "font-bold" : ""}>Français</Typography>
41
36
  {i18n.language === "fr" && <CheckIcon size={iconSize.small}/>}
42
37
  </div>
43
38
  </MenuItem>
44
39
  <MenuItem onClick={() => i18n.changeLanguage("it")}>
45
40
  <div className="flex w-full items-center justify-between gap-4">
46
- {/* eslint-disable-next-line i18next/no-literal-string */}
47
41
  <Typography variant="body2" className={i18n.language === "it" ? "font-bold" : ""}>Italiano</Typography>
48
42
  {i18n.language === "it" && <CheckIcon size={iconSize.small}/>}
49
43
  </div>
50
44
  </MenuItem>
51
45
  <MenuItem onClick={() => i18n.changeLanguage("hi")}>
52
46
  <div className="flex w-full items-center justify-between gap-4">
53
- {/* eslint-disable-next-line i18next/no-literal-string */}
54
47
  <Typography variant="body2" className={i18n.language === "hi" ? "font-bold" : ""}>हिन्दी</Typography>
55
48
  {i18n.language === "hi" && <CheckIcon size={iconSize.small}/>}
56
49
  </div>
57
50
  </MenuItem>
58
51
  <MenuItem onClick={() => i18n.changeLanguage("pt")}>
59
52
  <div className="flex w-full items-center justify-between gap-4">
60
- {/* eslint-disable-next-line i18next/no-literal-string */}
61
53
  <Typography variant="body2" className={i18n.language === "pt" ? "font-bold" : ""}>Português</Typography>
62
54
  {i18n.language === "pt" && <CheckIcon size={iconSize.small}/>}
63
55
  </div>
@@ -18,8 +18,22 @@ declare global {
18
18
  }
19
19
  }
20
20
 
21
- import { Button, cls, IconButton, LoadingButton, Menu, MenuItem, TextField, Typography, iconSize } from "@rebasepro/ui";
22
- import { ArrowLeftIcon, MailIcon, MoonIcon, SunIcon, SunMoonIcon } from "lucide-react";
21
+ import {
22
+ ArrowLeftIcon,
23
+ Button,
24
+ cls,
25
+ IconButton,
26
+ iconSize,
27
+ LoadingButton,
28
+ MailIcon,
29
+ Menu,
30
+ MenuItem,
31
+ MoonIcon,
32
+ SunIcon,
33
+ SunMoonIcon,
34
+ TextField,
35
+ Typography
36
+ } from "@rebasepro/ui";
23
37
  import { AuthControllerExtended, User } from "@rebasepro/types";
24
38
  import { ErrorView } from "../ErrorView";
25
39
  import { RebaseLogo } from "../RebaseLogo";
@@ -73,11 +87,7 @@ export interface LoginViewProps {
73
87
  */
74
88
  notAllowedError?: string | Error;
75
89
 
76
- /**
77
- * Override: enable Google login button.
78
- * If not set, checks `authController.capabilities.googleLogin`.
79
- */
80
- googleEnabled?: boolean;
90
+
81
91
 
82
92
  /**
83
93
  * Google client ID for Google OAuth.
@@ -112,7 +122,6 @@ export function LoginView({
112
122
  disableSignupScreen = false,
113
123
  disabled = false,
114
124
  notAllowedError,
115
- googleEnabled,
116
125
  googleClientId,
117
126
  needsSetup,
118
127
  registrationEnabled
@@ -140,7 +149,7 @@ export function LoginView({
140
149
  ?? ("needsSetup" in authController && !!(authController as { needsSetup?: boolean }).needsSetup)
141
150
  ?? false;
142
151
  const canRegister = registrationEnabled ?? caps.registration ?? false;
143
- const hasGoogleLogin = googleEnabled ?? caps.googleLogin ?? false;
152
+ const hasGoogleLogin = googleClientId && (caps.enabledProviders?.includes("google") ?? caps.googleLogin ?? false);
144
153
  const hasPasswordReset = caps.passwordReset ?? !!authController.forgotPassword;
145
154
 
146
155
  const showRegistration = !disableSignupScreen && canRegister;
@@ -190,7 +199,7 @@ export function LoginView({
190
199
  return (
191
200
  <div
192
201
  className={cls(
193
- "relative flex items-center justify-center h-screen w-screen p-4 transition-opacity duration-500 bg-white dark:bg-surface-900",
202
+ "relative flex items-center justify-center h-screen w-screen p-4 transition-opacity duration-500 bg-surface-50 dark:bg-surface-800",
194
203
  fadeIn ? "opacity-100" : "opacity-0"
195
204
  )}>
196
205
 
@@ -443,7 +452,7 @@ function LoginForm({
443
452
  useEffect(() => {
444
453
  if (!document) return;
445
454
  const escFunction = (event: KeyboardEvent) => {
446
- if (event.keyCode === 27) {
455
+ if (event.key === "Escape") {
447
456
  onClose();
448
457
  }
449
458
  };
@@ -631,7 +640,7 @@ function ForgotPasswordForm({
631
640
  useEffect(() => {
632
641
  if (!document) return;
633
642
  const escFunction = (event: KeyboardEvent) => {
634
- if (event.keyCode === 27) {
643
+ if (event.key === "Escape") {
635
644
  onClose();
636
645
  }
637
646
  };
@@ -1,5 +1,4 @@
1
- import { cls, defaultBorderMixin } from "@rebasepro/ui";
2
- import { CircleUserIcon } from "lucide-react";
1
+ import { CircleUserIcon, cls, defaultBorderMixin } from "@rebasepro/ui";
3
2
  import { User } from "@rebasepro/types";
4
3
 
5
4
  /**
@@ -1,7 +1,18 @@
1
1
 
2
2
  import React, { useCallback, useMemo, useRef, useState } from "react";
3
- import { cls, defaultBorderMixin, Popover, Typography, CircularProgress, IconButton , iconSize, SearchBar } from "@rebasepro/ui";
4
- import { CircleUserIcon, SearchIcon, XIcon } from "lucide-react";
3
+ import {
4
+ CircleUserIcon,
5
+ CircularProgress,
6
+ cls,
7
+ defaultBorderMixin,
8
+ IconButton,
9
+ iconSize,
10
+ Popover,
11
+ SearchBar,
12
+ SearchIcon,
13
+ Typography,
14
+ XIcon
15
+ } from "@rebasepro/ui";
5
16
  import { User } from "@rebasepro/types";
6
17
 
7
18
  /**
@@ -1,7 +1,16 @@
1
1
 
2
2
  import React, { useEffect, useState } from "react";
3
- import { Avatar, Button, CircularProgress, Tabs, Tab, TextField, Typography, IconButton } from "@rebasepro/ui";
4
- import { Trash2Icon } from "lucide-react";
3
+ import {
4
+ Avatar,
5
+ Button,
6
+ CircularProgress,
7
+ IconButton,
8
+ Tab,
9
+ Tabs,
10
+ TextField,
11
+ Trash2Icon,
12
+ Typography
13
+ } from "@rebasepro/ui";
5
14
  import { useAuthController, useTranslation } from "../hooks";
6
15
 
7
16
  interface SessionInfo {
@@ -22,7 +31,7 @@ interface ExtendedAuthController {
22
31
  }
23
32
 
24
33
  export function UserSettingsView() {
25
- const authController = useAuthController() as unknown as ExtendedAuthController;
34
+ const authController = useAuthController() as ExtendedAuthController;
26
35
  const user = authController.user;
27
36
  const { t } = useTranslation();
28
37
 
@@ -270,7 +270,7 @@ export function useDataTableController<M extends Record<string, any> = any, USER
270
270
  limit: itemCount,
271
271
  orderBy: orderByParams,
272
272
  searchString
273
- }, ((res: FindResponse<M>) => onEntitiesUpdate(res.data)) as any, onError);
273
+ }, (res) => onEntitiesUpdate(res.data as Entity<M>[]), onError);
274
274
  } else {
275
275
  accessor.find({
276
276
  where: whereParams,
@@ -278,7 +278,7 @@ export function useDataTableController<M extends Record<string, any> = any, USER
278
278
  orderBy: orderByParams,
279
279
  searchString
280
280
  })
281
- .then(((res: FindResponse<M>) => onEntitiesUpdate(res.data)) as any)
281
+ .then((res) => onEntitiesUpdate(res.data as Entity<M>[]))
282
282
  .catch(onError);
283
283
  unsubscribe = () => undefined;
284
284
  }
@@ -3,10 +3,11 @@
3
3
  import React, { PropsWithChildren } from "react";
4
4
  import { SnackbarProvider as NotistackSnackbarProvider } from "notistack";
5
5
 
6
- export const SnackbarProvider: React.FC<PropsWithChildren<{}>> = ({ children }) => {
6
+ export const SnackbarProvider: React.FC<PropsWithChildren> = ({ children }) => {
7
7
 
8
8
  return (
9
9
  <NotistackSnackbarProvider maxSnack={3}
10
+ preventDuplicate={true}
10
11
  autoHideDuration={3500}>
11
12
  {children}
12
13
  </NotistackSnackbarProvider>
@@ -58,7 +58,6 @@ export function PluginLifecycleManager({
58
58
  }
59
59
  };
60
60
  // Only run on mount/unmount — plugins array identity should be stable
61
- // eslint-disable-next-line react-hooks/exhaustive-deps
62
61
  }, []);
63
62
 
64
63
  // ── Auth state change lifecycle ──────────────────────────────────
@@ -89,7 +89,7 @@ export function Rebase<USER extends User>(props: RebaseProps<USER>) {
89
89
  loading: false,
90
90
  users: [],
91
91
  getUser: (uid: string) => null
92
- } as unknown as UserManagementDelegate<USER>, [plugins, _userManagement]);
92
+ } as UserManagementDelegate<USER>, [plugins, _userManagement]);
93
93
 
94
94
  // Auth fallback logic
95
95
  const clientAuthController = useAuthSubscription(authControllerProp ? undefined : client?.auth);
@@ -5,7 +5,7 @@ import { RebaseData } from "@rebasepro/types";
5
5
  /**
6
6
  * @group Hooks and utilities
7
7
  */
8
- export type SaveEntityWithCallbacksProps<M extends Record<string, any>> =
8
+ export type SaveEntityWithCallbacksProps<M extends Record<string, unknown>> =
9
9
  SaveEntityProps<M> &
10
10
  {
11
11
  afterSave?: (updatedEntity: Entity<M>) => void,
@@ -31,7 +31,7 @@ export type SaveEntityWithCallbacksProps<M extends Record<string, any>> =
31
31
  * @param afterSaveError
32
32
  * @group Hooks and utilities
33
33
  */
34
- export async function saveEntityWithCallbacks<M extends Record<string, any>>({
34
+ export async function saveEntityWithCallbacks<M extends Record<string, unknown>>({
35
35
  collection,
36
36
  path,
37
37
  entityId,
@@ -57,9 +57,9 @@ export async function saveEntityWithCallbacks<M extends Record<string, any>>({
57
57
 
58
58
  let savePromise: Promise<Entity<M>>;
59
59
  if (status === "new" || status === "copy") {
60
- savePromise = accessor.create(values, entityId) as any;
60
+ savePromise = accessor.create(values, entityId) as Promise<Entity<M>>;
61
61
  } else {
62
- savePromise = accessor.update(entityId!, values) as any;
62
+ savePromise = accessor.update(entityId!, values) as Promise<Entity<M>>;
63
63
  }
64
64
 
65
65
  return savePromise.then((entity) => {
@@ -125,7 +125,7 @@ export function useCollectionFetch<M extends Record<string, any>, USER extends U
125
125
 
126
126
  // Eagerly include relations to avoid N+1 fetches.
127
127
  const hasRelations = collection.properties && Object.values(collection.properties).some(
128
- (p: any) => p.type === "relation" || p.type === "reference"
128
+ (p) => p.type === "relation" || p.type === "reference"
129
129
  );
130
130
  const includeParams = hasRelations ? ["*"] : undefined;
131
131
 
@@ -136,7 +136,7 @@ export function useCollectionFetch<M extends Record<string, any>, USER extends U
136
136
  orderBy: orderByParams,
137
137
  searchString,
138
138
  include: includeParams
139
- }, onEntitiesUpdate as any, onError);
139
+ }, (res) => onEntitiesUpdate({ data: res.data as Entity<M>[], meta: res.meta }), onError);
140
140
  } else {
141
141
  accessor.find({
142
142
  where: whereParams,
@@ -145,12 +145,12 @@ export function useCollectionFetch<M extends Record<string, any>, USER extends U
145
145
  searchString,
146
146
  include: includeParams
147
147
  })
148
- .then(onEntitiesUpdate as any)
148
+ .then((res) => onEntitiesUpdate({ data: res.data as Entity<M>[], meta: res.meta }))
149
149
  .catch(onError);
150
150
  return () => {
151
151
  };
152
152
  }
153
- }, [path, itemCount, currentSort, sortByProperty, filterValues, searchString]);
153
+ }, [path, itemCount, currentSort, sortByProperty, filterValues, searchString, dataClient, collection]);
154
154
 
155
155
  return useMemo(() => ({
156
156
  data,
@@ -39,6 +39,16 @@ export function populateEntityFetchCache<M extends Record<string, any>>(path: st
39
39
  }
40
40
  }
41
41
 
42
+ /**
43
+ * Clear the entity fetch cache. Call this on auth state changes (e.g. logout)
44
+ * to prevent stale data from a previous session leaking into the next.
45
+ */
46
+ export function clearEntityFetchCache(): void {
47
+ for (const key of Object.keys(CACHE)) {
48
+ delete CACHE[key];
49
+ }
50
+ }
51
+
42
52
  /**
43
53
  * This hook is used to fetch an entity.
44
54
  * It gives real time updates if the driver supports it.
@@ -104,10 +114,10 @@ export function useEntityFetch<M extends Record<string, any>, USER extends User
104
114
  const accessor = dataClient.collection(path);
105
115
 
106
116
  if (accessor.listenById) {
107
- return accessor.listenById(entityId, onEntityUpdate as any, onError);
117
+ return accessor.listenById(entityId, (entity) => onEntityUpdate(entity as Entity<M> | undefined), onError);
108
118
  } else {
109
119
  accessor.findById(entityId)
110
- .then(onEntityUpdate as any)
120
+ .then((entity) => onEntityUpdate(entity as Entity<M> | undefined))
111
121
  .catch(onError);
112
122
  return () => {
113
123
  };
@@ -119,7 +129,7 @@ export function useEntityFetch<M extends Record<string, any>, USER extends User
119
129
  return () => {
120
130
  };
121
131
  }
122
- }, [entityId, path]);
132
+ }, [entityId, path, dataClient, collection, useCache, databaseId]);
123
133
 
124
134
  return useMemo(() => ({
125
135
  entity,
@@ -181,7 +181,7 @@ export function useRelationSelector<M extends Record<string, any> = any>(
181
181
  limit: limit,
182
182
  orderBy: undefined,
183
183
  searchString: currentSearch
184
- }, onEntitiesUpdate as any, onErrorUpdate);
184
+ }, (res) => onEntitiesUpdate({ data: res.data as Entity<M>[], meta: res.meta }), onErrorUpdate);
185
185
  } else {
186
186
  accessor.find({
187
187
  where: whereParams,
@@ -190,7 +190,7 @@ export function useRelationSelector<M extends Record<string, any> = any>(
190
190
  orderBy: undefined,
191
191
  searchString: currentSearch
192
192
  })
193
- .then(onEntitiesUpdate as any)
193
+ .then((res) => onEntitiesUpdate({ data: res.data as Entity<M>[], meta: res.meta }))
194
194
  .catch(onErrorUpdate);
195
195
  unsubscribe = () => {};
196
196
  }
@@ -13,7 +13,7 @@ import { isLazyComponentRef } from "@rebasepro/types";
13
13
  * to the `React.lazy()` wrapper it produced. Strings are keyed by a separate
14
14
  * plain Map since they can't be WeakMap keys.
15
15
  */
16
- const lazyCache = new WeakMap<object | Function, React.ComponentType<any>>();
16
+ const lazyCache = new WeakMap<object | ((..._args: any[]) => any), React.ComponentType<any>>();
17
17
 
18
18
  /**
19
19
  * Resolves a `ComponentRef` into a renderable `React.ComponentType`.
@@ -50,7 +50,7 @@ const lazyCache = new WeakMap<object | Function, React.ComponentType<any>>();
50
50
  * </Suspense>
51
51
  * );
52
52
  * ```
53
- */
53
+ * */
54
54
  export function useResolvedComponent<P = unknown>(
55
55
  ref: ComponentRef<P> | undefined
56
56
  ): React.ComponentType<P> | undefined {
@@ -62,13 +62,14 @@ export function useResolvedComponent<P = unknown>(
62
62
  * same loader always returns the same lazy component identity.
63
63
  */
64
64
  function getOrCreateLazy<P>(
65
- key: object | Function,
65
+ key: object | ((..._args: any[]) => any),
66
66
  loader: () => Promise<{ default: React.ComponentType<P> }>
67
67
  ): React.ComponentType<P> {
68
68
  const cached = lazyCache.get(key);
69
69
  if (cached) return cached as React.ComponentType<P>;
70
70
 
71
- const LazyComponent = lazy(loader) as unknown as React.ComponentType<P>;
71
+ // SAFETY: React.lazy returns LazyExoticComponent which is structurally compatible with ComponentType<P>
72
+ const LazyComponent = lazy(loader) as React.ComponentType<P>;
72
73
  lazyCache.set(key, LazyComponent);
73
74
  return LazyComponent;
74
75
  }
@@ -100,14 +101,14 @@ export function resolveComponentRef<P = unknown>(
100
101
  // The object has { __rebaseLazy: true, load: () => import(...) }.
101
102
  if (isLazyComponentRef(ref)) {
102
103
  return getOrCreateLazy<P>(
103
- ref as unknown as object,
104
+ ref as object,
104
105
  () => (ref as LazyComponentRef<P>).load()
105
106
  );
106
107
  }
107
108
 
108
109
  // 3. Function — either a React component or a lazy import loader.
109
110
  if (typeof ref === "function") {
110
- const fn = ref as Function;
111
+ const fn = ref as (..._args: any[]) => any;
111
112
 
112
113
  // Class components (React.Component / PureComponent) have this flag
113
114
  if (fn.prototype?.isReactComponent) {
@@ -205,7 +205,6 @@ unregister }), [register, unregister]);
205
205
  const bridgeValue = useMemo<StudioBridge>(() => ({
206
206
  ...NOOP_BRIDGE,
207
207
  ...slicesRef.current
208
- // eslint-disable-next-line react-hooks/exhaustive-deps
209
208
  }), [version]);
210
209
 
211
210
  return (
package/src/locales/en.ts CHANGED
@@ -25,6 +25,8 @@ export const en: RebaseTranslations = {
25
25
  copy: "Copy",
26
26
  delete: "Delete",
27
27
  delete_not_allowed: "You have selected at least one entity you cannot delete",
28
+ edit_entity: "Edit",
29
+ back_to_detail: "Back to details",
28
30
 
29
31
  // ─── Delete dialog ───────────────────────────────────────────
30
32
  delete_confirmation_title: "Delete?",
@@ -414,6 +416,12 @@ export const en: RebaseTranslations = {
414
416
 
415
417
  no_filterable_properties: "No filterable properties available",
416
418
  apply_filters: "Apply filters",
419
+
420
+ // ─── Filter Presets ──────────────────────────────────────────
421
+ filter_presets: "Presets",
422
+ filter_preset_apply: "Apply preset",
423
+ filter_preset_active: "Active: {{label}}",
424
+
417
425
  list: "List",
418
426
  table_view_mode: "Table",
419
427
  cards: "Cards",