@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.
- package/README.md +13 -13
- package/dist/controllers/auth.d.ts +7 -1
- package/dist/controllers/client.d.ts +204 -6
- package/dist/controllers/collection_registry.d.ts +3 -3
- package/dist/controllers/customization_controller.d.ts +1 -1
- package/dist/controllers/data.d.ts +309 -35
- package/dist/controllers/data_driver.d.ts +92 -37
- package/dist/controllers/email.d.ts +2 -2
- package/dist/controllers/index.d.ts +1 -1
- package/dist/controllers/local_config_persistence.d.ts +4 -4
- package/dist/controllers/navigation.d.ts +2 -2
- package/dist/controllers/registry.d.ts +21 -8
- package/dist/controllers/{side_entity_controller.d.ts → side_panel_controller.d.ts} +7 -7
- package/dist/controllers/storage.d.ts +39 -0
- package/dist/errors.d.ts +64 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +160 -15
- package/dist/index.es.js.map +1 -1
- package/dist/rebase_context.d.ts +8 -4
- package/dist/types/auth_adapter.d.ts +29 -4
- package/dist/types/backend.d.ts +33 -26
- package/dist/types/backup.d.ts +20 -0
- package/dist/types/builders.d.ts +2 -2
- package/dist/types/collections.d.ts +84 -75
- package/dist/types/component_overrides.d.ts +61 -3
- package/dist/types/cron.d.ts +11 -2
- package/dist/types/data_source.d.ts +15 -2
- package/dist/types/database_adapter.d.ts +21 -4
- package/dist/types/entities.d.ts +7 -7
- package/dist/types/entity_actions.d.ts +7 -7
- package/dist/types/entity_callbacks.d.ts +39 -39
- package/dist/types/entity_views.d.ts +3 -3
- package/dist/types/filter-operators.d.ts +62 -17
- package/dist/types/index.d.ts +1 -0
- package/dist/types/modify_collections.d.ts +2 -2
- package/dist/types/plugins.d.ts +9 -9
- package/dist/types/policy.d.ts +111 -11
- package/dist/types/properties.d.ts +54 -9
- package/dist/types/relations.d.ts +3 -3
- package/dist/types/slots.d.ts +14 -14
- package/dist/types/translations.d.ts +5 -1
- package/dist/types/user_management_delegate.d.ts +8 -2
- package/dist/types/websockets.d.ts +46 -13
- package/dist/users/user.d.ts +21 -9
- package/package.json +5 -6
- package/src/controllers/auth.tsx +7 -1
- package/src/controllers/client.ts +235 -7
- package/src/controllers/collection_registry.ts +3 -3
- package/src/controllers/customization_controller.tsx +1 -1
- package/src/controllers/data.ts +330 -39
- package/src/controllers/data_driver.ts +93 -40
- package/src/controllers/email.ts +2 -2
- package/src/controllers/index.ts +1 -1
- package/src/controllers/local_config_persistence.tsx +4 -4
- package/src/controllers/navigation.ts +2 -2
- package/src/controllers/registry.ts +22 -8
- package/src/controllers/{side_entity_controller.tsx → side_panel_controller.tsx} +7 -7
- package/src/controllers/storage.ts +60 -1
- package/src/errors.ts +80 -0
- package/src/index.ts +1 -0
- package/src/rebase_context.tsx +8 -4
- package/src/types/auth_adapter.ts +29 -4
- package/src/types/backend.ts +44 -36
- package/src/types/backup.ts +26 -0
- package/src/types/builders.ts +2 -2
- package/src/types/collections.ts +101 -91
- package/src/types/component_overrides.ts +72 -5
- package/src/types/cron.ts +11 -2
- package/src/types/data_source.ts +24 -2
- package/src/types/database_adapter.ts +19 -4
- package/src/types/entities.ts +7 -7
- package/src/types/entity_actions.tsx +7 -7
- package/src/types/entity_callbacks.ts +42 -42
- package/src/types/entity_views.tsx +3 -3
- package/src/types/filter-operators.ts +96 -23
- package/src/types/index.ts +1 -0
- package/src/types/modify_collections.tsx +2 -2
- package/src/types/plugins.tsx +9 -9
- package/src/types/policy.ts +114 -9
- package/src/types/properties.ts +58 -9
- package/src/types/relations.ts +3 -3
- package/src/types/slots.tsx +14 -14
- package/src/types/translations.ts +5 -1
- package/src/types/user_management_delegate.ts +8 -2
- package/src/types/websockets.ts +43 -14
- package/src/users/user.ts +22 -9
- package/dist/index.umd.js +0 -458
- package/dist/index.umd.js.map +0 -1
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import type { CollectionRegistryController } from "./collection_registry";
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
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
|
|
10
|
+
export interface FetchOneProps<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
11
11
|
path: string;
|
|
12
|
-
|
|
12
|
+
id: string | number;
|
|
13
13
|
databaseId?: string;
|
|
14
|
-
collection?:
|
|
14
|
+
collection?: CollectionConfig<M>
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* @internal
|
|
19
19
|
*/
|
|
20
|
-
export type
|
|
21
|
-
|
|
20
|
+
export type ListenOneProps<M extends Record<string, unknown> = Record<string, unknown>> =
|
|
21
|
+
FetchOneProps<M>
|
|
22
22
|
& {
|
|
23
|
-
onUpdate: (
|
|
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?:
|
|
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: (
|
|
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
|
|
73
|
+
export interface SaveProps<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
74
74
|
path: string;
|
|
75
75
|
values: Partial<EntityValues<M>>;
|
|
76
|
-
|
|
76
|
+
id?: string | number; // can be empty for new entities
|
|
77
77
|
previousValues?: Partial<EntityValues<M>>;
|
|
78
|
-
collection?:
|
|
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
|
|
86
|
-
|
|
87
|
-
collection?:
|
|
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:
|
|
118
|
+
collection: CollectionConfig;
|
|
94
119
|
filterValues: FilterValues<string>;
|
|
95
120
|
sortBy?: [string, "asc" | "desc"];
|
|
96
121
|
};
|
|
97
122
|
|
|
98
123
|
/**
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
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
|
|
151
|
+
* @return Promise of flat rows
|
|
119
152
|
*/
|
|
120
|
-
fetchCollection<M extends Record<string, unknown> = Record<string, unknown>>(props: FetchCollectionProps<M>): Promise<
|
|
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
|
|
164
|
+
* Retrieve a single row given a path and a collection
|
|
132
165
|
* @param props
|
|
133
166
|
*/
|
|
134
|
-
|
|
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
|
|
170
|
+
* Get realtime updates on one row.
|
|
138
171
|
* @param props
|
|
139
172
|
* @return Function to cancel subscription
|
|
140
173
|
*/
|
|
141
|
-
|
|
174
|
+
listenOne?<M extends Record<string, unknown> = Record<string, unknown>>(props: ListenOneProps<M>): () => void;
|
|
142
175
|
|
|
143
176
|
/**
|
|
144
|
-
* Save
|
|
177
|
+
* Save a row to the specified path
|
|
145
178
|
* @param props
|
|
146
179
|
*/
|
|
147
|
-
|
|
180
|
+
save<M extends Record<string, unknown> = Record<string, unknown>>(props: SaveProps<M>): Promise<Record<string, unknown>>;
|
|
148
181
|
|
|
149
182
|
/**
|
|
150
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
176
|
-
collection?:
|
|
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
|
-
|
|
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:
|
|
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 `
|
|
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
|
|
248
|
-
*
|
|
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
|
-
|
|
328
|
+
fetchOneForRest(
|
|
276
329
|
collectionPath: string,
|
|
277
|
-
|
|
330
|
+
id: string | number,
|
|
278
331
|
include?: string[],
|
|
279
332
|
databaseId?: string
|
|
280
333
|
): Promise<Record<string, unknown> | null>;
|
package/src/controllers/email.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Email service types — portable interface shared by RebaseClient and server
|
|
2
|
+
* Email service types — portable interface shared by RebaseClient and server.
|
|
3
3
|
*
|
|
4
|
-
* The concrete SMTP implementation lives in `@rebasepro/server
|
|
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
|
*/
|
package/src/controllers/index.ts
CHANGED
|
@@ -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 "./
|
|
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 {
|
|
1
|
+
import type { CollectionConfig } from "../types/collections";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @group Models
|
|
5
5
|
*/
|
|
6
|
-
export type
|
|
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:
|
|
15
|
-
getCollectionConfig: <M extends Record<string, unknown> = Record<string, unknown>>(path: string) =>
|
|
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 {
|
|
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?:
|
|
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 {
|
|
3
|
-
import type {
|
|
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 `<
|
|
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
|
|
25
|
-
collections?: 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:
|
|
82
|
+
cmsConfig: RebaseAdminConfig | null;
|
|
69
83
|
studioConfig: RebaseStudioConfig | null;
|
|
70
84
|
authConfig: RebaseAuthConfig | null;
|
|
71
85
|
|
|
72
86
|
// Registration functions
|
|
73
|
-
registerCMS: (config:
|
|
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 {
|
|
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
|
|
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?:
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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
package/src/rebase_context.tsx
CHANGED
|
@@ -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 {
|
|
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:
|
|
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
|
|
84
|
+
* Controller to open the side panel displaying entity forms
|
|
81
85
|
*/
|
|
82
|
-
|
|
86
|
+
sidePanelController?: import("./controllers/side_panel_controller").SidePanelController;
|
|
83
87
|
|
|
84
88
|
/**
|
|
85
89
|
* Controller resolving URLs in the CMS
|