@rebasepro/studio 0.4.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 (30) hide show
  1. package/README.md +73 -140
  2. package/dist/{JSEditor-BCSoElPg.js → JSEditor-DfwRLBZg.js} +2 -2
  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/util/permissions.d.ts +14 -6
  9. package/dist/index.es.js +2 -2
  10. package/dist/index.umd.js +2 -2
  11. package/dist/index.umd.js.map +1 -1
  12. package/dist/studio/src/components/RLSEditor/RLSEditor.d.ts +0 -6
  13. package/dist/studio/src/components/SchemaVisualizer/schema-visualizer.utils.d.ts +0 -8
  14. package/dist/studio/src/utils/pgColumnToProperty.d.ts +1 -1
  15. package/dist/types/src/types/backend.d.ts +36 -1
  16. package/dist/types/src/types/collections.d.ts +21 -1
  17. package/dist/types/src/types/properties.d.ts +0 -8
  18. package/package.json +8 -8
  19. package/src/components/JSEditor/JSEditor.tsx +1 -1
  20. package/src/components/RLSEditor/RLSEditor.tsx +1 -1
  21. package/src/components/SchemaVisualizer/schema-visualizer.utils.ts +3 -3
  22. package/src/components/SchemaVisualizer/useSchemaGraph.ts +2 -2
  23. package/src/utils/pgColumnToProperty.ts +23 -20
  24. package/src/utils/sql_utils.ts +1 -1
  25. package/dist/JSEditor-BCSoElPg.js.map +0 -1
  26. package/dist/SQLEditor-BC0IOUQu.js.map +0 -1
  27. package/dist/studio/src/components/SchemaVisualizer/index.d.ts +0 -5
  28. package/dist/studio/src/utils/entities.d.ts +0 -0
  29. package/src/components/SchemaVisualizer/index.ts +0 -5
  30. package/src/utils/entities.ts +0 -2
@@ -8,12 +8,6 @@ export interface PostgresPolicy {
8
8
  with_check: string | null;
9
9
  status?: "live" | "code_only" | "both";
10
10
  }
11
- export interface TableRLSStatus {
12
- schemaName: string;
13
- tableName: string;
14
- rlsEnabled: boolean;
15
- policies: PostgresPolicy[];
16
- }
17
11
  export declare const RLSEditor: ({ apiUrl }: {
18
12
  apiUrl?: string;
19
13
  }) => import("react/jsx-runtime").JSX.Element;
@@ -1,9 +1,5 @@
1
1
  import type { Node, Edge } from "@xyflow/react";
2
2
  export declare const NODE_WIDTH = 280;
3
- /** Header with a single line of text (junction tables or tableName === collectionName). */
4
- export declare const HEADER_HEIGHT_SINGLE = 33;
5
- /** Header with two lines (name + subtitle when collectionName !== tableName). */
6
- export declare const HEADER_HEIGHT_DOUBLE = 47;
7
3
  /**
8
4
  * Compute the correct header height for a table node.
9
5
  */
@@ -12,10 +8,6 @@ export declare const getHeaderHeight: (opts: {
12
8
  collectionName: string;
13
9
  tableName: string;
14
10
  }) => number;
15
- /**
16
- * Estimate the pixel height of a table node based on column count.
17
- */
18
- export declare const estimateNodeHeight: (columnCount: number, headerHeight?: number) => number;
19
11
  /**
20
12
  * Get the vertical center Y of a specific column row (0-indexed)
21
13
  * relative to the top of the node.
@@ -1,4 +1,4 @@
1
- import { EntityCollection, TableMetadata } from "@rebasepro/types";
1
+ import type { EntityCollection, TableMetadata } from "@rebasepro/types";
2
2
  /**
3
3
  * Builds a partial EntityCollection from PostgreSQL table metadata.
4
4
  * This is used when creating a new collection from an existing database table.
@@ -1,5 +1,6 @@
1
1
  import type { Entity } from "./entities";
2
2
  import type { EntityCollection, FilterValues, WhereFilterOp } from "./collections";
3
+ import type { AuthAdapter } from "./auth_adapter";
3
4
  /**
4
5
  * Abstract database connection interface.
5
6
  * Represents a connection to any database system.
@@ -182,6 +183,24 @@ export interface RealtimeProvider {
182
183
  * Notify all relevant subscribers of an entity update
183
184
  */
