@rebasepro/types 0.8.0 → 0.9.1-canary.09aaf62

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 (88) hide show
  1. package/README.md +13 -13
  2. package/dist/controllers/auth.d.ts +7 -1
  3. package/dist/controllers/client.d.ts +204 -6
  4. package/dist/controllers/collection_registry.d.ts +3 -3
  5. package/dist/controllers/customization_controller.d.ts +1 -1
  6. package/dist/controllers/data.d.ts +309 -35
  7. package/dist/controllers/data_driver.d.ts +92 -37
  8. package/dist/controllers/email.d.ts +2 -2
  9. package/dist/controllers/index.d.ts +1 -1
  10. package/dist/controllers/local_config_persistence.d.ts +4 -4
  11. package/dist/controllers/navigation.d.ts +2 -2
  12. package/dist/controllers/registry.d.ts +21 -8
  13. package/dist/controllers/{side_entity_controller.d.ts → side_panel_controller.d.ts} +7 -7
  14. package/dist/controllers/storage.d.ts +39 -0
  15. package/dist/errors.d.ts +64 -0
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.es.js +160 -15
  18. package/dist/index.es.js.map +1 -1
  19. package/dist/rebase_context.d.ts +8 -4
  20. package/dist/types/auth_adapter.d.ts +29 -4
  21. package/dist/types/backend.d.ts +33 -26
  22. package/dist/types/backup.d.ts +20 -0
  23. package/dist/types/builders.d.ts +2 -2
  24. package/dist/types/collections.d.ts +84 -75
  25. package/dist/types/component_overrides.d.ts +61 -3
  26. package/dist/types/cron.d.ts +11 -2
  27. package/dist/types/data_source.d.ts +15 -2
  28. package/dist/types/database_adapter.d.ts +21 -4
  29. package/dist/types/entities.d.ts +7 -7
  30. package/dist/types/entity_actions.d.ts +7 -7
  31. package/dist/types/entity_callbacks.d.ts +39 -39
  32. package/dist/types/entity_views.d.ts +3 -3
  33. package/dist/types/filter-operators.d.ts +62 -17
  34. package/dist/types/index.d.ts +1 -0
  35. package/dist/types/modify_collections.d.ts +2 -2
  36. package/dist/types/plugins.d.ts +9 -9
  37. package/dist/types/policy.d.ts +111 -11
  38. package/dist/types/properties.d.ts +54 -9
  39. package/dist/types/relations.d.ts +3 -3
  40. package/dist/types/slots.d.ts +14 -14
  41. package/dist/types/translations.d.ts +5 -1
  42. package/dist/types/user_management_delegate.d.ts +8 -2
  43. package/dist/types/websockets.d.ts +46 -13
  44. package/dist/users/user.d.ts +21 -9
  45. package/package.json +5 -6
  46. package/src/controllers/auth.tsx +7 -1
  47. package/src/controllers/client.ts +235 -7
  48. package/src/controllers/collection_registry.ts +3 -3
  49. package/src/controllers/customization_controller.tsx +1 -1
  50. package/src/controllers/data.ts +330 -39
  51. package/src/controllers/data_driver.ts +93 -40
  52. package/src/controllers/email.ts +2 -2
  53. package/src/controllers/index.ts +1 -1
  54. package/src/controllers/local_config_persistence.tsx +4 -4
  55. package/src/controllers/navigation.ts +2 -2
  56. package/src/controllers/registry.ts +22 -8
  57. package/src/controllers/{side_entity_controller.tsx → side_panel_controller.tsx} +7 -7
  58. package/src/controllers/storage.ts +60 -1
  59. package/src/errors.ts +80 -0
  60. package/src/index.ts +1 -0
  61. package/src/rebase_context.tsx +8 -4
  62. package/src/types/auth_adapter.ts +29 -4
  63. package/src/types/backend.ts +44 -36
  64. package/src/types/backup.ts +26 -0
  65. package/src/types/builders.ts +2 -2
  66. package/src/types/collections.ts +101 -91
  67. package/src/types/component_overrides.ts +72 -5
  68. package/src/types/cron.ts +11 -2
  69. package/src/types/data_source.ts +24 -2
  70. package/src/types/database_adapter.ts +19 -4
  71. package/src/types/entities.ts +7 -7
  72. package/src/types/entity_actions.tsx +7 -7
  73. package/src/types/entity_callbacks.ts +42 -42
  74. package/src/types/entity_views.tsx +3 -3
  75. package/src/types/filter-operators.ts +96 -23
  76. package/src/types/index.ts +1 -0
  77. package/src/types/modify_collections.tsx +2 -2
  78. package/src/types/plugins.tsx +9 -9
  79. package/src/types/policy.ts +114 -9
  80. package/src/types/properties.ts +58 -9
  81. package/src/types/relations.ts +3 -3
  82. package/src/types/slots.tsx +14 -14
  83. package/src/types/translations.ts +5 -1
  84. package/src/types/user_management_delegate.ts +8 -2
  85. package/src/types/websockets.ts +43 -14
  86. package/src/users/user.ts +22 -9
  87. package/dist/index.umd.js +0 -458
  88. package/dist/index.umd.js.map +0 -1
