@rebasepro/agent-skills 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/package.json +9 -3
- package/skills/rebase-auth/SKILL.md +26 -8
- package/skills/rebase-basics/SKILL.md +7 -2
- package/skills/rebase-collections/SKILL.md +41 -0
- package/skills/rebase-cron-jobs/SKILL.md +5 -0
- package/skills/rebase-custom-functions/SKILL.md +21 -0
- package/skills/rebase-deployment/SKILL.md +11 -2
- package/skills/rebase-sdk/SKILL.md +23 -0
- package/skills/rebase-security/SKILL.md +9 -0
- package/skills/rebase-storage/SKILL.md +172 -8
- package/skills/rebase-ui-components/SKILL.md +91 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rebase
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/agent-skills",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Bundled AI agent skills for Rebase",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/rebasepro/rebase.git",
|
|
10
|
+
"directory": "rebase-agent-skills"
|
|
11
|
+
},
|
|
7
12
|
"files": [
|
|
8
13
|
"skills/"
|
|
9
14
|
],
|
|
@@ -11,5 +16,6 @@
|
|
|
11
16
|
".": "./package.json",
|
|
12
17
|
"./package.json": "./package.json",
|
|
13
18
|
"./skills": "./skills/"
|
|
14
|
-
}
|
|
15
|
-
}
|
|
19
|
+
},
|
|
20
|
+
"scripts": {}
|
|
21
|
+
}
|
|
@@ -5,7 +5,7 @@ description: Guide for setting up and using Rebase Authentication, roles, Row-Le
|
|
|
5
5
|
|
|
6
6
|
# Rebase Authentication
|
|
7
7
|
|
|
8
|
-
Rebase ships a complete, built-in authentication system with JWT sessions, OAuth, MFA/TOTP, API keys, Row-Level Security, and lifecycle hooks — or you can plug in an external
|
|
8
|
+
Rebase ships a complete, built-in authentication system with JWT sessions, OAuth, MFA/TOTP, API keys, Row-Level Security, and lifecycle hooks — or you can plug in an external auth system (e.g., Clerk, Auth0, or custom identity providers) via the `AuthAdapter` interface.
|
|
9
9
|
|
|
10
10
|
> **IMPORTANT FOR AGENTS:** Always read the `rebase-basics` skill first. The auth system is configured inside `initializeRebaseBackend()` which is covered there.
|
|
11
11
|
|
|
@@ -33,7 +33,7 @@ Rebase ships a complete, built-in authentication system with JWT sessions, OAuth
|
|
|
33
33
|
|
|
34
34
|
Authentication is configured via the `auth` property of `initializeRebaseBackend()`. It accepts **either** a `RebaseAuthConfig` object (built-in auth) or an `AuthAdapter` (external auth).
|
|
35
35
|
|
|
36
|
-
> **Auth & multiple data sources.** The built-in auth system (users, sessions, API keys) is bootstrapped on the **default** data source — the auth collection must live there (the backend warns at boot otherwise). **RLS only protects Postgres**: server collections on engines without row-level security (e.g. MongoDB) still require authentication but enforce authorization at the app layer (the backend warns for these). **Direct data sources (e.g. Firestore) bypass Rebase auth entirely** — they're governed by the external backend's own rules/token; use an `AuthAdapter`
|
|
36
|
+
> **Auth & multiple data sources.** The built-in auth system (users, sessions, API keys) is bootstrapped on the **default** data source — the auth collection must live there (the backend warns at boot otherwise). **RLS only protects Postgres**: server collections on engines without row-level security (e.g. MongoDB) still require authentication but enforce authorization at the app layer (the backend warns for these). **Direct data sources (e.g. Firestore) bypass Rebase auth entirely** — they're governed by the external backend's own rules/token; use an `AuthAdapter` to unify identity. See the **rebase-collections** skill for the data-source model.
|
|
37
37
|
|
|
38
38
|
### RebaseAuthConfig
|
|
39
39
|
|
|
@@ -47,7 +47,7 @@ Authentication is configured via the `auth` property of `initializeRebaseBackend
|
|
|
47
47
|
| `allowRegistration` | `boolean` | `false` | Enable self-service registration via `POST /auth/register`. |
|
|
48
48
|
| `serviceKey` | `string` | — | Static secret for server-to-server auth. Must be ≥ 32 characters. Requests with `Authorization: Bearer <serviceKey>` get admin access. |
|
|
49
49
|
| `defaultRole` | `string` | — | Role ID assigned to new users (except the first user, who always gets `"admin"`). **Must NOT be `"admin"`** — throws a security error at startup. |
|
|
50
|
-
| `providers` | `OAuthProvider<
|
|
50
|
+
| `providers` | `OAuthProvider<unknown>[]` | `[]` | **Canonical** OAuth provider array. Use `create*Provider` factories or pass custom providers. Named shorthand fields below are merged into this array at startup. |
|
|
51
51
|
| `hooks` | `AuthHooks` | — | [Lifecycle hooks](#auth-lifecycle-hooks) to customize passwords, credentials, and auth events. |
|
|
52
52
|
| `email` | `EmailConfig` | — | [Email configuration](#email-configuration) for password resets, verification, and welcome emails. |
|
|
53
53
|
| `google` | `{ clientId, clientSecret? }` | — | Google OAuth shorthand. |
|
|
@@ -351,14 +351,14 @@ hooks: {
|
|
|
351
351
|
}
|
|
352
352
|
```
|
|
353
353
|
|
|
354
|
-
### Example:
|
|
354
|
+
### Example: External Token Bridge (e.g. custom auth system)
|
|
355
355
|
|
|
356
356
|
```typescript
|
|
357
357
|
import admin from "firebase-admin";
|
|
358
358
|
|
|
359
359
|
hooks: {
|
|
360
360
|
transformAuthResponse: async (response, context) => {
|
|
361
|
-
// Generate a
|
|
361
|
+
// Generate a custom provider token for the authenticated user
|
|
362
362
|
const firebaseToken = await admin.auth().createCustomToken(context.userId);
|
|
363
363
|
return {
|
|
364
364
|
...response,
|
|
@@ -371,7 +371,7 @@ hooks: {
|
|
|
371
371
|
}
|
|
372
372
|
```
|
|
373
373
|
|
|
374
|
-
The frontend can then call `signInWithCustomToken(
|
|
374
|
+
The frontend can then call `signInWithCustomToken(providerToken)` immediately after login.
|
|
375
375
|
|
|
376
376
|
---
|
|
377
377
|
|
|
@@ -664,7 +664,7 @@ All login/register/OAuth endpoints return:
|
|
|
664
664
|
}
|
|
665
665
|
```
|
|
666
666
|
|
|
667
|
-
> **TIP:** Use the `transformAuthResponse` hook to inject additional tokens (e.g.,
|
|
667
|
+
> **TIP:** Use the `transformAuthResponse` hook to inject additional tokens (e.g., external system tokens) or metadata into this response. See [Auth Lifecycle Hooks](#auth-lifecycle-hooks).
|
|
668
668
|
|
|
669
669
|
### Error Response Format
|
|
670
670
|
|
|
@@ -857,6 +857,21 @@ Requests with the `serviceKey` bypass RLS — they receive admin-level access wi
|
|
|
857
857
|
|
|
858
858
|
API keys use a service identity for RLS scoping: `uid: "api-key:{id}"`, `roles: ["service"]` (or `["admin", "service"]` when `admin: true`). They do not inherit the `created_by` user's identity.
|
|
859
859
|
|
|
860
|
+
### Reserved System Identities
|
|
861
|
+
|
|
862
|
+
The auth middleware assigns these reserved identities automatically. They are visible in `context.user` (Entity Callbacks / DataHooks) and `c.get("user")` (custom functions):
|
|
863
|
+
|
|
864
|
+
| Auth Method | `userId` | `roles` | When It Occurs |
|
|
865
|
+
|---|---|---|---|
|
|
866
|
+
| JWT (end-user) | Real user ID (e.g. `"abc123"`) | User's assigned roles (e.g. `["viewer"]`) | Normal authenticated requests |
|
|
867
|
+
| Service Key | `"service"` | `["admin"]` | Server-side `rebase.data` calls, cron jobs, or any request with `Authorization: Bearer <serviceKey>` |
|
|
868
|
+
| API Key (default) | `"api-key:{id}"` | `["service"]` | Machine-to-machine API key requests |
|
|
869
|
+
| API Key (admin) | `"api-key:{id}"` | `["admin", "service"]` | Admin API key requests |
|
|
870
|
+
| Anonymous | `"anon"` | `["anon"]` | Unauthenticated when `requireAuth: false` |
|
|
871
|
+
| No token + `requireAuth: true` | — | — | **Rejected (401)** |
|
|
872
|
+
|
|
873
|
+
> **IMPORTANT FOR AGENTS:** Server-side `rebase.data` (the singleton used in cron jobs, custom functions, and webhooks) is built with `createRebaseClient({ token: serviceKey })`. It round-trips through the REST API, so all middleware, DataHooks, and Entity Callbacks fire with `userId: "service"`, `roles: ["admin"]`. This lets developers distinguish server-internal reads from end-user reads in callbacks.
|
|
874
|
+
|
|
860
875
|
---
|
|
861
876
|
|
|
862
877
|
## Rate Limiting
|
|
@@ -917,7 +932,7 @@ const myLimiter = createRateLimiter({
|
|
|
917
932
|
|
|
918
933
|
## Custom Auth Adapters
|
|
919
934
|
|
|
920
|
-
For external auth systems (Clerk, Auth0,
|
|
935
|
+
For external auth systems (Clerk, Auth0, custom providers, or custom JWT), use the `AuthAdapter` interface or the `createCustomAuthAdapter()` helper.
|
|
921
936
|
|
|
922
937
|
### AuthAdapter Interface
|
|
923
938
|
|
|
@@ -1241,6 +1256,8 @@ When a request includes `Authorization: Bearer <serviceKey>`:
|
|
|
1241
1256
|
- Comparison is done with constant-time comparison to prevent timing attacks.
|
|
1242
1257
|
- Must be ≥ 32 characters (validated at startup).
|
|
1243
1258
|
|
|
1259
|
+
> **TIP:** In Entity Callbacks and DataHooks, server-side `rebase.data` calls appear as `userId: "service"`, `roles: ["admin"]`. Use this to skip masking, bypass rate limits, or grant elevated access in your callback logic.
|
|
1260
|
+
|
|
1244
1261
|
### Token Rotation
|
|
1245
1262
|
|
|
1246
1263
|
Refresh tokens are rotated on every use:
|
|
@@ -1281,3 +1298,4 @@ All auth endpoints validate input with Zod schemas:
|
|
|
1281
1298
|
- Source: `packages/types/src/types/auth_adapter.ts` — `AuthAdapter` interface
|
|
1282
1299
|
- Source: `packages/server-core/src/auth/rls-scope.ts` — RLS scoping
|
|
1283
1300
|
- Source: `packages/server-core/src/email/types.ts` — Email configuration
|
|
1301
|
+
- **Reserved Identities**: `"service"` / `"anon"` / `"api-key:{id}"` — see [Row-Level Security > Reserved System Identities](#reserved-system-identities)
|
|
@@ -388,7 +388,7 @@ When `extend` is provided, the base `rebaseEnvSchema` is merged (`.merge()`) wit
|
|
|
388
388
|
| `DB_POOL_MAX` | `string` → `number` | `"20"` | No | Max database pool connections |
|
|
389
389
|
| `DB_POOL_IDLE_TIMEOUT` | `string` → `number` | `"30000"` | No | Pool idle timeout (ms) |
|
|
390
390
|
| `DB_POOL_CONNECT_TIMEOUT` | `string` → `number` | `"10000"` | No | Pool connect timeout (ms) |
|
|
391
|
-
| `STORAGE_TYPE` | `"local" \| "s3"` | `"local"` | No | File storage backend type |
|
|
391
|
+
| `STORAGE_TYPE` | `"local" \| "s3" \| "gcs"` | `"local"` | No | File storage backend type |
|
|
392
392
|
| `STORAGE_PATH` | `string` | — | No | Local storage directory path |
|
|
393
393
|
| `FORCE_LOCAL_STORAGE` | `"true" \| "false"` | — | No | Force local storage even in production |
|
|
394
394
|
| `S3_BUCKET` | `string` | — | When S3 | S3 bucket name |
|
|
@@ -397,6 +397,9 @@ When `extend` is provided, the base `rebaseEnvSchema` is merged (`.merge()`) wit
|
|
|
397
397
|
| `S3_SECRET_ACCESS_KEY` | `string` | — | When S3 | S3 secret key |
|
|
398
398
|
| `S3_ENDPOINT` | `string` (URL) | — | No | Custom S3 endpoint (MinIO, R2, etc.) |
|
|
399
399
|
| `S3_FORCE_PATH_STYLE` | `"true" \| "false"` | — | No | Use path-style S3 URLs |
|
|
400
|
+
| `GCS_BUCKET` | `string` | — | When GCS | GCS/Firebase Storage bucket name |
|
|
401
|
+
| `GCS_PROJECT_ID` | `string` | — | When GCS | GCP project ID |
|
|
402
|
+
| `GOOGLE_APPLICATION_CREDENTIALS` | `string` (path) | — | When GCS | Path to GCP service account JSON |
|
|
400
403
|
|
|
401
404
|
### Production Validations
|
|
402
405
|
|
|
@@ -438,7 +441,7 @@ import { initializeRebaseBackend, RebaseBackendConfig } from "@rebasepro/server-
|
|
|
438
441
|
| `bootstrappers` | `BackendBootstrapper[]` | `[]` | Database bootstrappers. Use one per engine for multiple engines in a single instance (e.g. Postgres + MongoDB); mark one `isDefault` |
|
|
439
442
|
| `dataSources` | `DataSourceDefinition[]` | `[]` | Declared data sources (`key`, `engine`, `transport`). Drives capabilities and the server-vs-direct distinction. Collections on a `direct`/`custom` transport are client-only — the backend skips data routes for them. Server engines need no entry |
|
|
440
443
|
| `auth` | `RebaseAuthConfig \| AuthAdapter` | — | Authentication config or pluggable adapter |
|
|
441
|
-
| `storage` | `BackendStorageConfig \| StorageController \| Record<string, ...>` | — | File storage configuration |
|
|
444
|
+
| `storage` | `BackendStorageConfig \| StorageController \| Record<string, ...>` | — | File storage configuration. Supports `"local"`, `"s3"`, and `"gcs"` (GCS/Firebase Storage) backends. Use `Record<string, StorageController>` for multi-backend setups with named sources |
|
|
442
445
|
| `history` | `unknown` | — | Entity history/audit-log configuration |
|
|
443
446
|
| `defaultSecurityRules` | `SecurityRule[]` | — | Default RLS rules for collections without their own |
|
|
444
447
|
| `enableSwagger` | `boolean` | `true` | Enable OpenAPI spec at `/api/docs` and Swagger UI at `/api/swagger` (dev only) |
|
|
@@ -546,6 +549,8 @@ await initializeRebaseBackend({
|
|
|
546
549
|
? { clientId: env.GOOGLE_CLIENT_ID }
|
|
547
550
|
: undefined,
|
|
548
551
|
},
|
|
552
|
+
// Single backend: { type: "local" | "s3" | "gcs" }
|
|
553
|
+
// Multi-backend: Record<string, StorageController> with named sources
|
|
549
554
|
storage: { type: env.STORAGE_TYPE },
|
|
550
555
|
defaultSecurityRules: [
|
|
551
556
|
{ operation: "select", access: "public" },
|
|
@@ -367,6 +367,23 @@ avatar: {
|
|
|
367
367
|
| `postProcess` | `(pathOrUrl) => Promise<string>` | — | Transform saved path/URL after upload |
|
|
368
368
|
| `includeBucketUrl` | `boolean` | `false` | Include bucket URL in saved path |
|
|
369
369
|
| `storeUrl` | `boolean` | `false` | Save download URL instead of storage path |
|
|
370
|
+
| `storageSource` | `string` | `undefined` (default backend) | Named storage source key — routes uploads to a specific backend registered in the backend `storage` map or in `storageSources` on `<Rebase>`. Must match a `StorageSourceDefinition.key`. |
|
|
371
|
+
|
|
372
|
+
#### Per-Property Backend Binding
|
|
373
|
+
|
|
374
|
+
When using multiple storage backends, use `storageSource` to route a specific property's uploads to a named backend:
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
// Route this property's uploads to Firebase Storage
|
|
378
|
+
image: {
|
|
379
|
+
type: "string",
|
|
380
|
+
storage: {
|
|
381
|
+
storageSource: "firebase",
|
|
382
|
+
storagePath: "products/{entityId}",
|
|
383
|
+
acceptedFiles: ["image/*"],
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
```
|
|
370
387
|
|
|
371
388
|
### ImageResize Options
|
|
372
389
|
|
|
@@ -892,6 +909,30 @@ All callbacks receive a `context` property of type `RebaseCallContext<USER>`. Th
|
|
|
892
909
|
| `context.storageSource` | `StorageSource` | File storage operations |
|
|
893
910
|
| `context.user` | `USER \| undefined` | Authenticated user (set by backend in server-side callbacks) |
|
|
894
911
|
|
|
912
|
+
### Reserved `context.user` Values
|
|
913
|
+
|
|
914
|
+
The `context.user` object is populated by the auth middleware. In server-side callbacks, it contains one of these reserved identities:
|
|
915
|
+
|
|
916
|
+
| Caller | `context.user.uid` | `context.user.roles` |
|
|
917
|
+
|---|---|---|
|
|
918
|
+
| JWT-authenticated end-user | Real user ID (e.g. `"abc123"`) | Their assigned roles (e.g. `["viewer"]`) |
|
|
919
|
+
| Server-side `rebase.data` (cron jobs, custom functions) | `"service"` | `["admin"]` |
|
|
920
|
+
| API key (default) | `"api-key:{id}"` | `["service"]` |
|
|
921
|
+
| API key (admin) | `"api-key:{id}"` | `["admin", "service"]` |
|
|
922
|
+
| Anonymous (no auth, `requireAuth: false`) | `"anon"` | `["anon"]` |
|
|
923
|
+
| Anonymous REST (no token) | `undefined` | N/A — `context.user` is not set; only the DataDriver is scoped |
|
|
924
|
+
|
|
925
|
+
> **IMPORTANT FOR AGENTS:** `rebase.data` calls (used in cron jobs, afterSave side-effects, custom functions) go through the full middleware pipeline with the service key, so callbacks see `uid: "service"`, `roles: ["admin"]`. Use this to gate behavior — e.g., skip PII masking for admin/service reads:
|
|
926
|
+
>
|
|
927
|
+
> ```typescript
|
|
928
|
+
> afterRead: async ({ entity, context }) => {
|
|
929
|
+
> // Server-side reads (cron jobs, admin) see real values
|
|
930
|
+
> if (context.user?.roles?.includes("admin")) return entity;
|
|
931
|
+
> // End-user reads get masked values
|
|
932
|
+
> return { ...entity, values: { ...entity.values, email: "***@***.***" } };
|
|
933
|
+
> }
|
|
934
|
+
> ```
|
|
935
|
+
|
|
895
936
|
> **WARNING FOR AGENTS:** Do NOT confuse `RebaseCallContext` (available in callbacks, both client & server) with `RebaseContext` (full context available only on the frontend, includes `authController`, `snackbarController`, `sideEntityController`, etc.). Entity callbacks always receive `RebaseCallContext`.
|
|
896
937
|
|
|
897
938
|
### Callback Example
|
|
@@ -230,6 +230,11 @@ interface CronJobContext {
|
|
|
230
230
|
| `log` | `(...args: unknown[]) => void` | Logger whose output is captured in `CronJobLogEntry.logs`. Use like `console.log`. |
|
|
231
231
|
| `client` | `RebaseClient` | Admin-level client for CRUD, auth, storage, and any SDK operation. |
|
|
232
232
|
|
|
233
|
+
> **IMPORTANT FOR AGENTS:** `ctx.client` authenticates as the **service identity** (`userId: "service"`, `roles: ["admin"]`). All data operations through `ctx.client` go through the full REST middleware pipeline (including DataHooks and Entity Callbacks). This means:
|
|
234
|
+
> - Entity Callbacks will see `context.user.uid === "service"` and `context.user.roles` containing `"admin"`
|
|
235
|
+
> - If your callbacks implement PII masking or role-based filtering, they should check for admin/service roles and skip masking for server-internal reads
|
|
236
|
+
> - The service identity bypasses RLS policies
|
|
237
|
+
|
|
233
238
|
### Using `ctx.client`
|
|
234
239
|
|
|
235
240
|
`ctx.client` is the same `RebaseClient` returned by `createRebaseClient()`. It supports all SDK operations:
|
|
@@ -118,6 +118,27 @@ app.get("/", async (c) => {
|
|
|
118
118
|
export default app;
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
+
### Reserved Identity Values in `c.get("user")`
|
|
122
|
+
|
|
123
|
+
The `user` object set by the auth middleware uses reserved values for system identities:
|
|
124
|
+
|
|
125
|
+
| Auth Method | `user.userId` | `user.roles` |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| JWT (end-user) | Real user ID | User's assigned roles |
|
|
128
|
+
| Service Key | `"service"` | `["admin"]` |
|
|
129
|
+
| API Key (default) | `"api-key:{id}"` | `["service"]` |
|
|
130
|
+
| API Key (admin) | `"api-key:{id}"` | `["admin", "service"]` |
|
|
131
|
+
| Anonymous | `"anon"` | `["anon"]` |
|
|
132
|
+
|
|
133
|
+
> **TIP:** Use these to differentiate internal vs. external callers in your custom functions:
|
|
134
|
+
> ```typescript
|
|
135
|
+
> app.get("/sensitive-data", async (c) => {
|
|
136
|
+
> const user = c.get("user");
|
|
137
|
+
> const isInternal = user?.userId === "service" || user?.roles?.includes("admin");
|
|
138
|
+
> // Return full or masked data based on identity
|
|
139
|
+
> });
|
|
140
|
+
> ```
|
|
141
|
+
|
|
121
142
|
## Invoking Functions from the Frontend
|
|
122
143
|
|
|
123
144
|
> **CRITICAL FOR AGENTS**: When calling custom backend functions from the frontend, **ALWAYS** use `client.functions.invoke()`. **NEVER** use raw `fetch()`, manually construct URLs, or manually extract auth tokens from `localStorage`. The SDK handles all of this automatically.
|
|
@@ -367,7 +367,7 @@ All variables recognized by the base `rebaseEnvSchema`:
|
|
|
367
367
|
| `DB_POOL_CONNECT_TIMEOUT` | `string` → `number` | No | `"10000"` → `10000` | Connection timeout (ms) |
|
|
368
368
|
| `DATABASE_DIRECT_URL` | `string` (URL) | No | — | Direct database URL (bypasses pooler) |
|
|
369
369
|
| `DATABASE_READ_URL` | `string` (URL) | No | — | Read replica database URL |
|
|
370
|
-
| `STORAGE_TYPE` | `enum` | No | `"local"` | `"local"` or `"
|
|
370
|
+
| `STORAGE_TYPE` | `enum` | No | `"local"` | `"local"`, `"s3"`, or `"gcs"` |
|
|
371
371
|
| `STORAGE_PATH` | `string` | No | — | Local file storage directory |
|
|
372
372
|
| `FORCE_LOCAL_STORAGE` | `optionalBoolString` | No | `undefined` → `false` | Suppress local-storage-in-production warning |
|
|
373
373
|
| `S3_BUCKET` | `string` | If S3 | — | S3 bucket name |
|
|
@@ -376,6 +376,9 @@ All variables recognized by the base `rebaseEnvSchema`:
|
|
|
376
376
|
| `S3_SECRET_ACCESS_KEY` | `string` | If S3 | — | S3 secret key |
|
|
377
377
|
| `S3_ENDPOINT` | `string` (URL) | No | — | Custom S3 endpoint (MinIO, R2) |
|
|
378
378
|
| `S3_FORCE_PATH_STYLE` | `optionalBoolString` | No | `undefined` → `false` | Use path-style S3 URLs (required for MinIO) |
|
|
379
|
+
| `GCS_BUCKET` | `string` | If GCS | — | Google Cloud Storage bucket name |
|
|
380
|
+
| `GCS_PROJECT_ID` | `string` | If GCS | — | GCP project ID for GCS |
|
|
381
|
+
| `GOOGLE_APPLICATION_CREDENTIALS` | `string` | If GCS (non-GCP) | — | Path to GCP service account JSON key file (not needed on GCP with default credentials) |
|
|
379
382
|
|
|
380
383
|
### Zod Type Helpers
|
|
381
384
|
|
|
@@ -573,7 +576,13 @@ Internet → Cloud Run (HTTPS, auto-scaling) → Cloud SQL PostgreSQL
|
|
|
573
576
|
--allow-unauthenticated
|
|
574
577
|
```
|
|
575
578
|
4. **Custom Domain** — Map a custom domain in Cloud Run settings. Cloud Run provides HTTPS automatically.
|
|
576
|
-
5. **File Storage** — Use
|
|
579
|
+
5. **File Storage** — Use native GCS support with `STORAGE_TYPE=gcs`:
|
|
580
|
+
```bash
|
|
581
|
+
STORAGE_TYPE=gcs
|
|
582
|
+
GCS_BUCKET=your-rebase-uploads
|
|
583
|
+
GCS_PROJECT_ID=your-gcp-project-id
|
|
584
|
+
```
|
|
585
|
+
On Cloud Run, the default service account credentials are used automatically — no key file needed. Alternatively, you can fall back to `STORAGE_TYPE=s3` with the GCS S3-compatible interop endpoint.
|
|
577
586
|
|
|
578
587
|
### Cloud SQL Auth Proxy (Connection String)
|
|
579
588
|
|
|
@@ -437,6 +437,29 @@ const { items, nextPageToken } = await rebase.storage.listObjects('prefix/', {
|
|
|
437
437
|
});
|
|
438
438
|
```
|
|
439
439
|
|
|
440
|
+
### Multi-Backend Storage
|
|
441
|
+
|
|
442
|
+
`rebase.storage` accesses the **default** storage source. For multi-backend setups (e.g. S3 + GCS/Firebase), collection properties can specify `storage.storageSource` in their schema to route uploads to a named backend.
|
|
443
|
+
|
|
444
|
+
On the frontend, register direct storage sources via the `storageSources` prop on `<Rebase>`:
|
|
445
|
+
|
|
446
|
+
```tsx
|
|
447
|
+
import { getStorage } from "firebase/storage";
|
|
448
|
+
|
|
449
|
+
const firebaseStorageSource = getStorage(firebaseApp);
|
|
450
|
+
|
|
451
|
+
<Rebase
|
|
452
|
+
client={rebaseClient}
|
|
453
|
+
storageSources={[
|
|
454
|
+
{ key: "firebase", engine: "firebase", transport: "direct", source: firebaseStorageSource }
|
|
455
|
+
]}
|
|
456
|
+
>
|
|
457
|
+
{children}
|
|
458
|
+
</Rebase>
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
The system resolves the correct source per-property via `StorageSourcesContext` (mirroring `DataSourcesContext`). Properties bound to a `direct` storage source bypass the Rebase backend and upload/download directly to the provider (e.g. Firebase Storage), while `server` sources route through the Rebase API with a `?storageId` query parameter for backend routing.
|
|
462
|
+
|
|
440
463
|
## Custom Functions
|
|
441
464
|
|
|
442
465
|
Invoke custom server-side functions defined in the backend:
|
|
@@ -136,6 +136,14 @@ export async function scopeDataDriver(
|
|
|
136
136
|
| Anonymous (`requireAuth: false`) | `"anon"` | `["anon"]` | RLS with anonymous identity |
|
|
137
137
|
| No token + `requireAuth: true` | — | — | **Rejected (401)** |
|
|
138
138
|
|
|
139
|
+
> **IMPORTANT FOR AGENTS:** These are **reserved system identity values** that the middleware injects automatically. When writing DataHooks or Entity Callbacks, developers should use these identities to gate behavior:
|
|
140
|
+
> - `uid: "service"` + `roles: ["admin"]` — Server-side `rebase.data` calls (cron jobs, custom functions using `rebase.data`, webhooks). These go through the full middleware pipeline authenticated with the service key.
|
|
141
|
+
> - `uid: "anon"` + `roles: ["anon"]` — Unauthenticated requests when `requireAuth: false`. **Note:** for anonymous REST requests, `context.user` in Entity Callbacks may be `undefined`; only the DataDriver is scoped with the anon identity. For WebSocket connections, a full `User` object with `uid: "anon"` is provided.
|
|
142
|
+
> - `uid: "api-key:{id}"` + `roles: ["service"]` (or `["admin", "service"]`) — API key requests.
|
|
143
|
+
> - Real user IDs and roles for JWT-authenticated requests.
|
|
144
|
+
>
|
|
145
|
+
> **Key insight:** `rebase.data` (the server-side singleton) is NOT a raw admin driver — it round-trips through the REST API using the service key, so all DataHooks and Entity Callbacks fire with `uid: "service"`, `roles: ["admin"]`. This means callbacks can distinguish server-internal reads from end-user reads by checking `context.user?.roles?.includes("admin")`.
|
|
146
|
+
|
|
139
147
|
---
|
|
140
148
|
|
|
141
149
|
## Layer 2: API Key Permission Guard
|
|
@@ -602,5 +610,6 @@ Use this checklist when setting up security for a Rebase project:
|
|
|
602
610
|
- **REST API Generator**: `packages/server-core/src/api/rest/api-generator.ts` — DataHooks integration
|
|
603
611
|
- **Backend Hooks Types**: `packages/types/src/types/backend_hooks.ts` — `DataHooks`, `BackendHooks` interfaces
|
|
604
612
|
- **Backend Init**: `packages/server-core/src/init.ts` — `hooks` config property
|
|
613
|
+
- **Reserved Identity Values**: See Identity Types table above — `"service"`, `"anon"`, `"api-key:{id}"` are system-assigned identities in `context.user` / `ctx.requestUser`
|
|
605
614
|
- **Entity Callbacks**: See `rebase-collections` skill → Entity Callbacks section
|
|
606
615
|
- **Auth Configuration**: See `rebase-auth` skill → Server-Side Configuration section
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: rebase-storage
|
|
3
|
-
description: Guide for setting up and using file storage in Rebase. Use this skill when the user needs to configure S3 or local file storage, handle file uploads, TUS resumable uploads, image transformations, or integrate the media manager.
|
|
3
|
+
description: Guide for setting up and using file storage in Rebase. Use this skill when the user needs to configure S3, Google Cloud Storage (GCS), or local file storage, handle file uploads, TUS resumable uploads, image transformations, multi-backend frontend storage sources, or integrate the media manager.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Rebase Storage
|
|
7
7
|
|
|
8
|
-
Rebase provides built-in file storage with support for local filesystem
|
|
8
|
+
Rebase provides built-in file storage with support for local filesystem, S3-compatible services, and Google Cloud Storage (GCS), TUS v1.0.0 resumable uploads, on-the-fly image transformation, a multi-backend registry, and frontend storage sources.
|
|
9
9
|
|
|
10
10
|
> **IMPORTANT FOR AGENTS:** Always read the `rebase-basics` skill first before using this skill. Storage requires a running Rebase backend with `initializeRebaseBackend()`.
|
|
11
11
|
|
|
@@ -15,7 +15,7 @@ The `storage` option in `initializeRebaseBackend()` accepts three forms:
|
|
|
15
15
|
|
|
16
16
|
| Form | Type | Description |
|
|
17
17
|
|------|------|-------------|
|
|
18
|
-
| Single config | `BackendStorageConfig` | `{ type: 'local' | 's3', ... }` — creates a single `(default)` backend |
|
|
18
|
+
| Single config | `BackendStorageConfig` | `{ type: 'local' | 's3' | 'gcs', ... }` — creates a single `(default)` backend |
|
|
19
19
|
| Single controller | `StorageController` | A custom controller instance — registered as `(default)` |
|
|
20
20
|
| Multi-backend map | `Record<string, BackendStorageConfig \| StorageController>` | Named backends, first becomes `(default)` if no `(default)` key |
|
|
21
21
|
|
|
@@ -44,6 +44,19 @@ The `storage` option in `initializeRebaseBackend()` accepts three forms:
|
|
|
44
44
|
| `allowedMimeTypes` | `string[]` | `undefined` (all allowed) | Restrict uploads to these MIME types |
|
|
45
45
|
| `signedUrlExpiration` | `number` | `3600` | Signed URL expiry in seconds |
|
|
46
46
|
|
|
47
|
+
### GCSStorageConfig
|
|
48
|
+
|
|
49
|
+
| Property | Type | Default | Description |
|
|
50
|
+
|----------|------|---------|-------------|
|
|
51
|
+
| `type` | `"gcs"` | — | **Required.** Must be `"gcs"` |
|
|
52
|
+
| `bucket` | `string` | — | **Required.** GCS bucket name |
|
|
53
|
+
| `projectId` | `string` | `undefined` | Google Cloud project ID (auto-detected from credentials if omitted) |
|
|
54
|
+
| `maxFileSize` | `number` | `52428800` (50 MB) | Maximum file size in bytes |
|
|
55
|
+
| `allowedMimeTypes` | `string[]` | `undefined` (all allowed) | Restrict uploads to these MIME types |
|
|
56
|
+
| `signedUrlExpiration` | `number` | `3600` | Signed URL expiry in seconds |
|
|
57
|
+
|
|
58
|
+
> **IMPORTANT FOR AGENTS:** GCS requires `@google-cloud/storage` as a peer dependency. Install it: `pnpm add @google-cloud/storage`. Authentication uses Application Default Credentials — set the `GOOGLE_APPLICATION_CREDENTIALS` environment variable to point to a service account key file, or rely on the default credentials when running on GCP.
|
|
59
|
+
|
|
47
60
|
### StorageRoutesConfig
|
|
48
61
|
|
|
49
62
|
These options are set internally when mounting routes but are derived from the backend config:
|
|
@@ -93,7 +106,7 @@ Local storage includes **path traversal protection** — any resolved path that
|
|
|
93
106
|
|
|
94
107
|
### S3-Compatible Storage
|
|
95
108
|
|
|
96
|
-
Works with AWS S3, MinIO, DigitalOcean Spaces, Cloudflare R2, Hetzner Object Storage, Backblaze B2, and any S3-compatible service.
|
|
109
|
+
Works with AWS S3, MinIO, DigitalOcean Spaces, Cloudflare R2, Hetzner Object Storage, Backblaze B2, and any S3-compatible service.
|
|
97
110
|
|
|
98
111
|
```typescript
|
|
99
112
|
const backend = await initializeRebaseBackend({
|
|
@@ -118,6 +131,28 @@ The S3 controller:
|
|
|
118
131
|
- Supports `s3://` and `gs://` URL schemes in key parameters for backward compatibility
|
|
119
132
|
- Flattens nested metadata to string values (S3 requirement)
|
|
120
133
|
|
|
134
|
+
### Google Cloud Storage (GCS)
|
|
135
|
+
Native Google Cloud Storage support via the `@google-cloud/storage` SDK. This is the preferred approach for GCS — no S3 interop layer needed. (Also works with Firebase Storage buckets, which are GCS buckets under the hood).
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
const backend = await initializeRebaseBackend({
|
|
139
|
+
server, app,
|
|
140
|
+
bootstrappers: [/* ... */],
|
|
141
|
+
storage: {
|
|
142
|
+
type: "gcs",
|
|
143
|
+
bucket: process.env.GCS_BUCKET!,
|
|
144
|
+
projectId: process.env.GCS_PROJECT_ID, // optional, auto-detected on GCP
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The GCS controller:
|
|
150
|
+
- Uses `@google-cloud/storage` natively (no S3 interop overhead)
|
|
151
|
+
- Authenticates via Application Default Credentials (`GOOGLE_APPLICATION_CREDENTIALS`)
|
|
152
|
+
- Maps the logical bucket name `"default"` to the configured GCS bucket
|
|
153
|
+
- Supports `gs://` URL scheme in key parameters
|
|
154
|
+
- Generates signed URLs using GCS's native signed URL mechanism
|
|
155
|
+
|
|
121
156
|
### Multiple Storage Backends
|
|
122
157
|
|
|
123
158
|
Rebase supports multiple storage backends simultaneously via the `StorageRegistry`:
|
|
@@ -138,7 +173,7 @@ const backend = await initializeRebaseBackend({
|
|
|
138
173
|
});
|
|
139
174
|
```
|
|
140
175
|
|
|
141
|
-
> **IMPORTANT FOR AGENTS:** If no `"(default)"` key is provided, the first entry is automatically registered as the default (with a console warning). The REST API routes
|
|
176
|
+
> **IMPORTANT FOR AGENTS:** If no `"(default)"` key is provided, the first entry is automatically registered as the default (with a console warning). The REST API routes use the default controller unless a `?storageId=<key>` query parameter is provided (see [REST API Endpoints](#rest-api-endpoints)). Use `storageRegistry.get("media")` or `storageRegistry.getOrDefault("media")` to access named backends programmatically.
|
|
142
177
|
|
|
143
178
|
### StorageRegistry API
|
|
144
179
|
|
|
@@ -156,7 +191,7 @@ The `StorageRegistry` interface:
|
|
|
156
191
|
|
|
157
192
|
### Custom Storage Providers
|
|
158
193
|
|
|
159
|
-
Implement the `StorageController` interface for unsupported providers (Azure Blob,
|
|
194
|
+
GCS is now built-in (see [Google Cloud Storage (GCS)](#google-cloud-storage-gcs)). Implement the `StorageController` interface for other unsupported providers (Azure Blob, etc.):
|
|
160
195
|
|
|
161
196
|
```typescript
|
|
162
197
|
interface StorageController {
|
|
@@ -185,7 +220,7 @@ The backend validates storage-related environment variables via a Zod schema:
|
|
|
185
220
|
|
|
186
221
|
| Variable | Type | Default | Description |
|
|
187
222
|
|----------|------|---------|-------------|
|
|
188
|
-
| `STORAGE_TYPE` | `"local" \| "s3"` | `"local"` | Storage provider type |
|
|
223
|
+
| `STORAGE_TYPE` | `"local" \| "s3" \| "gcs"` | `"local"` | Storage provider type |
|
|
189
224
|
| `STORAGE_PATH` | `string` | `"./uploads"` | Base path for local storage / TUS temp directory |
|
|
190
225
|
| `FORCE_LOCAL_STORAGE` | `"true" \| "false"` | `false` | Suppress the production warning for local storage |
|
|
191
226
|
| `S3_BUCKET` | `string` | — | S3 bucket name |
|
|
@@ -194,8 +229,12 @@ The backend validates storage-related environment variables via a Zod schema:
|
|
|
194
229
|
| `S3_SECRET_ACCESS_KEY` | `string` | — | S3 secret access key |
|
|
195
230
|
| `S3_ENDPOINT` | `string` (URL) | — | Custom S3-compatible endpoint |
|
|
196
231
|
| `S3_FORCE_PATH_STYLE` | `"true" \| "false"` | — | Enable path-style URLs |
|
|
232
|
+
| `GCS_BUCKET` | `string` | — | GCS bucket name |
|
|
233
|
+
| `GCS_PROJECT_ID` | `string` | — | Google Cloud project ID (auto-detected on GCP) |
|
|
234
|
+
| `GOOGLE_APPLICATION_CREDENTIALS` | `string` (path) | — | Path to GCP service account key JSON file |
|
|
197
235
|
|
|
198
236
|
```env
|
|
237
|
+
# S3 example
|
|
199
238
|
STORAGE_TYPE=s3
|
|
200
239
|
S3_BUCKET=my-bucket
|
|
201
240
|
S3_REGION=us-east-1
|
|
@@ -203,12 +242,35 @@ S3_ACCESS_KEY_ID=AKIA...
|
|
|
203
242
|
S3_SECRET_ACCESS_KEY=...
|
|
204
243
|
S3_ENDPOINT=https://minio.example.com # Optional: for MinIO, R2, etc.
|
|
205
244
|
S3_FORCE_PATH_STYLE=true # Optional: for MinIO
|
|
245
|
+
|
|
246
|
+
# GCS example
|
|
247
|
+
STORAGE_TYPE=gcs
|
|
248
|
+
GCS_BUCKET=my-gcs-bucket
|
|
249
|
+
GCS_PROJECT_ID=my-gcp-project # Optional: auto-detected on GCP
|
|
250
|
+
GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
|
|
206
251
|
```
|
|
207
252
|
|
|
208
253
|
## REST API Endpoints
|
|
209
254
|
|
|
210
255
|
All storage routes are mounted at `/api/storage` by default. Write operations require authentication when `requireAuth` is `true` (the default). Read operations also require authentication unless `publicRead` is `true`.
|
|
211
256
|
|
|
257
|
+
### Multi-Backend Routing (`storageId`)
|
|
258
|
+
|
|
259
|
+
All storage endpoints accept a `?storageId=<key>` query parameter that routes the request to a specific named backend in the `StorageRegistry`. For multipart uploads (`POST /api/storage/upload`), the `storageId` can also be sent as a form field.
|
|
260
|
+
|
|
261
|
+
If `storageId` is omitted, the request is routed to the `(default)` backend.
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
# Upload to the "media" backend
|
|
265
|
+
POST /api/storage/upload?storageId=media
|
|
266
|
+
|
|
267
|
+
# Download from the "media" backend
|
|
268
|
+
GET /api/storage/file/images/photo.jpg?storageId=media
|
|
269
|
+
|
|
270
|
+
# List files in the "media" backend
|
|
271
|
+
GET /api/storage/list?prefix=images/&storageId=media
|
|
272
|
+
```
|
|
273
|
+
|
|
212
274
|
### Standard Endpoints
|
|
213
275
|
|
|
214
276
|
| Method | Endpoint | Auth | Description |
|
|
@@ -241,6 +303,7 @@ Upload a file via `multipart/form-data`.
|
|
|
241
303
|
| `file` | `File` | Yes | The file to upload |
|
|
242
304
|
| `key` | `string` | No | Storage key/path. Falls back to original filename, then `"unnamed"` |
|
|
243
305
|
| `bucket` | `string` | No | Target bucket name |
|
|
306
|
+
| `storageId` | `string` | No | Route to a named backend (alternative to `?storageId` query param) |
|
|
244
307
|
| `metadata_*` | `string` | No | Custom metadata keys prefixed with `metadata_` |
|
|
245
308
|
|
|
246
309
|
**Response** (201):
|
|
@@ -562,6 +625,24 @@ Transformed responses include `Cache-Control: public, max-age=31536000, immutabl
|
|
|
562
625
|
|
|
563
626
|
The `@rebasepro/client` package provides a `StorageSource` interface via `createStorage(transport)`. These methods are available on `client.storage` (or through the React/Studio hooks).
|
|
564
627
|
|
|
628
|
+
All SDK methods accept an optional `storageId` parameter (passed as a query parameter) to route the request to a specific named backend:
|
|
629
|
+
|
|
630
|
+
```typescript
|
|
631
|
+
// Upload to a specific backend
|
|
632
|
+
const result = await client.storage.putObject({
|
|
633
|
+
file: myFile,
|
|
634
|
+
key: "products/images/photo.jpg",
|
|
635
|
+
storageId: "media", // routes to the "media" backend
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
// Get signed URL from a specific backend
|
|
639
|
+
const config = await client.storage.getSignedUrl(
|
|
640
|
+
"products/images/photo.jpg",
|
|
641
|
+
"default", // bucket
|
|
642
|
+
{ storageId: "media" }
|
|
643
|
+
);
|
|
644
|
+
```
|
|
645
|
+
|
|
565
646
|
### putObject
|
|
566
647
|
|
|
567
648
|
Upload a file.
|
|
@@ -662,7 +743,8 @@ const productsCollection: PostgresCollection = {
|
|
|
662
743
|
name: "Product Image",
|
|
663
744
|
type: "string",
|
|
664
745
|
storage: {
|
|
665
|
-
|
|
746
|
+
storageSource: "external-source",
|
|
747
|
+
storagePath: "products/{entityId}",
|
|
666
748
|
acceptedFiles: ["image/*"],
|
|
667
749
|
maxSize: 5 * 1024 * 1024, // 5MB
|
|
668
750
|
}
|
|
@@ -673,6 +755,7 @@ const productsCollection: PostgresCollection = {
|
|
|
673
755
|
of: {
|
|
674
756
|
type: "string",
|
|
675
757
|
storage: {
|
|
758
|
+
storageSource: "media", // routes to the "media" backend
|
|
676
759
|
storagePath: "products/documents",
|
|
677
760
|
acceptedFiles: ["application/pdf", "application/msword"],
|
|
678
761
|
}
|
|
@@ -682,6 +765,87 @@ const productsCollection: PostgresCollection = {
|
|
|
682
765
|
};
|
|
683
766
|
```
|
|
684
767
|
|
|
768
|
+
The `storageSource` field maps to a named backend registered in the `StorageRegistry` (server-side) or in the `storageSources` prop (client-side). When omitted, the `(default)` backend is used.
|
|
769
|
+
|
|
770
|
+
### StorageConfig Properties
|
|
771
|
+
|
|
772
|
+
| Property | Type | Description |
|
|
773
|
+
|----------|------|-------------|
|
|
774
|
+
| `storageSource` | `string` | Named backend to route uploads to (optional, defaults to `(default)`) |
|
|
775
|
+
| `storagePath` | `string` | Path template for uploaded files. Supports `{entityId}` placeholder |
|
|
776
|
+
| `acceptedFiles` | `string[]` | Accepted MIME types or globs (e.g., `["image/*"]`) |
|
|
777
|
+
| `maxSize` | `number` | Maximum file size in bytes |
|
|
778
|
+
|
|
779
|
+
## Frontend Storage Sources
|
|
780
|
+
|
|
781
|
+
The `<Rebase>` component accepts a `storageSources` prop to register client-side storage sources, following the same pattern as `dataSources` for databases. This enables **direct** (client-to-provider) file operations, bypassing the Rebase backend.
|
|
782
|
+
|
|
783
|
+
### Registering Storage Sources
|
|
784
|
+
|
|
785
|
+
```tsx
|
|
786
|
+
import type { RebaseStorageSource } from "@rebasepro/core";
|
|
787
|
+
import { myExternalStorageSource } from "./my-external-storage";
|
|
788
|
+
|
|
789
|
+
<Rebase
|
|
790
|
+
client={rebaseClient}
|
|
791
|
+
storageSources={[
|
|
792
|
+
{
|
|
793
|
+
key: "external",
|
|
794
|
+
engine: "external",
|
|
795
|
+
transport: "direct",
|
|
796
|
+
source: myExternalStorageSource,
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
key: "media",
|
|
800
|
+
engine: "s3",
|
|
801
|
+
transport: "server", // proxied through the Rebase backend
|
|
802
|
+
label: "Media Storage",
|
|
803
|
+
},
|
|
804
|
+
]}
|
|
805
|
+
>
|
|
806
|
+
{children}
|
|
807
|
+
</Rebase>
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
### StorageSourceDefinition
|
|
811
|
+
|
|
812
|
+
| Property | Type | Description |
|
|
813
|
+
|----------|------|-------------|
|
|
814
|
+
| `key` | `string` | **Required.** Unique identifier for this storage source |
|
|
815
|
+
| `engine` | `string` | **Required.** Storage engine type (e.g., `"firebase"`, `"s3"`, `"gcs"`, `"local"`) |
|
|
816
|
+
| `transport` | `"server" \| "direct"` | **Required.** `"server"` routes through the backend; `"direct"` talks to the provider from the client |
|
|
817
|
+
| `source` | `StorageSource` | The client-side `StorageSource` implementation (required when `transport` is `"direct"`) |
|
|
818
|
+
| `label` | `string` | Optional human-readable label for the UI |
|
|
819
|
+
|
|
820
|
+
### StorageSourcesContext
|
|
821
|
+
|
|
822
|
+
Registered storage sources are exposed to the component tree via `StorageSourcesContext`, mirroring the `DataSourcesContext` pattern:
|
|
823
|
+
|
|
824
|
+
```typescript
|
|
825
|
+
import { useStorageSources } from "@rebasepro/core";
|
|
826
|
+
|
|
827
|
+
function MyUploadComponent() {
|
|
828
|
+
const storageSources = useStorageSources();
|
|
829
|
+
|
|
830
|
+
// Get a specific source
|
|
831
|
+
const externalSource = storageSources.find(s => s.key === "external");
|
|
832
|
+
|
|
833
|
+
// Use it for direct uploads
|
|
834
|
+
if (externalSource?.source) {
|
|
835
|
+
await externalSource.source.putObject({ file, key: "photos/image.jpg" });
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
### Direct vs Server Transport
|
|
841
|
+
|
|
842
|
+
| Transport | Description | Use Case |
|
|
843
|
+
|-----------|-------------|----------|
|
|
844
|
+
| `"server"` | File operations are proxied through the Rebase backend REST API | S3, GCS, or local backends managed by the server |
|
|
845
|
+
| `"direct"` | File operations go directly from the client to the storage provider | External cloud storage, or any provider with client-side SDKs |
|
|
846
|
+
|
|
847
|
+
> **IMPORTANT FOR AGENTS:** Direct transport sources must provide a `source` property implementing the `StorageSource` interface. Server transport sources use the built-in SDK transport and only need `key` and `engine`.
|
|
848
|
+
|
|
685
849
|
## Storage Browser (Studio)
|
|
686
850
|
|
|
687
851
|
The `@rebasepro/studio` package includes a built-in `StorageView` component:
|
|
@@ -848,6 +848,97 @@ Key props: `data`, `columns` (array of `VirtualTableColumn`), `cellRenderer`, `o
|
|
|
848
848
|
|
|
849
849
|
See `VirtualTableProps` and `VirtualTableColumn` types for full API.
|
|
850
850
|
|
|
851
|
+
#### Generic Editable Tables & Cell Selection
|
|
852
|
+
|
|
853
|
+
`@rebasepro/ui` includes a generic, high-performance selection engine and primitive cell inputs that allow you to construct keyboard-navigable editable grid tables without any Rebase database dependencies.
|
|
854
|
+
|
|
855
|
+
**Selection Hooks & Context:**
|
|
856
|
+
* `createVirtualTableSelectionStore()` — Instantiates a selection store tracking active selection by `rowId` and `columnKey`. It uses `useSyncExternalStore` so that selection changes only trigger re-renders on the active cell (preventing whole-table lag).
|
|
857
|
+
* `VirtualTableSelectionProvider` — React context provider to pass down the selection store.
|
|
858
|
+
* `useVirtualTableSelection()` — Hook to access the selection store and triggers inside custom cells.
|
|
859
|
+
* `useVirtualTableCellSelected(store, columnKey, rowId)` — Returns `boolean` indicating if this cell is currently focused.
|
|
860
|
+
|
|
861
|
+
**Generic Cell Inputs:**
|
|
862
|
+
* `VirtualTableInput` — Auto-growing, debounced multiline/single text input.
|
|
863
|
+
* `VirtualTableNumberInput` — Constrained numeric input.
|
|
864
|
+
* `VirtualTableSwitch` — Small boolean switch toggle.
|
|
865
|
+
* `VirtualTableDateField` — Decoupled Date/DateTime picker (accepts `locale`, `timezone`, `mode`).
|
|
866
|
+
* `VirtualTableSelect` — Option-based single/multi dropdown list using chips. Takes `options: { value, label, color? }[]`.
|
|
867
|
+
|
|
868
|
+
**Example Custom Editable Table:**
|
|
869
|
+
```tsx
|
|
870
|
+
import React, { useMemo, useState, useCallback } from "react";
|
|
871
|
+
import {
|
|
872
|
+
VirtualTable,
|
|
873
|
+
createVirtualTableSelectionStore,
|
|
874
|
+
VirtualTableSelectionProvider,
|
|
875
|
+
useVirtualTableCellSelected,
|
|
876
|
+
useVirtualTableSelection,
|
|
877
|
+
VirtualTableInput
|
|
878
|
+
} from "@rebasepro/ui";
|
|
879
|
+
|
|
880
|
+
function EditableCell({ rowId, columnKey, value, onUpdate }) {
|
|
881
|
+
const { selectionStore } = useVirtualTableSelection();
|
|
882
|
+
const selected = useVirtualTableCellSelected(selectionStore, columnKey, rowId);
|
|
883
|
+
|
|
884
|
+
return (
|
|
885
|
+
<div
|
|
886
|
+
className="w-full h-full flex items-center px-2 cursor-text"
|
|
887
|
+
onClick={(e) => selectionStore.select({
|
|
888
|
+
rowId,
|
|
889
|
+
columnKey,
|
|
890
|
+
cellRect: e.currentTarget.getBoundingClientRect(),
|
|
891
|
+
width: e.currentTarget.offsetWidth,
|
|
892
|
+
height: e.currentTarget.offsetHeight
|
|
893
|
+
})}
|
|
894
|
+
>
|
|
895
|
+
<VirtualTableInput
|
|
896
|
+
value={value}
|
|
897
|
+
updateValue={onUpdate}
|
|
898
|
+
focused={selected}
|
|
899
|
+
disabled={false}
|
|
900
|
+
multiline={false}
|
|
901
|
+
/>
|
|
902
|
+
</div>
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
export function MyEditableGrid() {
|
|
907
|
+
const [data, setData] = useState([
|
|
908
|
+
{ id: "row-1", name: "Project Alpha" },
|
|
909
|
+
{ id: "row-2", name: "Project Beta" }
|
|
910
|
+
]);
|
|
911
|
+
|
|
912
|
+
const selectionStore = useMemo(() => createVirtualTableSelectionStore(), []);
|
|
913
|
+
|
|
914
|
+
const updateCell = useCallback((rowId: string, value: string) => {
|
|
915
|
+
setData(prev => prev.map(row => row.id === rowId ? { ...row, name: value } : row));
|
|
916
|
+
}, []);
|
|
917
|
+
|
|
918
|
+
const columns = [
|
|
919
|
+
{ key: "name", title: "Name", width: 300 }
|
|
920
|
+
];
|
|
921
|
+
|
|
922
|
+
return (
|
|
923
|
+
<VirtualTableSelectionProvider store={selectionStore}>
|
|
924
|
+
<VirtualTable
|
|
925
|
+
data={data}
|
|
926
|
+
columns={columns}
|
|
927
|
+
rowHeight={52}
|
|
928
|
+
cellRenderer={({ rowData, column }) => (
|
|
929
|
+
<EditableCell
|
|
930
|
+
rowId={rowData.id}
|
|
931
|
+
columnKey={column.key}
|
|
932
|
+
value={rowData.name}
|
|
933
|
+
onUpdate={(val) => updateCell(rowData.id, val || "")}
|
|
934
|
+
/>
|
|
935
|
+
)}
|
|
936
|
+
/>
|
|
937
|
+
</VirtualTableSelectionProvider>
|
|
938
|
+
);
|
|
939
|
+
}
|
|
940
|
+
```
|
|
941
|
+
|
|
851
942
|
### ToggleButtonGroup
|
|
852
943
|
|
|
853
944
|
Single-select button group for toggling between options.
|