184
185
  notifyEntityUpdate(path: string, entityId: string, entity: Entity | null, databaseId?: string): Promise<void>;
186
+ /**
187
+ * Called when the HTTP server is ready and listening.
188
+ * Useful for providers that need the server address for callbacks.
189
+ */
190
+ onServerReady?(serverInfo: {
191
+ port: number;
192
+ hostname?: string;
193
+ }): void;
194
+ /**
195
+ * Gracefully shut down the realtime provider.
196
+ * Called during server shutdown to clean up resources.
197
+ */
198
+ destroy?(): Promise<void>;
199
+ /**
200
+ * Stop the internal LISTEN client (e.g., PostgreSQL LISTEN/NOTIFY).
201
+ * Called during graceful shutdown before closing database connections.
202
+ */
203
+ stopListening?(): Promise<void>;
185
204
  }
186
205
  /**
187
206
  * Abstract collection registry interface.
@@ -464,6 +483,22 @@ export interface BackendBootstrapper {
464
483
  * (e.g., `"postgres"`, `"mongodb"`, `"mysql"`).
465
484
  */
466
485
  type: string;
486
+ /**
487
+ * Unique identifier for this bootstrapper instance.
488
+ * Used to register the driver in the driver registry.
489
+ * Defaults to `type` if not set.
490
+ */
491
+ id?: string;
492
+ /**
493
+ * Whether this bootstrapper provides the default driver.
494
+ * When true, the coordinator uses this driver as the primary one.
495
+ */
496
+ isDefault?: boolean;
497
+ /**
498
+ * Run database migrations for this driver.
499
+ * Called by the coordinator after all drivers are initialized.
500
+ */
501
+ runMigrations?(config: unknown, driverResult: InitializedDriver): Promise<void>;
467
502
  /**
468
503
  * Create a DataDriver from the given config.
469
504
  * This is the only **required** method.
@@ -498,7 +533,7 @@ export interface BackendBootstrapper {
498
533
  /**
499
534
  * Initialize WebSocket server for realtime operations.
500
535
  */
501
- initializeWebsockets?(server: unknown, realtimeService: RealtimeProvider, driver: import("../controllers/data_driver").DataDriver, config?: unknown): Promise<void> | void;
536
+ initializeWebsockets?(server: unknown, realtimeService: RealtimeProvider, driver: import("../controllers/data_driver").DataDriver, config?: unknown, authAdapter?: AuthAdapter): Promise<void> | void;
502
537
  }
503
538
  /**
504
539
  * Result of `BackendBootstrapper.initializeDriver()`.
@@ -343,6 +343,23 @@ export interface BaseEntityCollection<M extends Record<string, unknown> = Record
343
343
  * Builder for the collection actions rendered in the toolbar
344
344
  */
345
345
  Actions?: ComponentRef<CollectionActionsProps>[];
346
+ /**
347
+ * The database table name for this collection.
348
+ * Automatically set for PostgreSQL collections.
349
+ * For non-SQL backends, this may be undefined.
350
+ */
351
+ table?: string;
352
+ /**
353
+ * Relations defined for this collection.
354
+ * Populated at normalization time from inline relation properties
355
+ * or explicit relation definitions.
356
+ */
357
+ relations?: Relation[];
358
+ /**
359
+ * Security rules for this collection (Row Level Security).
360
+ * When defined, the backend enforces access control policies.
361
+ */
362
+ securityRules?: SecurityRule[];
346
363
  }
