@rebasepro/studio 0.2.1 → 0.2.4

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 (83) hide show
  1. package/dist/{ApiExplorer-BmcdhAX0.js → ApiExplorer-CGHEF1uL.js} +4 -4
  2. package/dist/ApiExplorer-CGHEF1uL.js.map +1 -0
  3. package/dist/{CronJobsView-CNfz0etw.js → CronJobsView-3PM_qR8v.js} +20 -3
  4. package/dist/CronJobsView-3PM_qR8v.js.map +1 -0
  5. package/dist/{JSEditor-Ch8z8lJ4.js → JSEditor-Br4ke-J4.js} +30 -27
  6. package/dist/JSEditor-Br4ke-J4.js.map +1 -0
  7. package/dist/LogsExplorer-_4sZadKn.js +162 -0
  8. package/dist/LogsExplorer-_4sZadKn.js.map +1 -0
  9. package/dist/{SQLEditor-BELYJQRP.js → SQLEditor-BC0IOUQu.js} +4 -4
  10. package/dist/SQLEditor-BC0IOUQu.js.map +1 -0
  11. package/dist/common/src/collections/default-collections.d.ts +12 -0
  12. package/dist/common/src/collections/index.d.ts +1 -0
  13. package/dist/common/src/data/query_builder.d.ts +51 -0
  14. package/dist/common/src/index.d.ts +1 -0
  15. package/dist/common/src/util/permissions.d.ts +1 -0
  16. package/dist/core/src/components/LoginView/LoginView.d.ts +17 -1
  17. package/dist/core/src/components/common/types.d.ts +10 -7
  18. package/dist/core/src/components/common/useDebouncedData.d.ts +1 -1
  19. package/dist/core/src/core/RebaseProps.d.ts +13 -2
  20. package/dist/core/src/core/RebaseRouter.d.ts +1 -1
  21. package/dist/core/src/hooks/index.d.ts +0 -1
  22. package/dist/core/src/util/entity_cache.d.ts +0 -5
  23. package/dist/core/src/util/index.d.ts +0 -2
  24. package/dist/core/src/util/useStorageUploadController.d.ts +2 -2
  25. package/dist/formex/src/utils.d.ts +2 -2
  26. package/dist/index.es.js +23 -5
  27. package/dist/index.es.js.map +1 -1
  28. package/dist/index.umd.js +232 -35
  29. package/dist/index.umd.js.map +1 -1
  30. package/dist/studio/src/components/ApiExplorer/parseSpec.d.ts +1 -1
  31. package/dist/studio/src/components/ApiExplorer/types.d.ts +3 -3
  32. package/dist/studio/src/components/LogsExplorer/LogsExplorer.d.ts +1 -0
  33. package/dist/types/src/controllers/auth.d.ts +2 -24
  34. package/dist/types/src/controllers/client.d.ts +0 -3
  35. package/dist/types/src/controllers/collection_registry.d.ts +1 -1
  36. package/dist/types/src/controllers/data.d.ts +21 -0
  37. package/dist/types/src/controllers/data_driver.d.ts +18 -0
  38. package/dist/types/src/controllers/registry.d.ts +5 -4
  39. package/dist/types/src/rebase_context.d.ts +1 -1
  40. package/dist/types/src/types/auth_adapter.d.ts +2 -4
  41. package/dist/types/src/types/collections.d.ts +0 -4
  42. package/dist/types/src/types/component_ref.d.ts +1 -1
  43. package/dist/types/src/types/cron.d.ts +1 -1
  44. package/dist/types/src/types/entity_views.d.ts +1 -0
  45. package/dist/types/src/types/export_import.d.ts +1 -1
  46. package/dist/types/src/types/formex.d.ts +2 -2
  47. package/dist/types/src/types/properties.d.ts +2 -2
  48. package/dist/types/src/types/translations.d.ts +28 -12
  49. package/dist/types/src/types/user_management_delegate.d.ts +6 -4
  50. package/dist/types/src/users/roles.d.ts +0 -8
  51. package/dist/ui/src/components/Button.d.ts +2 -2
  52. package/dist/ui/src/components/ErrorBoundary.d.ts +25 -3
  53. package/dist/ui/src/components/VirtualTable/VirtualTable.d.ts +1 -1
  54. package/dist/ui/src/components/VirtualTable/VirtualTableCell.d.ts +6 -6
  55. package/dist/ui/src/components/VirtualTable/VirtualTableHeader.d.ts +8 -8
  56. package/dist/ui/src/components/VirtualTable/VirtualTableHeaderRow.d.ts +1 -1
  57. package/dist/ui/src/components/VirtualTable/VirtualTableProps.d.ts +11 -11
  58. package/dist/ui/src/components/VirtualTable/VirtualTableRow.d.ts +1 -1
  59. package/dist/ui/src/components/VirtualTable/types.d.ts +9 -9
  60. package/dist/ui/src/hooks/useDebounceCallback.d.ts +1 -1
  61. package/dist/ui/src/util/debounce.d.ts +1 -1
  62. package/package.json +13 -8
  63. package/src/components/ApiExplorer/ApiExplorer.tsx +2 -2
  64. package/src/components/ApiExplorer/EndpointDetail.tsx +1 -1
  65. package/src/components/ApiExplorer/TryItPanel.tsx +5 -5
  66. package/src/components/ApiExplorer/parseSpec.ts +3 -3
  67. package/src/components/ApiExplorer/types.ts +3 -3
  68. package/src/components/CronJobs/CronJobsView.tsx +27 -2
  69. package/src/components/JSEditor/JSEditor.tsx +21 -18
  70. package/src/components/JSEditor/JSMonacoEditor.tsx +10 -10
  71. package/src/components/LogsExplorer/LogsExplorer.tsx +224 -0
  72. package/src/components/RebaseStudio.tsx +10 -1
  73. package/src/components/SQLEditor/SQLEditor.tsx +28 -7
  74. package/src/components/StudioHomePage.tsx +2 -1
  75. package/src/utils/parseSpec.test.ts +274 -0
  76. package/src/utils/pgColumnToProperty.ts +16 -2
  77. package/dist/ApiExplorer-BmcdhAX0.js.map +0 -1
  78. package/dist/CronJobsView-CNfz0etw.js.map +0 -1
  79. package/dist/JSEditor-Ch8z8lJ4.js.map +0 -1
  80. package/dist/SQLEditor-BELYJQRP.js.map +0 -1
  81. package/dist/core/src/hooks/useValidateAuthenticator.d.ts +0 -21
  82. package/dist/core/src/util/icon_synonyms.d.ts +0 -1
  83. package/dist/core/src/util/useTraceUpdate.d.ts +0 -2
