@rebasepro/studio 0.3.0 → 0.5.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.
Files changed (52) hide show
  1. package/README.md +73 -140
  2. package/dist/{JSEditor-Br4ke-J4.js → JSEditor-DfwRLBZg.js} +2 -15
  3. package/dist/JSEditor-DfwRLBZg.js.map +1 -0
  4. package/dist/RLSEditor-CHEExeSB.js.map +1 -1
  5. package/dist/{SQLEditor-BC0IOUQu.js → SQLEditor-CQXaI0iU.js} +2 -2
  6. package/dist/SQLEditor-CQXaI0iU.js.map +1 -0
  7. package/dist/SchemaVisualizer-BGpmzyXT.js.map +1 -1
  8. package/dist/common/src/collections/default-collections.d.ts +5 -8
  9. package/dist/common/src/data/query_builder.d.ts +6 -2
  10. package/dist/common/src/util/permissions.d.ts +14 -6
  11. package/dist/core/src/components/LoginView/LoginView.d.ts +9 -1
  12. package/dist/core/src/components/common/types.d.ts +3 -3
  13. package/dist/core/src/hooks/data/useCollectionFetch.d.ts +12 -1
  14. package/dist/index.es.js +2 -2
  15. package/dist/index.umd.js +2 -15
  16. package/dist/index.umd.js.map +1 -1
  17. package/dist/studio/src/components/RLSEditor/RLSEditor.d.ts +0 -6
  18. package/dist/studio/src/components/SchemaVisualizer/schema-visualizer.utils.d.ts +0 -8
  19. package/dist/studio/src/utils/pgColumnToProperty.d.ts +1 -1
  20. package/dist/types/src/controllers/auth.d.ts +2 -2
  21. package/dist/types/src/controllers/client.d.ts +25 -40
  22. package/dist/types/src/controllers/data.d.ts +21 -3
  23. package/dist/types/src/controllers/data_driver.d.ts +5 -0
  24. package/dist/types/src/controllers/email.d.ts +2 -0
  25. package/dist/types/src/types/auth_adapter.d.ts +3 -56
  26. package/dist/types/src/types/backend.d.ts +38 -3
  27. package/dist/types/src/types/backend_hooks.d.ts +2 -17
  28. package/dist/types/src/types/collections.d.ts +30 -6
  29. package/dist/types/src/types/entity_views.d.ts +19 -28
  30. package/dist/types/src/types/properties.d.ts +9 -15
  31. package/dist/types/src/types/user_management_delegate.d.ts +16 -53
  32. package/dist/types/src/users/index.d.ts +0 -1
  33. package/dist/types/src/users/user.d.ts +0 -1
  34. package/dist/ui/src/components/Card.d.ts +2 -3
  35. package/dist/ui/src/components/FilterChip.d.ts +2 -10
  36. package/dist/ui/src/components/VirtualTable/VirtualTableProps.d.ts +8 -2
  37. package/package.json +8 -8
  38. package/src/components/JSEditor/JSEditor.tsx +1 -1
  39. package/src/components/JSEditor/JSMonacoEditor.tsx +0 -13
  40. package/src/components/RLSEditor/RLSEditor.tsx +1 -1
  41. package/src/components/SchemaVisualizer/schema-visualizer.utils.ts +3 -3
  42. package/src/components/SchemaVisualizer/useSchemaGraph.ts +2 -2
  43. package/src/utils/pgColumnToProperty.test.ts +1 -0
  44. package/src/utils/pgColumnToProperty.ts +42 -22
  45. package/src/utils/sql_utils.ts +1 -1
  46. package/dist/JSEditor-Br4ke-J4.js.map +0 -1
  47. package/dist/SQLEditor-BC0IOUQu.js.map +0 -1
  48. package/dist/studio/src/components/SchemaVisualizer/index.d.ts +0 -5
  49. package/dist/studio/src/utils/entities.d.ts +0 -0
  50. package/dist/types/src/users/roles.d.ts +0 -14
  51. package/src/components/SchemaVisualizer/index.ts +0 -5
  52. package/src/utils/entities.ts +0 -2