@@ -1,26 +1,26 @@
1
1
  import type { CollectionRegistryController } from "./collection_registry";
2
- import type { Entity, EntityStatus, EntityValues } from "../types/entities";
3
- import type { EntityCollection, FilterValues } from "../types/collections";
2
+ import type { EntityStatus, EntityValues } from "../types/entities";
3
+ import type { CollectionConfig, FilterValues } from "../types/collections";
4
4
  import type { RebaseContext } from "../rebase_context";
5
5
 
6
6
 
7
7
  /**
8
8
  * @internal
9
9
  */
10
- export interface FetchEntityProps<M extends Record<string, unknown> = Record<string, unknown>> {
10
+ export interface FetchOneProps<M extends Record<string, unknown> = Record<string, unknown>> {
11
11
  path: string;
12
- entityId: string | number;
12
+ id: string | number;
13
13
  databaseId?: string;
14
- collection?: EntityCollection<M>
14
+ collection?: CollectionConfig<M>
15
15
  }
16
16
 
17
17
  /**
18
18
  * @internal
19
19
  */
20
- export type ListenEntityProps<M extends Record<string, unknown> = Record<string, unknown>> =
21
- FetchEntityProps<M>
20
+ export type ListenOneProps<M extends Record<string, unknown> = Record<string, unknown>> =
21
+ FetchOneProps<M>
22
22
  & {
23
- onUpdate: (entity: Entity<M> | null) => void,
23
+ onUpdate: (row: Record<string, unknown> | null) => void,
24
24
  onError?: (error: Error) => void,
25
25
  }
26
26
 
@@ -45,7 +45,7 @@ export interface VectorSearchParams {
45
45
  */
46
46
  export interface FetchCollectionProps<M extends Record<string, unknown> = Record<string, unknown>> {
47
47
  path: string;
48
- collection?: EntityCollection<M>;
48
+ collection?: CollectionConfig<M>;
49
49
  filter?: FilterValues<Extract<keyof M, string>>,
50
50
  limit?: number;
51
51
  offset?: number;
@@ -63,42 +63,75 @@ export interface FetchCollectionProps<M extends Record<string, unknown> = Record
63
63
  export type ListenCollectionProps<M extends Record<string, unknown> = Record<string, unknown>> =
64
64
  FetchCollectionProps<M> &
65
65
  {
66
- onUpdate: (entities: Entity<M>[]) => void;
66
+ onUpdate: (rows: Record<string, unknown>[]) => void;
67
67
  onError?: (error: Error) => void;
68
68
  };
69
69
 
70
70
  /**
71
71
  * @internal
72
72
  */
73
- export interface SaveEntityProps<M extends Record<string, unknown> = Record<string, unknown>> {
73
+ export interface SaveProps<M extends Record<string, unknown> = Record<string, unknown>> {
74
74
  path: string;
75
75
  values: Partial<EntityValues<M>>;
76
- entityId?: string | number; // can be empty for new entities
76
+ id?: string | number; // can be empty for new entities
77
77
  previousValues?: Partial<EntityValues<M>>;
78
- collection?: EntityCollection<M>;
78
+ collection?: CollectionConfig<M>;
79
79
  status: EntityStatus;
80
+ /**
81
+ * Write the row with INSERT ... ON CONFLICT DO UPDATE on the primary key
82
+ * instead of choosing between insert and update up front.
83
+ *
84
+ * One statement, so it does not lose the race a read-then-write can, and it
85
+ * succeeds whether or not the row is already there — what a re-runnable
86
+ * import needs. Requires every primary key column to be present; without
87
+ * them there is no conflict target and the row is inserted normally.
88
+ */
89
+ upsert?: boolean;
90
+ }
91
+
92
+ /**
93
+ * @internal
94
+ */
95
+ export interface SaveManyProps<M extends Record<string, unknown> = Record<string, unknown>> {
96
+ path: string;
97
+ /**
98
+ * The rows to write. A row carrying its primary key updates (or, with
99
+ * `upsert`, inserts-or-updates) that row; one without inserts.
100
+ */
101
+ rows: Partial<EntityValues<M>>[];
102
+ collection?: CollectionConfig<M>;
103
+ /** Apply every row as INSERT ... ON CONFLICT DO UPDATE. See {@link SaveProps.upsert}. */
104
+ upsert?: boolean;
80
105
  }
81
106
 
82
107
  /**
83
108
  * @internal
84
109
  */
85
- export interface DeleteEntityProps<M extends Record<string, unknown> = Record<string, unknown>> {
86
- entity: Entity<M>;
87
- collection?: EntityCollection<M>;
110
+ export interface DeleteProps<M extends Record<string, unknown> = Record<string, unknown>> {
111
+ row: { id: string | number; path: string; values?: Partial<EntityValues<M>> };
112
+ collection?: CollectionConfig<M>;
88
113
  }
89
114
 
90
115
  export type FilterCombinationValidProps = {
91
116
  path: string;
92
117
  databaseId?: string;
93
- collection: EntityCollection;
118
+ collection: CollectionConfig;
94
119
  filterValues: FilterValues<string>;
95
120
  sortBy?: [string, "asc" | "desc"];
96
121
  };
97
122
 
98
123
  /**
99
- * Internal driver interface for communicating with the data layer.
100
- * This is NOT the public API — use `RebaseData` / `context.data` instead.
101
- * @internal
124
+ * The integration SPI for plugging a data backend into Rebase.
125
+ *
126
+ * Implement this interface to connect a custom backend (or use a built-in
127
+ * driver such as the Firestore one) and register it on
128
+ * `<Rebase dataSources>`. Rebase wraps drivers via `buildRebaseData` and
129
+ * routes collections to them by their `dataSource` key.
130
+ *
131
+ * For *consuming* data in application code, use `RebaseData` /
132
+ * `context.data` instead — this interface is only for providing it.
133
+ *
134
+ * @group Datasource
102
135
  */
103
136
  export interface DataDriver {
104
137
 
@@ -115,9 +148,9 @@ export interface DataDriver {
115
148
  /**
116
149
  * Fetch data from a collection
117
150
  * @param props
118
- * @return Promise of entities
151
+ * @return Promise of flat rows
119
152
  */
120
- fetchCollection<M extends Record<string, unknown> = Record<string, unknown>>(props: FetchCollectionProps<M>): Promise<Entity<M>[]>;
153
+ fetchCollection<M extends Record<string, unknown> = Record<string, unknown>>(props: FetchCollectionProps<M>): Promise<Record<string, unknown>[]>;
121
154
 
122
155
  /**
123
156
  * Listen to a collection in a given path. If you don't implement this method
@@ -128,30 +161,44 @@ export interface DataDriver {
128
161
  listenCollection?<M extends Record<string, unknown> = Record<string, unknown>>(props: ListenCollectionProps<M>): () => void;
129
162
 
130
163
  /**
131
- * Retrieve an entity given a path and a collection
164
+ * Retrieve a single row given a path and a collection
132
165
  * @param props
133
166
  */
134
- fetchEntity<M extends Record<string, unknown> = Record<string, unknown>>(props: FetchEntityProps<M>): Promise<Entity<M> | undefined>;
167
+ fetchOne<M extends Record<string, unknown> = Record<string, unknown>>(props: FetchOneProps<M>): Promise<Record<string, unknown> | undefined>;
135
168
 
136
169
  /**
137
- * Get realtime updates on one entity.
170
+ * Get realtime updates on one row.
138
171
  * @param props
139
172
  * @return Function to cancel subscription
140
173
  */
141
- listenEntity?<M extends Record<string, unknown> = Record<string, unknown>>(props: ListenEntityProps<M>): () => void;
174
+ listenOne?<M extends Record<string, unknown> = Record<string, unknown>>(props: ListenOneProps<M>): () => void;
142
175
 
143
176
  /**
144
- * Save entity to the specified path
177
+ * Save a row to the specified path
145
178
  * @param props
146
179
  */
147
- saveEntity<M extends Record<string, unknown> = Record<string, unknown>>(props: SaveEntityProps<M>): Promise<Entity<M>>;
180
+ save<M extends Record<string, unknown> = Record<string, unknown>>(props: SaveProps<M>): Promise<Record<string, unknown>>;
148
181
 
149
182
  /**
150
- * Delete an entity
183
+ * Save many rows as one unit of work.
184
+ *
185
+ * Every row runs the same pipeline as {@link save} — callbacks, relations
186
+ * and row-level security all still apply — but they share a single
187
+ * transaction, so the batch either lands whole or not at all. That, and the
188
+ * single round trip, is what makes importing tens of thousands of rows
189
+ * viable without dropping to raw SQL.
190
+ *
191
+ * Optional: drivers that cannot do this leave it undefined and callers fall
192
+ * back to `save` per row.
193
+ */
194
+ saveMany?<M extends Record<string, unknown> = Record<string, unknown>>(props: SaveManyProps<M>): Promise<Record<string, unknown>[]>;
195
+
196
+ /**
197
+ * Delete a entity
151
198
  * @param props
152
199
  * @return was the whole deletion flow successful
153
200
  */
154
- deleteEntity<M extends Record<string, unknown> = Record<string, unknown>>(props: DeleteEntityProps<M>): Promise<void>;
201
+ delete<M extends Record<string, unknown> = Record<string, unknown>>(props: DeleteProps<M>): Promise<void>;
155
202
 
156
203
  /**
157
204
  * Delete all entities from a collection.
@@ -164,7 +211,7 @@ export interface DataDriver {
164
211
  * @param path Collection path
165
212
  * @param name of the property
166
213
  * @param value
167
- * @param entityId
214
+ * @param id
168
215
  * @param collection
169
216
  * @return `true` if there are no other fields besides the given entity
170
217
  */
@@ -172,14 +219,14 @@ export interface DataDriver {
172
219
  path: string,
173
220
  name: string,
174
221
  value: unknown,
175
- entityId?: string | number,
176
- collection?: EntityCollection
222
+ id?: string | number,
223
+ collection?: CollectionConfig
177
224
  ): Promise<boolean>;
178
225
 
179
226
  /**
180
227
  * Count the number of entities in a collection
181
228
  */
182
- countEntities?<M extends Record<string, unknown> = Record<string, unknown>>(props: FetchCollectionProps<M>): Promise<number>;
229
+ count?<M extends Record<string, unknown> = Record<string, unknown>>(props: FetchCollectionProps<M>): Promise<number>;
183
230
 
184
231
  /**
185
232
  * Check if the given filter combination is valid
@@ -202,7 +249,7 @@ export interface DataDriver {
202
249
  context: RebaseContext,
203
250
  path: string,
204
251
  databaseId?: string,
205
- collection: EntityCollection,
252
+ collection: CollectionConfig,
206
253
  parentCollectionSlugs?: string[];
207
254
  parentEntityIds?: string[];
208
255
  }) => Promise<boolean>;
@@ -216,7 +263,7 @@ export interface DataDriver {
216
263
 
217
264
  /**
218
265
  * Optional REST-optimised fetch service. When present, the REST API
219
- * generator uses these methods instead of the generic `fetchEntity` /
266
+ * generator uses these methods instead of the generic `fetchOne` /
220
267
  * `fetchCollection` pipeline, enabling include-aware eager-loading.
221
268
  */
222
269
  restFetchService?: RestFetchService;
@@ -244,8 +291,14 @@ export interface DataDriver {
244
291
  * REST-optimised fetch service exposed by drivers that support
245
292
  * eager-loading of relations via `include`.
246
293
  *
247
- * The methods return flattened rows (`{ id, ...columns }`) rather
248
- * than the `Entity<M>` wrapper used by the generic DataDriver API.
294
+ * The methods return flattened rows exactly the table's columns, under their
295
+ * own names and with the types the database returned — and included relations
296
+ * inlined as plain nested rows. This is the shape served to app developers
297
+ * through the REST API / SDK client.
298
+ *
299
+ * No synthesized `id`: identity is a primary key, which may be named anything
300
+ * and span several columns, so an address is derived by whoever needs one (see
301
+ * `buildCompositeId`) rather than written into the row on top of the data.
249
302
  *
250
303
  * @group DataDriver
251
304
  */
@@ -272,9 +325,9 @@ export interface RestFetchService {
272
325
  /**
273
326
  * Fetch a single flattened entity with optional relation includes.
274
327
  */
275
- fetchEntityForRest(
328
+ fetchOneForRest(
276
329
  collectionPath: string,
277
- entityId: string | number,
330
+ id: string | number,
278
331
  include?: string[],
279
332
  databaseId?: string
280
333
  ): Promise<Record<string, unknown> | null>;
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Email service types — portable interface shared by RebaseClient and server-core.
2
+ * Email service types — portable interface shared by RebaseClient and server.
3
3
  *
4
- * The concrete SMTP implementation lives in `@rebasepro/server-core/email`.
4
+ * The concrete SMTP implementation lives in `@rebasepro/server/email`.
5
5
  * This file provides only the consumer-facing contract so that it can be
6
6
  * referenced from `RebaseClient` without dragging in nodemailer.
7
7
  */
@@ -14,7 +14,7 @@ export * from "./storage";
14
14
  export * from "./email";
15
15
  export * from "./client";
16
16
  export * from "./customization_controller";
17
- export * from "./side_entity_controller";
17
+ export * from "./side_panel_controller";
18
18
  export * from "./side_dialogs_controller";
19
19
  export * from "./dialogs_controller";
20
20
  export * from "./snackbar";
@@ -1,9 +1,9 @@
1
- import type { EntityCollection } from "../types/collections";
1
+ import type { CollectionConfig } from "../types/collections";
2
2
 
3
3
  /**
4
4
  * @group Models
5
5
  */
6
- export type PartialEntityCollection<M extends Record<string, unknown> = Record<string, unknown>> = Partial<EntityCollection<M>>;
6
+ export type PartialCollectionConfig<M extends Record<string, unknown> = Record<string, unknown>> = Partial<CollectionConfig<M>>;
7
7
 
8
8
  /**
9
9
  * This interface is in charge of defining the controller that persists
@@ -11,8 +11,8 @@ export type PartialEntityCollection<M extends Record<string, unknown> = Record<s
11
11
  * a data source, such as local storage or Firestore.
12
12
  */
13
13
  export interface UserConfigurationPersistence {
14
- onCollectionModified: <M extends Record<string, unknown> = Record<string, unknown>>(path: string, partialCollection: PartialEntityCollection<M>) => void;
15
- getCollectionConfig: <M extends Record<string, unknown> = Record<string, unknown>>(path: string) => PartialEntityCollection<M>;
14
+ onCollectionModified: <M extends Record<string, unknown> = Record<string, unknown>>(path: string, partialCollection: PartialCollectionConfig<M>) => void;
15
+ getCollectionConfig: <M extends Record<string, unknown> = Record<string, unknown>>(path: string) => PartialCollectionConfig<M>;
16
16
  recentlyVisitedPaths: string[];
17
17
  setRecentlyVisitedPaths: (paths: string[]) => void;
18
18
  favouritePaths: string[];
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
  import type { EntityReference } from "../types/entities";
3
- import type { EntityCollection } from "../types/collections";
3
+ import type { CollectionConfig } from "../types/collections";
4
4
 
5
5
 
6
6
  /**
@@ -248,7 +248,7 @@ export interface NavigationEntry {
248
248
  name: string;
249
249
  slug: string;
250
250
  type: "collection" | "view" | "admin";
251
- collection?: EntityCollection;
251
+ collection?: CollectionConfig;
252
252
  view?: AppView;
253
253
  description?: string;
254
254
  group: string;
@@ -1,13 +1,13 @@
1
1
  import { ReactNode } from "react";
2
- import type { EntityCollection } from "../types/collections";
3
- import type { EntityCollectionsBuilder, AppViewsBuilder } from "../types/builders";
2
+ import type { CollectionConfig } from "../types/collections";
3
+ import type { CollectionConfigsBuilder, AppViewsBuilder } from "../types/builders";
4
4
  import type { EntityCustomView } from "../types/entity_views";
5
5
  import type { EntityAction } from "../types/entity_actions";
6
6
  import type { AppView, NavigationGroupMapping } from "./navigation";
7
7
 
8
8
  /**
9
9
  * Options to enable the built-in collection editor.
10
- * When provided to `<RebaseCMS>`, the editor is auto-wired as a native feature.
10
+ * When provided to `<RebaseAdmin>`, the editor is auto-wired as a native feature.
11
11
  */
12
12
  export interface CollectionEditorOptions {
13
13
  /**
@@ -21,8 +21,8 @@ export interface CollectionEditorOptions {
21
21
  pathSuggestions?: string[];
22
22
  }
23
23
 
24
- export interface RebaseCMSConfig<EC extends EntityCollection = EntityCollection> {
25
- collections?: EC[] | EntityCollectionsBuilder<EC>;
24
+ export interface RebaseAdminConfig<EC extends CollectionConfig = CollectionConfig> {
25
+ collections?: EC[] | CollectionConfigsBuilder<EC>;
26
26
 
27
27
  /**
28
28
  * Custom top-level views added to the main navigation.
@@ -51,10 +51,24 @@ export interface RebaseCMSConfig<EC extends EntityCollection = EntityCollection>
51
51
  * are all auto-wired — no plugin or manual view injection needed.
52
52
  */
53
53
  collectionEditor?: boolean | CollectionEditorOptions;
54
+
55
+ /**
56
+ * URL path prefix the admin is mounted under, when it does not live at the
57
+ * site root. Set this when the admin is rendered inside a path-prefixed
58
+ * route (e.g. `<Route path="/admin/*">`) so URL⇄collection resolution
59
+ * accounts for the prefix — otherwise the current path won't be recognized
60
+ * as a collection path and views hang on a spinner with no data fetch.
61
+ *
62
+ * Do NOT set this when mounting via a react-router `basename` — react-router
63
+ * already strips the prefix from the location, so the default (`/`) is correct.
64
+ *
65
+ * @default "/"
66
+ */
67
+ basePath?: string;
54
68
  }
55
69
 
56
70
  export interface RebaseStudioConfig {
57
- tools?: ("sql" | "js" | "rls" | "schema" | "storage" | "cron" | "schema-visualizer" | "branches" | "api" | "logs" | "api-keys")[];
71
+ tools?: ("sql" | "js" | "rls" | "schema" | "storage" | "cron" | "schema-visualizer" | "branches" | "backups" | "api" | "logs" | "api-keys")[];
58
72
  homePage?: ReactNode;
59
73
  devViews?: AppView[];
60
74
  }
@@ -65,12 +79,12 @@ export interface RebaseAuthConfig {
65
79
 
66
80
  export interface RebaseRegistryController {
67
81
  // Current state
68
- cmsConfig: RebaseCMSConfig | null;
82
+ cmsConfig: RebaseAdminConfig | null;
69
83
  studioConfig: RebaseStudioConfig | null;
70
84
  authConfig: RebaseAuthConfig | null;
71
85
 
72
86
  // Registration functions
73
- registerCMS: (config: RebaseCMSConfig) => void;
87
+ registerCMS: (config: RebaseAdminConfig) => void;
74
88
  unregisterCMS: () => void;
75
89
 
76
90
  registerStudio: (config: RebaseStudioConfig) => void;
@@ -1,11 +1,11 @@
1
1
  import type { Entity } from "../types/entities";
2
- import type { EntityCollection } from "../types/collections";
2
+ import type { CollectionConfig } from "../types/collections";
3
3
 
4
4
  /**
5
5
  * Props used to open a side dialog
6
6
  * @group Hooks and utilities
7
7
  */
8
- export interface EntitySidePanelProps<M extends Record<string, unknown> = Record<string, unknown>> {
8
+ export interface SidePanelBindingProps<M extends Record<string, unknown> = Record<string, unknown>> {
9
9
 
10
10
  /**
11
11
  * Absolute path of the entity
@@ -38,7 +38,7 @@ export interface EntitySidePanelProps<M extends Record<string, unknown> = Record
38
38
  * Collection representing the entities of this view.
39
39
  * If you leave it blank it will be induced by your navigation
40
40
  */
41
- collection?: EntityCollection<M>;
41
+ collection?: CollectionConfig<M>;
42
42
 
43
43
  /**
44
44
  * Should update the URL when opening the dialog.
@@ -87,7 +87,7 @@ export interface EntitySidePanelProps<M extends Record<string, unknown> = Record
87
87
  * Controller to open the side dialog displaying entity forms
88
88
  * @group Hooks and utilities
89
89
  */
90
- export interface SideEntityController {
90
+ export interface SidePanelController {
91
91
  /**
92
92
  * Close the last panel
93
93
  */
@@ -98,15 +98,15 @@ export interface SideEntityController {
98
98
  * of the view is fetched from the collections you have specified in the
99
99
  * navigation.
100
100
  * At least you need to pass the path of the entity you would like
101
- * to edit. You can set an entityId if you would like to edit and existing one
101
+ * to edit. You can set a entityId if you would like to edit and existing one
102
102
  * (or a new one with that id).
103
103
  * @param props
104
104
  */
105
- open: <M extends Record<string, unknown> = Record<string, unknown>>(props: EntitySidePanelProps<M>) => void;
105
+ open: <M extends Record<string, unknown> = Record<string, unknown>>(props: SidePanelBindingProps<M>) => void;
106
106
 
107
107
  /**
108
108
  * Replace the last open entity panel with the given one.
109
109
  * @param props
110
110
  */
111
- replace: <M extends Record<string, unknown> = Record<string, unknown>>(props: EntitySidePanelProps<M>) => void;
111
+ replace: <M extends Record<string, unknown> = Record<string, unknown>>(props: SidePanelBindingProps<M>) => void;
112
112
  }
@@ -1,3 +1,40 @@
1
+ /**
2
+ * Path prefix that marks an object as **public**. Files stored under this
3
+ * prefix are served without any auth token via a stable, permanent,
4
+ * CDN-cacheable URL (see {@link StorageSource.getSignedUrl}). Shared by the
5
+ * client SDK and the backend so both agree on which objects are public.
6
+ *
7
+ * @group Models
8
+ */
9
+ export const PUBLIC_STORAGE_PREFIX = "public/";
10
+
11
+ /**
12
+ * True when a storage key/path points at a public object (lives under
13
+ * {@link PUBLIC_STORAGE_PREFIX}). The check is applied to the key *within the
14
+ * bucket* — strip any `bucket/` and `scheme://` prefixes first.
15
+ *
16
+ * @group Models
17
+ */
18
+ export function isPublicStoragePath(path: string | null | undefined): boolean {
19
+ if (!path) return false;
20
+ let p = path;
21
+ const scheme = p.indexOf("://");
22
+ if (scheme !== -1) p = p.substring(scheme + 3);
23
+ p = p.replace(/^\/+/, "");
24
+
25
+ // Defense-in-depth: a path containing traversal segments is never public,
26
+ // so an attacker can't reach a private object via `public/../secret`.
27
+ if (p.split("/").some((seg) => seg === "..")) return false;
28
+
29
+ // Public iff the object **key** starts with the public prefix. A single
30
+ // leading `default/` bucket segment is tolerated (the default bucket).
31
+ // A substring match is deliberately NOT used — a private object under a
32
+ // folder literally named `public` (e.g. `reports/public/q3.pdf`) must stay
33
+ // private. Named buckets: pass the key (not `bucket/key`) so the prefix is
34
+ // anchored; otherwise it falls back to a private, token-scoped URL (safe).
35
+ return p.startsWith(PUBLIC_STORAGE_PREFIX) || p.startsWith(`default/${PUBLIC_STORAGE_PREFIX}`);
36
+ }
37
+
1
38
  /**
2
39
  * @group Models
3
40
  */
@@ -5,7 +42,14 @@ export interface UploadFileProps {
5
42
  file: File,
6
43
  key: string,
7
44
  metadata?: Record<string, unknown>,
8
- bucket?: string
45
+ bucket?: string,
46
+ /**
47
+ * Store this object as **public**: it is placed under
48
+ * {@link PUBLIC_STORAGE_PREFIX} and served via a stable, token-less,
49
+ * permanent URL (safe to persist in a database and cache on a CDN).
50
+ * Defaults to `false` (private, short-lived signed URLs).
51
+ */
52
+ public?: boolean
9
53
  }
10
54
 
11
55
  /**
@@ -74,6 +118,21 @@ export declare interface DownloadMetadata {
74
118
  contentType: string;
75
119
 
76
120
  customMetadata: Record<string, unknown>;
121
+ /**
122
+ * Optional short-lived download token (for local/server-mediated storage).
123
+ * Absent for public objects, which need no token.
124
+ */
125
+ token?: string;
126
+ /**
127
+ * Optional remaining lifetime of the token, in seconds.
128
+ */
129
+ tokenExpiresIn?: number;
130
+ /**
131
+ * True when this object is public: it is served without a token via a
132
+ * stable, permanent, CDN-cacheable URL. When set, the client builds a
133
+ * token-less URL and caches it indefinitely.
134
+ */
135
+ public?: boolean;
77
136
  }
78
137
 
79
138
  /**
package/src/errors.ts ADDED
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Structured initializer for {@link RebaseApiError}.
3
+ *
4
+ * @group Errors
5
+ */
6
+ export interface RebaseErrorInit {
7
+ /**
8
+ * HTTP status code, when the error originated from an HTTP response.
9
+ * Left `undefined` for realtime/WebSocket, network, and client-side
10
+ * logic errors that have no HTTP status.
11
+ */
12
+ status?: number;
13
+ /** Stable, machine-readable error code (e.g. `"NOT_FOUND"`, `"BAD_REQUEST"`). */
14
+ code?: string;
15
+ /** Structured error payload returned by the server, when present. */
16
+ details?: unknown;
17
+ /** The underlying error this one wraps, if any. */
18
+ cause?: unknown;
19
+ }
20
+
21
+ /**
22
+ * The single error type thrown across the entire Rebase client surface —
23
+ * HTTP data/control-plane calls, realtime/WebSocket operations, and
24
+ * client-side logic errors (e.g. an unknown collection accessor). A `catch`
25
+ * block only ever needs to check for this one class:
26
+ *
27
+ * ```ts
28
+ * import { RebaseApiError } from "@rebasepro/client"; // re-exported
29
+ *
30
+ * try {
31
+ * await client.data.products.update(id, { price: 9 });
32
+ * } catch (e) {
33
+ * if (e instanceof RebaseApiError) {
34
+ * if (e.status === 404) { ... } // HTTP failures carry a status
35
+ * console.error(e.code, e.details);
36
+ * }
37
+ * }
38
+ * ```
39
+ *
40
+ * `status` is present for HTTP failures and `undefined` otherwise, so its
41
+ * presence distinguishes transport-level errors from realtime/logic errors.
42
+ *
43
+ * @group Errors
44
+ */
45
+ export class RebaseApiError extends Error {
46
+ /** HTTP status code, or `undefined` for non-HTTP errors. */
47
+ readonly status?: number;
48
+ /** Stable machine-readable error code, when the server supplied one. */
49
+ readonly code?: string;
50
+ /** Structured error payload from the server, when present. */
51
+ readonly details?: unknown;
52
+
53
+ constructor(message: string, init: RebaseErrorInit = {}) {
54
+ super(message);
55
+ this.name = "RebaseApiError";
56
+ this.status = init.status;
57
+ this.code = init.code;
58
+ this.details = init.details;
59
+ if (init.cause !== undefined) {
60
+ // `cause` is standard on Error but not always in the lib target's type.
61
+ (this as { cause?: unknown }).cause = init.cause;
62
+ }
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Client-side logic error — raised before any request is made (e.g. accessing
68
+ * an unknown collection accessor when a typed dictionary is configured).
69
+ *
70
+ * A subclass of {@link RebaseApiError} (with no `status`), so a single
71
+ * `catch (e) { if (e instanceof RebaseApiError) ... }` handles it too.
72
+ *
73
+ * @group Errors
74
+ */
75
+ export class RebaseClientError extends RebaseApiError {
76
+ constructor(message: string) {
77
+ super(message);
78
+ this.name = "RebaseClientError";
79
+ }
80
+ }
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from "./errors";
1
2
  export * from "./rebase_context";
2
3
  export * from "./types";
3
4
  export * from "./controllers";
@@ -5,7 +5,7 @@ import type { UserConfigurationPersistence } from "./controllers/local_config_pe
5
5
  import type { DatabaseAdmin } from "./types/backend";
6
6
  import type { RebaseClient } from "./controllers/client";
7
7
 
8
- import type { RebaseData } from "./controllers/data";
8
+ import type { RebaseSdkData } from "./controllers/data";
9
9
  import type { User } from "./users";
10
10
 
11
11
 
@@ -36,8 +36,12 @@ export type RebaseCallContext<USER extends User = User> = {
36
36
  /**
37
37
  * Unified data access — `context.data.products.create(...)`.
38
38
  * Access any collection as a dynamic property.
39
+ *
40
+ * Returns flat rows (`{ id, ...columns }`), identical to the frontend SDK
41
+ * client — so `context.data` in a backend callback and `client.data` in the
42
+ * frontend behave the same way (`row.title`, never `row.values.title`).
39
43
  */
40
- data: RebaseData;
44
+ data: RebaseSdkData;
41
45
 
42
46
  /**
43
47
  * Used storage implementation
@@ -77,9 +81,9 @@ export type RebaseContext<USER extends User = User, AuthControllerType extends A
77
81
  sideDialogsController?: import("./controllers/side_dialogs_controller").SideDialogsController;
78
82
 
79
83
  /**
80
- * Controller to open the side dialog displaying entity forms
84
+ * Controller to open the side panel displaying entity forms
81
85
  */
82
- sideEntityController?: import("./controllers/side_entity_controller").SideEntityController;
86
+ sidePanelController?: import("./controllers/side_panel_controller").SidePanelController;
83
87
 
84
88
  /**
85
89
  * Controller resolving URLs in the CMS