@@ -1,12 +1,12 @@
1
1
  import React from "react";
2
2
  import { CellRendererParams, OnRowClickParams, OnVirtualTableColumnResizeParams, VirtualTableColumn, VirtualTableFilterValues, VirtualTableWhereFilterOp } from "./VirtualTableProps";
3
3
  import { FilterFormFieldProps } from "./VirtualTableHeader";
4
- export type VirtualTableRowProps<T> = {
5
- style: any;
4
+ export type VirtualTableRowProps<T extends Record<string, unknown>> = {
5
+ style: React.CSSProperties;
6
6
  rowHeight: number;
7
7
  rowData: T;
8
8
  rowIndex: number;
9
- onRowClick?: (props: OnRowClickParams<any>) => void;
9
+ onRowClick?: (props: OnRowClickParams<Record<string, unknown>>) => void;
10
10
  children: React.ReactNode[];
11
11
  columns: VirtualTableColumn[];
12
12
  hoverRow?: boolean;
@@ -19,20 +19,20 @@ export type VirtualTableContextProps<T> = {
19
19
  columns: VirtualTableColumn[];
20
20
  cellRenderer: React.ComponentType<CellRendererParams<T>>;
21
21
  currentSort: "asc" | "desc" | undefined;
22
- filter?: VirtualTableFilterValues<any>;
23
- onRowClick?: (props: OnRowClickParams<any>) => void;
24
- onColumnSort: (key: string) => any;
22
+ filter?: VirtualTableFilterValues<string>;
23
+ onRowClick?: (props: OnRowClickParams<Record<string, unknown>>) => void;
24
+ onColumnSort: (key: string) => void;
25
25
  onColumnResize: (params: OnVirtualTableColumnResizeParams) => void;
26
26
  onColumnResizeEnd: (params: OnVirtualTableColumnResizeParams) => void;
27
- onFilterUpdate: (column: VirtualTableColumn, filterForProperty?: [VirtualTableWhereFilterOp, any]) => void;
27
+ onFilterUpdate: (column: VirtualTableColumn, filterForProperty?: [VirtualTableWhereFilterOp, unknown]) => void;
28
28
  sortByProperty?: string;
29
29
  customView?: React.ReactNode;
30
30
  hoverRow: boolean;
31
- createFilterField?: (props: FilterFormFieldProps<any>) => React.ReactNode;
31
+ createFilterField?: (props: FilterFormFieldProps<unknown>) => React.ReactNode;
32
32
  rowClassName?: (rowData: T) => string | undefined;
33
33
  endAdornment?: React.ReactNode;
34
34
  AddColumnComponent?: React.ComponentType;
35
35
  onColumnsOrderChange?: (columns: VirtualTableColumn[]) => void;
36
36
  draggingColumnId?: string | null;
37
- extraData?: Record<string, any>;
37
+ extraData?: unknown;
38
38
  };
@@ -1 +1 @@
1
- export declare function useDebounceCallback<T extends (...args: any[]) => any>(callback?: T, delay?: number): T;
1
+ export declare function useDebounceCallback<T extends (...args: any[]) => unknown>(callback?: T, delay?: number): T;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @ignore
3
3
  */
4
- export declare function debounce<T extends (...args: any[]) => any>(func: T, wait?: number): T & Cancelable;
4
+ export declare function debounce<T extends (...args: any[]) => unknown>(func: T, wait?: number): T & Cancelable;
5
5
  /**
6
6
  * @ignore
7
7
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/studio",
3
3
  "type": "module",
4
- "version": "0.2.1",
4
+ "version": "0.2.4",
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/common": "0.2.1",
19
- "@rebasepro/core": "0.2.1",
20
- "@rebasepro/types": "0.2.1",
21
- "@rebasepro/utils": "0.2.1",
22
- "@rebasepro/ui": "0.2.1",
23
- "@rebasepro/client": "0.2.1"
18
+ "@rebasepro/common": "0.2.4",
19
+ "@rebasepro/client": "0.2.4",
20
+ "@rebasepro/core": "0.2.4",
21
+ "@rebasepro/ui": "0.2.4",
22
+ "@rebasepro/utils": "0.2.4",
23
+ "@rebasepro/types": "0.2.4"
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.2.1"
30
+ "@rebasepro/admin": "0.2.4"
31
31
  },
32
32
  "peerDependenciesMeta": {
33
33
  "@rebasepro/admin": {
@@ -78,6 +78,11 @@
78
78
  "access": "public"
79
79
  },
80
80
  "gitHead": "9ecf37abf793bd2f2daaaed6f517ee5ee19b01ae",
81
+ "repository": {
82
+ "type": "git",
83
+ "url": "https://github.com/rebasepro/rebase.git",
84
+ "directory": "packages/studio"
85
+ },
81
86
  "scripts": {
82
87
  "dev": "vite",
83
88
  "test": "jest --passWithNoTests",
@@ -50,9 +50,9 @@ export function ApiExplorer() {
50
50
  setSpec(data);
51
51
  setLoading(false);
52
52
  }
53
- } catch (err: any) {
53
+ } catch (err: unknown) {
54
54
  if (!cancelled) {
55
- setError(err.message ?? "Failed to load API spec");
55
+ setError(err instanceof Error ? err.message : "Failed to load API spec");
56
56
  setLoading(false);
57
57
  }
58
58
  }
@@ -153,7 +153,7 @@ function SchemaBlock({ schema, spec, depth }: { schema: OpenApiSchema; spec: Ope
153
153
  // Resolve $ref
154
154
  if (schema.$ref) {
155
155
  const name = resolveRefName(schema.$ref);
156
- const resolved = resolveRef(spec, schema.$ref);
156
+ const resolved = resolveRef(spec, schema.$ref) as OpenApiSchema;
157
157
  return (
158
158
  <div>
159
159
  <Typography
@@ -125,8 +125,8 @@ export function TryItPanel({ endpoint, apiUrl, getAuthToken, user, basePath = ""
125
125
  if (hasBody && body.trim()) {
126
126
  try {
127
127
  JSON.parse(body);
128
- } catch (err: any) {
129
- setValidationError(`Invalid JSON: ${err.message}`);
128
+ } catch (err: unknown) {
129
+ setValidationError(`Invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
130
130
  return;
131
131
  }
132
132
  }
@@ -171,11 +171,11 @@ export function TryItPanel({ endpoint, apiUrl, getAuthToken, user, basePath = ""
171
171
  statusText: res.statusText,
172
172
  body: text,
173
173
  time: elapsed });
174
- } catch (err: any) {
174
+ } catch (err: unknown) {
175
175
  setResponse({
176
176
  status: 0,
177
177
  statusText: "Network Error",
178
- body: err.message ?? "Request failed",
178
+ body: err instanceof Error ? err.message : "Request failed",
179
179
  time: Math.round(performance.now() - start)
180
180
  });
181
181
  } finally {
@@ -479,7 +479,7 @@ function buildBodyTemplate(endpoint: ParsedEndpoint): string {
479
479
  return lines.join("\n");
480
480
  }
481
481
 
482
- function defaultValue(schema: any): string {
482
+ function defaultValue(schema: { type?: string; format?: string; enum?: (string | number)[] }): string {
483
483
  if (schema.enum) return JSON.stringify(schema.enum[0]);
484
484
  switch (schema.type) {
485
485
  case "string":
@@ -94,11 +94,11 @@ export function resolveRefName(ref: string): string {
94
94
  /**
95
95
  * Resolve a $ref to its actual schema from the spec.
96
96
  */
97
- export function resolveRef(spec: OpenApiSpec, ref: string): any {
97
+ export function resolveRef(spec: OpenApiSpec, ref: string): unknown {
98
98
  const parts = ref.replace("#/", "").split("/");
99
- let current: any = spec;
99
+ let current: unknown = spec;
100
100
  for (const part of parts) {
101
- current = current?.[part];
101
+ current = (current as Record<string, unknown>)?.[part];
102
102
  }
103
103
  return current ?? {};
104
104
  }
@@ -6,7 +6,7 @@ export interface OpenApiSpec {
6
6
  paths: Record<string, Record<string, OpenApiOperation>>;
7
7
  components?: {
8
8
  schemas?: Record<string, OpenApiSchema>;
9
- securitySchemes?: Record<string, any>;
9
+ securitySchemes?: Record<string, unknown>;
10
10
  };
11
11
  tags?: { name: string; description?: string }[];
12
12
  security?: Record<string, string[]>[];
@@ -57,8 +57,8 @@ export interface OpenApiSchema {
57
57
  maximum?: number;
58
58
  maxLength?: number;
59
59
  minLength?: number;
60
- default?: any;
61
- example?: any;
60
+ default?: unknown;
61
+ example?: unknown;
62
62
  additionalProperties?: boolean | OpenApiSchema;
63
63
  }
64
64
 
@@ -91,8 +91,33 @@ export function CronJobsView() {
91
91
  }
92
92
 
93
93
  load();
94
- const t = setInterval(load, 15_000);
95
- return () => { cancelled = true; clearInterval(t); };
94
+
95
+ let timeoutId: ReturnType<typeof setTimeout> | null = null;
96
+
97
+ const scheduleNext = () => {
98
+ if (cancelled) return;
99
+ timeoutId = setTimeout(async () => {
100
+ if (document.visibilityState === "visible") {
101
+ await load();
102
+ }
103
+ scheduleNext();
104
+ }, 15_000);
105
+ };
106
+
107
+ scheduleNext();
108
+
109
+ const handleVisibility = () => {
110
+ if (document.visibilityState === "visible") {
111
+ load();
112
+ }
113
+ };
114
+ document.addEventListener("visibilitychange", handleVisibility);
115
+
116
+ return () => {
117
+ cancelled = true;
118
+ if (timeoutId) clearTimeout(timeoutId);
119
+ document.removeEventListener("visibilitychange", handleVisibility);
120
+ };
96
121
  }, []); // runs once
97
122
 
98
123
  // ── Fetch logs when selection changes ──
@@ -47,12 +47,12 @@ import { AuthSimulationSelector } from "../AuthSimulationSelector";
47
47
 
48
48
  interface ConsoleEntry {
49
49
  type: "log" | "warn" | "error" | "info";
50
- args: any[];
50
+ args: unknown[];
51
51
  timestamp: number;
52
52
  }
53
53
 
54
54
  interface ExecutionResult {
55
- value: any;
55
+ value: unknown;
56
56
  console: ConsoleEntry[];
57
57
  duration: number;
58
58
  error?: string;
@@ -101,7 +101,7 @@ function saveToStorage<T>(key: string, value: T) {
101
101
  } catch { /* quota */ }
102
102
  }
103
103
 
104
- function formatJSON(value: any): string {
104
+ function formatJSON(value: unknown): string {
105
105
  try {
106
106
  return JSON.stringify(value, null, 2);
107
107
  } catch {
@@ -125,7 +125,7 @@ interface MatchedJSCollection {
125
125
  */
126
126
  function detectCollectionsInResult(
127
127
  code: string,
128
- resultValue: any,
128
+ resultValue: unknown,
129
129
  collections: EntityCollection[]
130
130
  ): MatchedJSCollection[] {
131
131
  if (!resultValue || !collections?.length) return [];
@@ -151,9 +151,10 @@ function detectCollectionsInResult(
151
151
  if (mentionedSlugs.size === 0) return [];
152
152
 
153
153
  // Check if result has rows with an "id" field
154
- let rows: any[] = [];
155
- if (resultValue?.data && Array.isArray(resultValue.data)) {
156
- rows = resultValue.data;
154
+ let rows: Record<string, unknown>[] = [];
155
+ const rv = resultValue as Record<string, unknown>;
156
+ if (rv?.data && Array.isArray(rv.data)) {
157
+ rows = rv.data;
157
158
  } else if (Array.isArray(resultValue)) {
158
159
  rows = resultValue;
159
160
  }
@@ -405,7 +406,7 @@ isScoped: true };
405
406
  info: console.info
406
407
  };
407
408
 
408
- const captureConsole = (type: ConsoleEntry["type"]) => (...args: any[]) => {
409
+ const captureConsole = (type: ConsoleEntry["type"]) => (...args: unknown[]) => {
409
410
  consoleEntries.push({ type,
410
411
  args,
411
412
  timestamp: Date.now() });
@@ -451,7 +452,8 @@ timestamp: Date.now() });
451
452
  });
452
453
 
453
454
  // Auto-detect best view
454
- if (value?.data && Array.isArray(value.data)) {
455
+ const resultObj = value as Record<string, unknown> | undefined;
456
+ if (resultObj?.data && Array.isArray(resultObj.data)) {
455
457
  setResultView("table");
456
458
  } else if (consoleEntries.length > 0 && value === undefined) {
457
459
  setResultView("console");
@@ -522,11 +524,12 @@ message: "Snippet saved" });
522
524
  if (!result?.value) return { columns: [] as VirtualTableColumn[],
523
525
  data: [] as Record<string, unknown>[] };
524
526
 
525
- let rows: any[] = [];
526
- if (result.value?.data && Array.isArray(result.value.data)) {
527
- rows = result.value.data.map((entity: any) => ({
527
+ let rows: Record<string, unknown>[] = [];
528
+ const val = result.value as Record<string, unknown>;
529
+ if (val?.data && Array.isArray(val.data)) {
530
+ rows = (val.data as Record<string, unknown>[]).map((entity) => ({
528
531
  id: entity.id,
529
- ...entity.values,
532
+ ...(entity.values as Record<string, unknown> ?? {}),
530
533
  ...(entity.values ? {} : entity)
531
534
  }));
532
535
  } else if (Array.isArray(result.value)) {
@@ -564,13 +567,13 @@ data: rows };
564
567
  );
565
568
  }, [result, activeTab?.code, collectionRegistry?.collections]);
566
569
 
567
- const getRowEntityActions = useCallback((rowData: any): { collection: MatchedJSCollection; entityId: string | number }[] => {
570
+ const getRowEntityActions = useCallback((rowData: Record<string, unknown>): { collection: MatchedJSCollection; entityId: string | number }[] => {
568
571
  if (!rowData || matchedCollections.length === 0) return [];
569
572
  return matchedCollections
570
573
  .filter(mc => rowData[mc.pkColumn] != null)
571
574
  .map(mc => ({
572
575
  collection: mc,
573
- entityId: rowData[mc.pkColumn]
576
+ entityId: rowData[mc.pkColumn] as string | number
574
577
  }));
575
578
  }, [matchedCollections]);
576
579
 
@@ -685,7 +688,7 @@ message: t("studio_sql_markdown_copy_failed") });
685
688
  </IconButton>
686
689
  </Tooltip>
687
690
 
688
- {result?.value && (
691
+ {result?.value != null && (
689
692
  <Tooltip title="Export result as JSON">
690
693
  <IconButton size="small" onClick={exportResult}>
691
694
  <DownloadIcon size={iconSize.smallest}/>
@@ -843,7 +846,7 @@ resizable: false }, ...tableData.columns]
843
846
  cellRenderer={({ rowData, column, rowIndex }: CellRendererParams<Record<string, unknown>>) => {
844
847
  // Entity action column
845
848
  if (column.key === "__entity_action__") {
846
- const rowActions = getRowEntityActions(rowData);
849
+ const rowActions = getRowEntityActions(rowData ?? {});
847
850
  if (rowActions.length === 0) return <div className="h-full w-full"/>;
848
851
  if (rowActions.length === 1) {
849
852
  const ra = rowActions[0];
@@ -1037,7 +1040,7 @@ id: String(ra.entityId) })}
1037
1040
 
1038
1041
  // ─── JSON Syntax Highlighting ────────────────────────────────────────
1039
1042
 
1040
- function JSONHighlight({ value }: { value: any }) {
1043
+ function JSONHighlight({ value }: { value: unknown }) {
1041
1044
  const json = formatJSON(value);
1042
1045
  const { mode } = useModeController();
1043
1046
 
@@ -111,7 +111,7 @@ interface RebaseAuth {
111
111
  changePassword(oldPassword: string, newPassword: string): Promise<{ success: boolean; message: string }>;
112
112
  sendVerificationEmail(): Promise<{ success: boolean; message: string }>;
113
113
  verifyEmail(token: string): Promise<{ success: boolean; message: string }>;
114
- getSessions(): Promise<any[]>;
114
+ getSessions(): Promise<RebaseSession[]>;
115
115
  revokeSession(sessionId: string): Promise<{ success: boolean }>;
116
116
  revokeAllSessions(): Promise<{ success: boolean }>;
117
117
  getSession(): RebaseSession | null;
@@ -133,8 +133,8 @@ interface RebaseRole {
133
133
  id: string;
134
134
  name: string;
135
135
  isAdmin: boolean;
136
- defaultPermissions: Record<string, any> | null;
137
- config: Record<string, any> | null;
136
+ defaultPermissions: Record<string, unknown> | null;
137
+ config: Record<string, unknown> | null;
138
138
  }
139
139
 
140
140
  interface RebaseAdmin {
@@ -145,8 +145,8 @@ interface RebaseAdmin {
145
145
  deleteUser(userId: string): Promise<{ success: boolean }>;
146
146
  listRoles(): Promise<{ roles: RebaseRole[] }>;
147
147
  getRole(roleId: string): Promise<{ role: RebaseRole }>;
148
- createRole(data: { id: string; name: string; isAdmin?: boolean; defaultPermissions?: any; config?: any }): Promise<{ role: RebaseRole }>;
149
- updateRole(roleId: string, data: { name?: string; isAdmin?: boolean; defaultPermissions?: any; config?: any }): 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
150
  deleteRole(roleId: string): Promise<{ success: boolean }>;
151
151
  bootstrap(): Promise<{ success: boolean; message: string; user: { uid: string; roles: string[] } }>;
152
152
  }
@@ -155,7 +155,7 @@ interface UploadFileProps {
155
155
  file: FileIcon;
156
156
  fileName?: string;
157
157
  path?: string;
158
- metadata?: Record<string, any>;
158
+ metadata?: Record<string, unknown>;
159
159
  bucket?: string;
160
160
  }
161
161
 
@@ -168,7 +168,7 @@ interface UploadFileResult {
168
168
  interface DownloadConfig {
169
169
  url: string | null;
170
170
  fileNotFound?: boolean;
171
- metadata?: any;
171
+ metadata?: Record<string, unknown>;
172
172
  }
173
173
 
174
174
  interface StorageSource {
@@ -176,7 +176,7 @@ interface StorageSource {
176
176
  getSignedUrl(pathOrUrl: string, bucket?: string): Promise<DownloadConfig>;
177
177
  getObject(path: string, bucket?: string): Promise<FileIcon | null>;
178
178
  deleteObject(path: string, bucket?: string): Promise<void>;
179
- listObjects(path: string, options?: { bucket?: string; maxResults?: number; pageToken?: string }): Promise<any>;
179
+ listObjects(path: string, options?: { bucket?: string; maxResults?: number; pageToken?: string }): Promise<unknown>;
180
180
  }
181
181
 
182
182
  type RebaseData = {
@@ -220,9 +220,9 @@ interface RebaseClient {
220
220
  /** Storage operations */
221
221
  storage?: StorageSource;
222
222
  /** Call a custom server-side endpoint */
223
- call<T = any>(endpoint: string, payload?: any): Promise<T>;
223
+ call<T = unknown>(endpoint: string, payload?: unknown): Promise<T>;
224
224
  /** Direct collection access (shorthand) */
225
- [collectionSlug: string]: any;
225
+ [collectionSlug: string]: unknown;
226
226
  }
227
227
 
228
228
  /** The pre-configured client instance. Already authenticated with the current user session. */
@@ -0,0 +1,224 @@
1
+ import React, { useState, useEffect, useRef, useCallback } from "react";
2
+ import { Select, SelectItem, TextField, Checkbox, Label } from "@rebasepro/ui";
3
+
4
+ interface LogEntry {
5
+ id: string;
6
+ timestamp: string;
7
+ level: "debug" | "info" | "warn" | "error";
8
+ source: "api" | "auth" | "storage" | "realtime" | "system";
9
+ message: string;
10
+ metadata?: Record<string, unknown>;
11
+ }
12
+
13
+ const LEVEL_COLORS: Record<string, string> = {
14
+ debug: "#6c7086",
15
+ info: "#89b4fa",
16
+ warn: "#f9e2af",
17
+ error: "#f38ba8"
18
+ };
19
+
20
+ const SOURCE_COLORS: Record<string, string> = {
21
+ api: "#74c7ec",
22
+ auth: "#cba6f7",
23
+ storage: "#a6e3a1",
24
+ realtime: "#fab387",
25
+ system: "#6c7086"
26
+ };
27
+
28
+ export function LogsExplorer() {
29
+ const [logs, setLogs] = useState<LogEntry[]>([]);
30
+ const [level, setLevel] = useState<string>("");
31
+ const [source, setSource] = useState<string>("");
32
+ const [search, setSearch] = useState("");
33
+ const [autoScroll, setAutoScroll] = useState(true);
34
+ const containerRef = useRef<HTMLDivElement>(null);
35
+ const fetchLogs = useCallback(async () => {
36
+ try {
37
+ const params = new URLSearchParams();
38
+ if (level) params.set("level", level);
39
+ if (source) params.set("source", source);
40
+ if (search) params.set("search", search);
41
+ params.set("limit", "200");
42
+
43
+ const resp = await fetch(`/api/logs?${params}`);
44
+ if (resp.ok) {
45
+ const data: { entries?: LogEntry[] } = await resp.json();
46
+ setLogs(data.entries || []);
47
+ }
48
+ } catch {
49
+ /* ignore poll failures */
50
+ }
51
+ }, [level, source, search]);
52
+
53
+ useEffect(() => {
54
+ let timeoutId: ReturnType<typeof setTimeout> | null = null;
55
+ let cancelled = false;
56
+
57
+ fetchLogs();
58
+
59
+ const scheduleNext = () => {
60
+ if (cancelled) return;
61
+ timeoutId = setTimeout(async () => {
62
+ if (document.visibilityState === "visible") {
63
+ await fetchLogs();
64
+ }
65
+ scheduleNext();
66
+ }, 3000);
67
+ };
68
+
69
+ scheduleNext();
70
+
71
+ const handleVisibility = () => {
72
+ if (document.visibilityState === "visible") {
73
+ fetchLogs();
74
+ }
75
+ };
76
+ document.addEventListener("visibilitychange", handleVisibility);
77
+
78
+ return () => {
79
+ cancelled = true;
80
+ if (timeoutId) clearTimeout(timeoutId);
81
+ document.removeEventListener("visibilitychange", handleVisibility);
82
+ };
83
+ }, [fetchLogs]);
84
+
85
+ useEffect(() => {
86
+ if (autoScroll && containerRef.current) {
87
+ containerRef.current.scrollTop = containerRef.current.scrollHeight;
88
+ }
89
+ }, [logs, autoScroll]);
90
+
91
+ const selectStyle: React.CSSProperties = {
92
+ background: "#313244",
93
+ color: "#cdd6f4",
94
+ border: "1px solid #45475a",
95
+ borderRadius: 4,
96
+ padding: "4px 8px"
97
+ };
98
+
99
+ return (
100
+ <div style={{
101
+ display: "flex",
102
+ flexDirection: "column",
103
+ height: "calc(100vh - 64px)",
104
+ background: "#1e1e2e",
105
+ color: "#cdd6f4"
106
+ }}>
107
+ {/* Toolbar */}
108
+ <div style={{
109
+ display: "flex",
110
+ gap: 8,
111
+ padding: "8px 16px",
112
+ borderBottom: "1px solid #313244",
113
+ alignItems: "center",
114
+ flexWrap: "wrap"
115
+ }}>
116
+ <Select
117
+ value={level}
118
+ onValueChange={setLevel}
119
+ size="small"
120
+ placeholder="All Levels"
121
+ >
122
+ <SelectItem value="">All Levels</SelectItem>
123
+ <SelectItem value="debug">Debug</SelectItem>
124
+ <SelectItem value="info">Info</SelectItem>
125
+ <SelectItem value="warn">Warn</SelectItem>
126
+ <SelectItem value="error">Error</SelectItem>
127
+ </Select>
128
+ <Select
129
+ value={source}
130
+ onValueChange={setSource}
131
+ size="small"
132
+ placeholder="All Sources"
133
+ >
134
+ <SelectItem value="">All Sources</SelectItem>
135
+ <SelectItem value="api">API</SelectItem>
136
+ <SelectItem value="auth">Auth</SelectItem>
137
+ <SelectItem value="storage">Storage</SelectItem>
138
+ <SelectItem value="realtime">Realtime</SelectItem>
139
+ <SelectItem value="system">System</SelectItem>
140
+ </Select>
141
+ <TextField
142
+ size="small"
143
+ placeholder="Search logs..."
144
+ value={search}
145
+ onChange={e => setSearch(e.target.value)}
146
+ className="flex-1 min-w-[200px]"
147
+ />
148
+ <div className="flex items-center gap-1.5 cursor-pointer">
149
+ <Checkbox
150
+ id="auto-scroll"
151
+ checked={autoScroll}
152
+ onCheckedChange={setAutoScroll}
153
+ size="small"
154
+ padding={false}
155
+ />
156
+ <Label
157
+ htmlFor="auto-scroll"
158
+ className="text-xs select-none cursor-pointer"
159
+ >
160
+ Auto-scroll
161
+ </Label>
162
+ </div>
163
+ <span style={{ fontSize: 12, color: "#6c7086" }}>
164
+ {logs.length} entries
165
+ </span>
166
+ </div>
167
+ {/* Log entries */}
168
+ <div
169
+ ref={containerRef}
170
+ style={{
171
+ flex: 1,
172
+ overflow: "auto",
173
+ fontFamily: "monospace",
174
+ fontSize: 12,
175
+ padding: "8px 0"
176
+ }}
177
+ >
178
+ {logs.map(log => (
179
+ <div
180
+ key={log.id}
181
+ style={{
182
+ padding: "2px 16px",
183
+ display: "flex",
184
+ gap: 8,
185
+ borderBottom: "1px solid #181825"
186
+ }}
187
+ >
188
+ <span style={{ color: "#6c7086", flexShrink: 0 }}>
189
+ {new Date(log.timestamp).toLocaleTimeString()}
190
+ </span>
191
+ <span style={{
192
+ color: LEVEL_COLORS[log.level] || "#cdd6f4",
193
+ width: 40,
194
+ flexShrink: 0,
195
+ textTransform: "uppercase",
196
+ fontWeight: 600
197
+ }}>
198
+ {log.level}
199
+ </span>
200
+ <span style={{
201
+ color: SOURCE_COLORS[log.source] || "#cdd6f4",
202
+ width: 64,
203
+ flexShrink: 0
204
+ }}>
205
+ [{log.source}]
206
+ </span>
207
+ <span style={{ color: "#cdd6f4", flex: 1 }}>
208
+ {log.message}
209
+ </span>
210
+ </div>
211
+ ))}
212
+ {logs.length === 0 && (
213
+ <div style={{
214
+ padding: 32,
215
+ textAlign: "center",
216
+ color: "#6c7086"
217
+ }}>
218
+ No log entries yet. Logs will appear here as requests come in.
219
+ </div>
220
+ )}
221
+ </div>
222
+ </div>
223
+ );
224
+ }