347
364
  /**
348
365
  * A collection backed by PostgreSQL (or any SQL database).
@@ -436,7 +453,10 @@ export interface MongoDBCollection<M extends Record<string, unknown> = Record<st
436
453
  * @group Models
437
454
  */
438
455
  export type EntityCollection<M extends Record<string, unknown> = Record<string, unknown>, USER extends User = User> = PostgresCollection<M, USER> | FirebaseCollection<M, USER> | MongoDBCollection<M, USER>;
439
- /** An EntityCollection that supports SQL-style relations (e.g. Postgres). */
456
+ /**
457
+ * An EntityCollection that supports SQL-style relations (e.g. Postgres).
458
+ * @deprecated Use `EntityCollection` directly — `table`, `relations`, and `securityRules` are now on `BaseEntityCollection`.
459
+ */
440
460
  export type CollectionWithRelations<M extends Record<string, unknown> = Record<string, unknown>> = EntityCollection<M> & {
441
461
  table?: string;
442
462
  relations?: Relation[];
@@ -641,14 +641,6 @@ export interface MapProperty extends BaseProperty {
641
641
  * Properties that are displayed when rendered as a preview
642
642
  */
643
643
  previewProperties?: string[];
644
- /**
645
- * Allow the user to add only some keys in this map.
646
- * By default, all properties of the map have the corresponding field in
647
- * the form view. Setting this flag to true allows to pick only some.
648
- * Useful for map that can have a lot of sub-properties that may not be
649
- * needed
650
- */
651
- pickOnlySomeKeys?: boolean;
652
644
  /**
653
645
  * Render this map as a key-value table that allows to use
654
646
  * arbitrary keys. You don't need to define the properties in this case.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/studio",
3
3
  "type": "module",
4
- "version": "0.4.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.4.0",
19
- "@rebasepro/common": "0.4.0",
20
- "@rebasepro/core": "0.4.0",
21
- "@rebasepro/types": "0.4.0",
22
- "@rebasepro/ui": "0.4.0",
23
- "@rebasepro/utils": "0.4.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.4.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) {
@@ -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
 
@@ -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.
@@ -212,13 +212,7 @@ export function buildCollectionFromTableMetadata(
212
212
  localKey?: string;
213
213
  through?: { table: string; sourceColumn: string; targetColumn: string };
214
214
  }> = [];
215
- const securityRules: Array<{
216
- name: string;
217
- operations: string[];
218
- roles: string[];
219
- qual: string | null | undefined;
220
- with_check: string | null | undefined;
221
- }> = [];
215
+ const securityRules: SecurityRule[] = [];
222
216
 
223
217
  // Parse columns
224
218
  for (const column of metadata.columns) {
@@ -271,22 +265,31 @@ export function buildCollectionFromTableMetadata(
271
265
  for (const policy of metadata.policies) {
272
266
  // Attempt to map typical cmds to operations.
273
267
  // Postgres cmd: SELECT, INSERT, UPDATE, DELETE, ALL
274
- let operations: string[] = [];
268
+ let operations: SecurityOperation[] = [];
275
269
  switch (policy.cmd) {
276
- case "ALL": operations = ["read", "create", "update", "delete"]; break;
277
- case "SELECT": operations = ["read"]; break;
278
- case "INSERT": operations = ["create"]; break;
270
+ case "ALL": operations = ["all"]; break;
271
+ case "SELECT": operations = ["select"]; break;
272
+ case "INSERT": operations = ["insert"]; break;
279
273
  case "UPDATE": operations = ["update"]; break;
280
274
  case "DELETE": operations = ["delete"]; break;
281
275
  }
282
- securityRules.push({
283
- name: policy.policy_name,
284
- operations,
285
- // roles is string[] e.g., ["public", "authenticated"]
286
- roles: policy.roles ?? [],
287
- qual: policy.qual,
288
- with_check: policy.with_check
289
- });
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
+ }
290
293
  }
291
294
  }
292
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