@@ -1,4 +1,4 @@
1
- import { Role, User } from "../users";
1
+ import type { User } from "../users";
2
2
  /**
3
3
  * Result of creating a new user via admin flow.
4
4
  * Contains the created user plus information about how credentials were delivered.
@@ -15,56 +15,46 @@ export interface UserCreationResult<USER extends User = User> {
15
15
  temporaryPassword?: string;
16
16
  }
17
17
  /**
18
- * Delegate to manage users, roles, and their permissions.
19
- * This interface allows the CMS to be completely agnostic of the underlying
20
- * authentication provider or backend.
18
+ * Delegate to manage auth-specific user operations.
19
+ *
20
+ * This interface allows the CMS to be agnostic of the underlying
21
+ * authentication provider or backend. User/role CRUD is now handled
22
+ * by the collection system; this delegate only exposes auth-specific
23
+ * operations (password hashing, invitations, bootstrap).
21
24
  *
22
25
  * @group Models
23
26
  */
24
27
  export interface UserManagementDelegate<USER extends User = User> {
25
28
  /**
26
- * Are the users and roles currently being fetched?
29
+ * Are auth-related operations currently loading?
27
30
  */
28
31
  loading: boolean;
29
32
  /**
30
- * List of users managed by the CMS.
33
+ * In-memory list of users (used for client-side filtering fallback).
31
34
  */
32
- users: USER[];
35
+ users?: USER[];
33
36
  /**
34
- * Optional error if users failed to load.
37
+ * Error from fetching the users list, if any.
35
38
  */
36
39
  usersError?: Error;
37
40
  /**
38
- * Function to get a user by its uid. This is used to show
39
- * user information when assigning ownership of an entity.
40
- * @param uid
41
+ * Look up a single user by UID from the in-memory cache.
41
42
  */
42
- getUser: (uid: string) => USER | null;
43
+ getUser?: (uid: string) => USER | null;
43
44
  /**
44
- * Search users with server-side pagination.
45
- * When provided, the CMS will use this for the users table
46
- * instead of loading all users into memory.
45
+ * Server-side user search with pagination.
47
46
  */
48
- searchUsers?: (options: {
47
+ searchUsers?: (params: {
49
48
  search?: string;
50
49
  limit?: number;
51
50
  offset?: number;
52
- orderBy?: string;
53
- orderDir?: "asc" | "desc";
54
- roleId?: string;
55
51
  }) => Promise<{
56
52
  users: USER[];
57
53
  total: number;
58
54
  }>;
59
- /**
60
- * Save a user (create or update)
61
- * @param user
62
- */
63
- saveUser?: (user: USER) => Promise<USER>;
64
55
  /**
65
56
  * Create a new user with invitation/password generation support.
66
57
  * Returns additional info about how the credentials were delivered.
67
- * Falls back to saveUser if not provided.
68
58
  */
69
59
  createUser?: (user: USER) => Promise<UserCreationResult<USER>>;
70
60
  /**
@@ -73,42 +63,15 @@ export interface UserManagementDelegate<USER extends User = User> {
73
63
  * or a flag indicating an email invitation was sent.
74
64
  */
75
65
  resetPassword?: (user: USER) => Promise<UserCreationResult<USER>>;
76
- /**
77
- * Delete a user
78
- * @param user
79
- */
80
- deleteUser?: (user: USER) => Promise<void>;
81
- /**
82
- * List of roles defined in the CMS.
83
- */
84
- roles?: Role[];
85
- /**
86
- * Optional error if roles failed to load.
87
- */
88
- rolesError?: Error;
89
- /**
90
- * Save a role (create or update)
91
- * @param role
92
- */
93
- saveRole?: (role: Role) => Promise<void>;
94
- /**
95
- * Delete a role
96
- * @param role
97
- */
98
- deleteRole?: (role: Role) => Promise<void>;
99
66
  /**
100
67
  * Is the currently logged in user an admin?
101
68
  */
102
69
  isAdmin?: boolean;
103
- /**
104
- * If true, the UI will allow the user to create the default roles (admin, editor, viewer).
105
- */
106
- allowDefaultRolesCreation?: boolean;
107
70
  /**
108
71
  * Optionally define roles for a given user. This is useful when the roles
109
72
  * are coming from a separate provider than the one issuing the tokens.
110
73
  */
111
- defineRolesFor?: (user: USER) => Promise<Role[] | undefined> | Role[] | undefined;
74
+ defineRolesFor?: (user: USER) => Promise<string[] | undefined> | string[] | undefined;
112
75
  /**
113
76
  * Whether any admin users exist. Used by the bootstrap banner to decide
114
77
  * whether to prompt. Populated via a lightweight check (e.g. `limit=1`
@@ -1,2 +1 @@
1
1
  export * from "./user";
2
- export * from "./roles";
@@ -35,7 +35,6 @@ export type User = {
35
35
  readonly isAnonymous: boolean;
36
36
  /**
37
37
  * Role IDs assigned to this user (e.g. ["admin", "editor"]).
38
- * These are plain string IDs — use the UserManagementDelegate to look up full Role objects.
39
38
  */
40
39
  roles?: string[];
41
40
  /**
@@ -1,9 +1,8 @@
1
1
  import React from "react";
2
- type CardProps = {
2
+ declare const Card: React.ForwardRefExoticComponent<{
3
3
  children: React.ReactNode;
4
4
  style?: React.CSSProperties;
5
5
  onClick?: (e?: React.MouseEvent) => void;
6
6
  className?: string;
7
- };
8
- declare const Card: React.ForwardRefExoticComponent<CardProps & React.RefAttributes<HTMLDivElement>>;
7
+ } & React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
9
8
  export { Card };
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- export interface FilterChipProps {
2
+ export interface FilterChipProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "children"> {
3
3
  /**
4
4
  * The text label displayed on the chip.
5
5
  */
@@ -8,10 +8,6 @@ export interface FilterChipProps {
8
8
  * Whether the chip is currently in an active/selected state.
9
9
  */
10
10
  active?: boolean;
11
- /**
12
- * Callback when the chip is clicked.
13
- */
14
- onClick?: () => void;
15
11
  /**
16
12
  * Optional icon rendered before the label.
17
13
  */
@@ -21,10 +17,6 @@ export interface FilterChipProps {
21
17
  * @default "medium"
22
18
  */
23
19
  size?: "small" | "medium";
24
- /**
25
- * Additional class names.
26
- */
27
- className?: string;
28
20
  /**
29
21
  * Whether the chip is disabled.
30
22
  */
@@ -39,4 +31,4 @@ export interface FilterChipProps {
39
31
  *
40
32
  * @group Interactive components
41
33
  */
42
- export declare function FilterChip({ children, active, onClick, icon, size, className, disabled }: FilterChipProps): import("react/jsx-runtime").JSX.Element;
34
+ export declare const FilterChip: React.ForwardRefExoticComponent<FilterChipProps & React.RefAttributes<HTMLButtonElement>>;
@@ -224,6 +224,12 @@ export type OnVirtualTableColumnResizeParams = {
224
224
  key: string;
225
225
  column: VirtualTableColumn;
226
226
  };
227
+ /**
228
+ * @see Table
229
+ * @group Components
230
+ */
231
+ export type WhereFilterOp = "<" | "<=" | "==" | "!=" | ">=" | ">" | "array-contains" | "in" | "not-in" | "array-contains-any";
232
+ export type FilterValues<Key extends string> = Partial<Record<Key, [WhereFilterOp, unknown] | [WhereFilterOp, unknown][]>>;
227
233
  /**
228
234
  * @see Table
229
235
  * @group Components
@@ -233,11 +239,11 @@ export type VirtualTableSort = "asc" | "desc" | undefined;
233
239
  * @see Table
234
240
  * @group Components
235
241
  */
236
- export type VirtualTableFilterValues<Key extends string> = Partial<Record<Key, [VirtualTableWhereFilterOp, unknown]>>;
242
+ export type VirtualTableFilterValues<Key extends string> = FilterValues<Key>;
237
243
  /**
238
244
  * Filter conditions in a `Query.where()` clause are specified using the
239
245
  * strings `<`, `<=`, `==`, `>=`, `>`, `array-contains`, `in`, and `array-contains-any`.
240
246
  * @see Table
241
247
  * @group Models
242
248
  */
243
- export type VirtualTableWhereFilterOp = "<" | "<=" | "==" | "!=" | ">=" | ">" | "array-contains" | "in" | "not-in" | "array-contains-any";
249
+ export type VirtualTableWhereFilterOp = WhereFilterOp;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/studio",
3
3
  "type": "module",
4
- "version": "0.3.0",
4
+ "version": "0.5.0",
5
5
  "main": "./dist/index.umd.js",
6
6
  "module": "./dist/index.es.js",
7
7
  "types": "dist/studio/src/index.d.ts",
@@ -15,19 +15,19 @@
15
15
  "pgsql-ast-parser": "12.0.2",
16
16
  "prism-react-renderer": "^2.4.1",
17
17
  "react-dropzone": "^14.4.1",
18
- "@rebasepro/client": "0.3.0",
19
- "@rebasepro/core": "0.3.0",
20
- "@rebasepro/common": "0.3.0",
21
- "@rebasepro/types": "0.3.0",
22
- "@rebasepro/ui": "0.3.0",
23
- "@rebasepro/utils": "0.3.0"
18
+ "@rebasepro/client": "0.5.0",
19
+ "@rebasepro/common": "0.5.0",
20
+ "@rebasepro/utils": "0.5.0",
21
+ "@rebasepro/core": "0.5.0",
22
+ "@rebasepro/types": "0.5.0",
23
+ "@rebasepro/ui": "0.5.0"
24
24
  },
25
25
  "peerDependencies": {
26
26
  "react": ">=19.0.0",
27
27
  "react-dom": ">=19.0.0",
28
28
  "react-router": "^7.0.0",
29
29
  "react-router-dom": "^7.0.0",
30
- "@rebasepro/admin": "0.3.0"
30
+ "@rebasepro/admin": "0.5.0"
31
31
  },
32
32
  "peerDependenciesMeta": {
33
33
  "@rebasepro/admin": {
@@ -166,7 +166,7 @@ function detectCollectionsInResult(
166
166
  for (const slug of mentionedSlugs) {
167
167
  const normalised = toSnakeCase(slug);
168
168
  const col = collections.find(c => {
169
- const tableName = (c as EntityCollection & { table?: string }).table || toSnakeCase(c.slug);
169
+ const tableName = ("table" in c ? c.table : undefined) || toSnakeCase(c.slug);
170
170
  return c.slug === slug || tableName === normalised || toSnakeCase(c.slug) === normalised;
171
171
  });
172
172
  if (col) {
@@ -129,25 +129,12 @@ interface AdminUser {
129
129
  updatedAt: string;
130
130
  }
131
131
 
132
- interface RebaseRole {
133
- id: string;
134
- name: string;
135
- isAdmin: boolean;
136
- defaultPermissions: Record<string, unknown> | null;
137
- config: Record<string, unknown> | null;
138
- }
139
-
140
132
  interface RebaseAdmin {
141
133
  listUsers(): Promise<{ users: AdminUser[] }>;
142
134
  getUser(userId: string): Promise<{ user: AdminUser }>;
143
135
  createUser(data: { email: string; displayName?: string; password?: string; roles?: string[] }): Promise<{ user: AdminUser }>;
144
136
  updateUser(userId: string, data: { email?: string; displayName?: string; password?: string; roles?: string[] }): Promise<{ user: AdminUser }>;
145
137
  deleteUser(userId: string): Promise<{ success: boolean }>;
146
- listRoles(): Promise<{ roles: RebaseRole[] }>;
147
- getRole(roleId: string): Promise<{ role: RebaseRole }>;
148
- createRole(data: { id: string; name: string; isAdmin?: boolean; defaultPermissions?: Record<string, unknown>; config?: Record<string, unknown> }): Promise<{ role: RebaseRole }>;
149
- updateRole(roleId: string, data: { name?: string; isAdmin?: boolean; defaultPermissions?: Record<string, unknown>; config?: Record<string, unknown> }): Promise<{ role: RebaseRole }>;
150
- deleteRole(roleId: string): Promise<{ success: boolean }>;
151
138
  bootstrap(): Promise<{ success: boolean; message: string; user: { uid: string; roles: string[] } }>;
152
139
  }
153
140
 
@@ -50,7 +50,7 @@ export interface PostgresPolicy {
50
50
  status?: "live" | "code_only" | "both";
51
51
  }
52
52
 
53
- export interface TableRLSStatus {
53
+ interface TableRLSStatus {
54
54
  schemaName: string;
55
55
  tableName: string;
56
56
  rlsEnabled: boolean;
@@ -4,9 +4,9 @@ import type { Node, Edge } from "@xyflow/react";
4
4
  // ─── Layout Constants ─────────────────────────────────────────────────
5
5
  export const NODE_WIDTH = 280;
6
6
  /** Header with a single line of text (junction tables or tableName === collectionName). */
7
- export const HEADER_HEIGHT_SINGLE = 33;
7
+ const HEADER_HEIGHT_SINGLE = 33;
8
8
  /** Header with two lines (name + subtitle when collectionName !== tableName). */
9
- export const HEADER_HEIGHT_DOUBLE = 47;
9
+ const HEADER_HEIGHT_DOUBLE = 47;
10
10
  const ROW_HEIGHT = 28; // height per column row
11
11
 
12
12
  /**
@@ -26,7 +26,7 @@ export const getHeaderHeight = (opts: {
26
26
  /**
27
27
  * Estimate the pixel height of a table node based on column count.
28
28
  */
29
- export const estimateNodeHeight = (columnCount: number, headerHeight: number = HEADER_HEIGHT_DOUBLE): number =>
29
+ const estimateNodeHeight = (columnCount: number, headerHeight: number = HEADER_HEIGHT_DOUBLE): number =>
30
30
  headerHeight + Math.max(columnCount, 1) * ROW_HEIGHT + 4; // +4 for bottom padding
31
31
 
32
32
  /**
@@ -46,9 +46,9 @@ const extractColumns = (collection: EntityCollection): ColumnInfo[] => {
46
46
  if (prop.type === "relation") continue; // Relations are shown as edges, not columns
47
47
 
48
48
  const isPk =
49
- ("isId" in prop && Boolean((prop as unknown as Record<string, unknown>).isId)) ||
49
+ ("isId" in prop && Boolean(prop.isId)) ||
50
50
  (!Object.values(properties).some(
51
- (p) => "isId" in p && Boolean((p as unknown as Record<string, unknown>).isId)
51
+ (p) => "isId" in p && Boolean(p.isId)
52
52
  ) &&
53
53
  propName === "id");
54
54
 
@@ -311,6 +311,7 @@ character_maximum_length: null }
311
311
 
312
312
  const prop = collection.properties!.tags as ArrayProperty;
313
313
  expect(prop.type).toBe("array");
314
+ expect(prop.columnType).toBe("text[]");
314
315
  expect(!Array.isArray(prop.of) && prop.of?.type).toBe("string");
315
316
  });
316
317
 
@@ -1,4 +1,4 @@
1
- import { EntityCollection, Property, StringProperty, NumberProperty, ArrayProperty, TableColumnInfo, TableMetadata } from "@rebasepro/types";
1
+ import type { EntityCollection, Property, StringProperty, NumberProperty, ArrayProperty, TableColumnInfo, TableMetadata, SecurityOperation, SecurityRule } from "@rebasepro/types";
2
2
 
3
3
  /**
4
4
  * Maps a PostgreSQL column data type to a Rebase property type.
@@ -159,12 +159,29 @@ label: v.replace(/_/g, " ").replace(/\b\w/g, (c: string) => c.toUpperCase()) }))
159
159
  };
160
160
 
161
161
  case "array":
162
- case "ARRAY":
162
+ case "ARRAY": {
163
+ let innerType = "string";
164
+ let colType: ArrayProperty["columnType"] = undefined;
165
+ if (udt_name === "_text" || udt_name === "_varchar") {
166
+ innerType = "string";
167
+ colType = "text[]";
168
+ } else if (udt_name === "_int4" || udt_name === "_int2" || udt_name === "_int8") {
169
+ innerType = "number";
170
+ colType = "integer[]";
171
+ } else if (udt_name === "_bool") {
172
+ innerType = "boolean";
173
+ colType = "boolean[]";
174
+ } else if (udt_name === "_numeric") {
175
+ innerType = "number";
176
+ colType = "numeric[]";
177
+ }
163
178
  return {
164
179
  type: "array",
165
180
  name: prettifiedName,
166
- of: { type: "string" }
181
+ columnType: colType,
182
+ of: { type: innerType }
167
183
  } as ArrayProperty;
184
+ }
168
185
 
169
186
  default:
170
187
  // Fallback: treat unknown types as string
@@ -195,13 +212,7 @@ export function buildCollectionFromTableMetadata(
195
212
  localKey?: string;
196
213
  through?: { table: string; sourceColumn: string; targetColumn: string };
197
214
  }> = [];
198
- const securityRules: Array<{
199
- name: string;
200
- operations: string[];
201
- roles: string[];
202
- qual: string | null | undefined;
203
- with_check: string | null | undefined;
204
- }> = [];
215
+ const securityRules: SecurityRule[] = [];
205
216
 
206
217
  // Parse columns
207
218
  for (const column of metadata.columns) {
@@ -254,22 +265,31 @@ export function buildCollectionFromTableMetadata(
254
265
  for (const policy of metadata.policies) {
255
266
  // Attempt to map typical cmds to operations.
256
267
  // Postgres cmd: SELECT, INSERT, UPDATE, DELETE, ALL
257
- let operations: string[] = [];
268
+ let operations: SecurityOperation[] = [];
258
269
  switch (policy.cmd) {
259
- case "ALL": operations = ["read", "create", "update", "delete"]; break;
260
- case "SELECT": operations = ["read"]; break;
261
- case "INSERT": operations = ["create"]; break;
270
+ case "ALL": operations = ["all"]; break;
271
+ case "SELECT": operations = ["select"]; break;
272
+ case "INSERT": operations = ["insert"]; break;
262
273
  case "UPDATE": operations = ["update"]; break;
263
274
  case "DELETE": operations = ["delete"]; break;
264
275
  }
265
- securityRules.push({
266
- name: policy.policy_name,
267
- operations,
268
- // roles is string[] e.g., ["public", "authenticated"]
269
- roles: policy.roles ?? [],
270
- qual: policy.qual,
271
- with_check: policy.with_check
272
- });
276
+ const qual = policy.qual ?? undefined;
277
+ const withCheck = policy.with_check ?? undefined;
278
+ if (qual) {
279
+ securityRules.push({
280
+ name: policy.policy_name,
281
+ operations,
282
+ roles: policy.roles ?? [],
283
+ using: qual,
284
+ ...(withCheck ? { withCheck } : {})
285
+ });
286
+ } else {
287
+ securityRules.push({
288
+ name: policy.policy_name,
289
+ operations,
290
+ roles: policy.roles ?? [],
291
+ });
292
+ }
273
293
  }
274
294
  }
275
295
 
@@ -105,7 +105,7 @@ export function resolveQueryCollections(
105
105
  for (const table of tables) {
106
106
  // Match table name against collection table or slug->snake_case
107
107
  const matched = collections.find(c => {
108
- const tableName = (c as EntityCollection & { table?: string }).table || toSnakeCase(c.slug);
108
+ const tableName = ("table" in c ? c.table : undefined) || toSnakeCase(c.slug);
109
109
  return tableName === table.name;
110
110
  });
111
111