@rebasepro/agent-skills 0.8.0 → 0.9.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/package.json +1 -1
- package/skills/rebase-admin/SKILL.md +32 -32
- package/skills/rebase-api/SKILL.md +6 -6
- package/skills/rebase-auth/SKILL.md +29 -7
- package/skills/rebase-backend-postgres/SKILL.md +3 -3
- package/skills/rebase-basics/SKILL.md +4 -4
- package/skills/rebase-collections/SKILL.md +46 -49
- package/skills/rebase-cron-jobs/SKILL.md +2 -2
- package/skills/rebase-deployment/SKILL.md +1 -1
- package/skills/rebase-email/SKILL.md +1 -1
- package/skills/rebase-entity-history/SKILL.md +9 -9
- package/skills/rebase-realtime/SKILL.md +5 -5
- package/skills/rebase-security/SKILL.md +62 -31
- package/skills/rebase-storage/SKILL.md +8 -4
- package/skills/rebase-studio/SKILL.md +8 -8
- package/skills/rebase-webhooks/SKILL.md +25 -30
|
@@ -11,7 +11,7 @@ Rebase collections are the core building blocks of your data model. They define
|
|
|
11
11
|
|
|
12
12
|
### Collections
|
|
13
13
|
|
|
14
|
-
A collection is defined as a TypeScript object implementing the `
|
|
14
|
+
A collection is defined as a TypeScript object implementing the `PostgresCollectionConfig` interface from `@rebasepro/types`. Each collection maps to a database table (via the `table` property) and generates:
|
|
15
15
|
- Full CRUD REST endpoints at `/api/data/{slug}`
|
|
16
16
|
- Optional GraphQL queries and mutations
|
|
17
17
|
- Admin panel views (table, forms, cards, kanban, list)
|
|
@@ -44,7 +44,7 @@ Properties define the fields of your collection. Rebase supports these built-in
|
|
|
44
44
|
| Junction tables | Yes (many-to-many) | No |
|
|
45
45
|
| Multi-hop joins | Yes (`joinPath`) | No |
|
|
46
46
|
| Inverse lookups | Yes (`direction: "inverse"`) | No |
|
|
47
|
-
| Where to use | **
|
|
47
|
+
| Where to use | **PostgresCollectionConfig** | FirebaseCollectionConfig or legacy |
|
|
48
48
|
| Stored value | FK column(s) managed by framework | `{ id, path }` object or string |
|
|
49
49
|
|
|
50
50
|
**Use `relation` for all new Postgres collections.** The `reference` type exists for backward compatibility with Firestore-style collections.
|
|
@@ -58,9 +58,9 @@ Collections are defined as standalone TypeScript files under `config/collections
|
|
|
58
58
|
## Defining a Collection
|
|
59
59
|
|
|
60
60
|
```typescript
|
|
61
|
-
import {
|
|
61
|
+
import { PostgresCollectionConfig } from "@rebasepro/types";
|
|
62
62
|
|
|
63
|
-
const productsCollection:
|
|
63
|
+
const productsCollection: PostgresCollectionConfig = {
|
|
64
64
|
name: "Products",
|
|
65
65
|
singularName: "Product",
|
|
66
66
|
slug: "products",
|
|
@@ -178,10 +178,10 @@ export default productsCollection;
|
|
|
178
178
|
| `additionalFields` | `AdditionalFieldDelegate[]` | — | Virtual computed columns for views |
|
|
179
179
|
| `entityActions` | `EntityAction[]` | — | Custom action buttons (see Entity Actions section) |
|
|
180
180
|
| `Actions` | `ComponentRef[]` | — | Custom toolbar action components |
|
|
181
|
-
| `callbacks` | `
|
|
181
|
+
| `callbacks` | `CollectionCallbacks<M, USER>` | — | Lifecycle hooks (see Collection Callbacks section) |
|
|
182
182
|
| `relations` | `Relation[]` | — | Explicit relation definitions (usually auto-extracted from properties) |
|
|
183
183
|
| `securityRules` | `SecurityRule[]` | — | Row Level Security policies |
|
|
184
|
-
| `childCollections` | `() =>
|
|
184
|
+
| `childCollections` | `() => CollectionConfig[]` | — | Nested child collections (populated automatically) |
|
|
185
185
|
| `overrides` | `EntityOverrides` | — | Override data source or storage source |
|
|
186
186
|
| `ownerId` | `string` | — | Owner user ID (for plugins/custom code) |
|
|
187
187
|
| `auth` | `boolean | AuthCollectionConfig` | — | Mark collection as authentication collection (user management, reset password, etc.) |
|
|
@@ -710,10 +710,10 @@ Relations are defined **directly on the property** using `type: "relation"`. The
|
|
|
710
710
|
### Many-to-One (Owning)
|
|
711
711
|
|
|
712
712
|
```typescript
|
|
713
|
-
import {
|
|
713
|
+
import { PostgresCollectionConfig } from "@rebasepro/types";
|
|
714
714
|
import authorsCollection from "./authors";
|
|
715
715
|
|
|
716
|
-
const postsCollection:
|
|
716
|
+
const postsCollection: PostgresCollectionConfig = {
|
|
717
717
|
name: "Posts",
|
|
718
718
|
slug: "posts",
|
|
719
719
|
table: "posts",
|
|
@@ -762,7 +762,7 @@ comments: {
|
|
|
762
762
|
|
|
763
763
|
| Option | Type | Default | Description |
|
|
764
764
|
|--------|------|---------|-------------|
|
|
765
|
-
| `target` | `string \| (() =>
|
|
765
|
+
| `target` | `string \| (() => CollectionConfig \| string)` | — | Target collection (use a function for lazy resolution to avoid circular imports) |
|
|
766
766
|
| `cardinality` | `"one" \| "many"` | `"one"` | Whether this references one or many records |
|
|
767
767
|
| `direction` | `"owning" \| "inverse"` | `"owning"` | Which side owns the FK or junction table |
|
|
768
768
|
| `localKey` | `string` | auto-inferred | Column on THIS table storing the FK (e.g. `"author_id"`) |
|
|
@@ -773,7 +773,7 @@ comments: {
|
|
|
773
773
|
| `inverseRelationName` | `string` | — | Name of the corresponding relation on the target collection |
|
|
774
774
|
| `onDelete` | `OnAction` | — | Cascade rule on delete |
|
|
775
775
|
| `onUpdate` | `OnAction` | — | Cascade rule on update |
|
|
776
|
-
| `overrides` | `Partial<
|
|
776
|
+
| `overrides` | `Partial<CollectionConfig>` | — | Override target collection config when rendered as subcollection tab |
|
|
777
777
|
| `fixedFilter` | `FilterValues` | — | Filter applied when selecting related entities |
|
|
778
778
|
| `includeId` | `boolean` | `true` | Show entity ID in the reference preview |
|
|
779
779
|
| `includeEntityLink` | `boolean` | `true` | Show link to open the related entity |
|
|
@@ -864,13 +864,13 @@ customer: {
|
|
|
864
864
|
|
|
865
865
|
> **See full documentation:** [Relations](https://rebase.pro/docs/collections/relations)
|
|
866
866
|
|
|
867
|
-
##
|
|
867
|
+
## Collection Callbacks (Lifecycle Hooks)
|
|
868
868
|
|
|
869
869
|
> **IMPORTANT FOR AGENTS**: Collections support **lifecycle callbacks** that let you run custom logic when entities are created, updated, read, or deleted. Use these to **sync data between collections**, transform data, validate business rules, or trigger side effects. **Do NOT use raw SQL triggers, cron jobs, or external scripts** when a callback can solve the problem.
|
|
870
870
|
|
|
871
871
|
### Generic Type Parameters
|
|
872
872
|
|
|
873
|
-
`
|
|
873
|
+
`CollectionCallbacks<M, USER>` accepts two generic type parameters:
|
|
874
874
|
|
|
875
875
|
| Parameter | Default | Description |
|
|
876
876
|
|-----------|---------|-------------|
|
|
@@ -878,7 +878,7 @@ customer: {
|
|
|
878
878
|
| `USER` | `User` | User type — extends the base `User` type with custom fields |
|
|
879
879
|
|
|
880
880
|
```typescript
|
|
881
|
-
import {
|
|
881
|
+
import { PostgresCollectionConfig, CollectionCallbacks } from "@rebasepro/types";
|
|
882
882
|
|
|
883
883
|
interface Product {
|
|
884
884
|
name: string;
|
|
@@ -887,7 +887,7 @@ interface Product {
|
|
|
887
887
|
status: string;
|
|
888
888
|
}
|
|
889
889
|
|
|
890
|
-
const callbacks:
|
|
890
|
+
const callbacks: CollectionCallbacks<Product> = {
|
|
891
891
|
beforeSave: async ({ values, status }) => {
|
|
892
892
|
// `values` is typed as Partial<Product>
|
|
893
893
|
if (values.name) {
|
|
@@ -925,11 +925,11 @@ The `context.user` object is populated by the auth middleware. In server-side ca
|
|
|
925
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
926
|
>
|
|
927
927
|
> ```typescript
|
|
928
|
-
> afterRead: async ({
|
|
928
|
+
> afterRead: async ({ row, context }) => {
|
|
929
929
|
> // Server-side reads (cron jobs, admin) see real values
|
|
930
|
-
> if (context.user?.roles?.includes("admin")) return
|
|
930
|
+
> if (context.user?.roles?.includes("admin")) return row;
|
|
931
931
|
> // End-user reads get masked values
|
|
932
|
-
> return { ...
|
|
932
|
+
> return { ...row, email: "***@***.***" };
|
|
933
933
|
> }
|
|
934
934
|
> ```
|
|
935
935
|
|
|
@@ -938,7 +938,7 @@ The `context.user` object is populated by the auth middleware. In server-side ca
|
|
|
938
938
|
### Callback Example
|
|
939
939
|
|
|
940
940
|
```typescript
|
|
941
|
-
const jobSubmissionsCollection:
|
|
941
|
+
const jobSubmissionsCollection: PostgresCollectionConfig = {
|
|
942
942
|
name: "Job Submissions",
|
|
943
943
|
slug: "job_submissions",
|
|
944
944
|
table: "job_submissions",
|
|
@@ -955,38 +955,35 @@ const jobSubmissionsCollection: PostgresCollection = {
|
|
|
955
955
|
},
|
|
956
956
|
|
|
957
957
|
// Runs AFTER saving — trigger side effects, sync other collections
|
|
958
|
-
afterSave: async ({ values,
|
|
958
|
+
afterSave: async ({ values, id, previousValues, context }) => {
|
|
959
959
|
if (values.status === "approved" && previousValues?.status !== "approved") {
|
|
960
960
|
await context.data.jobs.create({
|
|
961
961
|
title: values.title,
|
|
962
962
|
description: values.description,
|
|
963
963
|
company_id: values.company_id,
|
|
964
964
|
status: "published",
|
|
965
|
-
source_submission_id:
|
|
965
|
+
source_submission_id: id,
|
|
966
966
|
});
|
|
967
967
|
}
|
|
968
968
|
},
|
|
969
969
|
|
|
970
970
|
// Runs BEFORE deleting — block or validate
|
|
971
|
-
beforeDelete: async ({
|
|
972
|
-
if (
|
|
971
|
+
beforeDelete: async ({ row }) => {
|
|
972
|
+
if (row.status === "published") {
|
|
973
973
|
throw new Error("Cannot delete published submissions");
|
|
974
974
|
}
|
|
975
975
|
},
|
|
976
976
|
|
|
977
977
|
// Runs AFTER deleting — cleanup related data
|
|
978
|
-
afterDelete: async ({
|
|
979
|
-
console.log(`Submission ${
|
|
978
|
+
afterDelete: async ({ id, context }) => {
|
|
979
|
+
console.log(`Submission ${id} deleted`);
|
|
980
980
|
},
|
|
981
981
|
|
|
982
982
|
// Runs AFTER reading — transform for display
|
|
983
|
-
afterRead: async ({
|
|
983
|
+
afterRead: async ({ row }) => {
|
|
984
984
|
return {
|
|
985
|
-
...
|
|
986
|
-
|
|
987
|
-
...entity.values,
|
|
988
|
-
displayName: `${entity.values.title} (${entity.values.company_name})`
|
|
989
|
-
}
|
|
985
|
+
...row,
|
|
986
|
+
displayName: `${row.title} (${row.company_name})`
|
|
990
987
|
};
|
|
991
988
|
}
|
|
992
989
|
},
|
|
@@ -1001,7 +998,7 @@ const jobSubmissionsCollection: PostgresCollection = {
|
|
|
1001
998
|
| `beforeSave` | Before write to DB (after validation) | Modified `values` (`Partial<EntityValues<M>>`) | Yes (throw to block) |
|
|
1002
999
|
| `afterSave` | After successful write | `void` | No |
|
|
1003
1000
|
| `afterSaveError` | After a failed write | `void` | No |
|
|
1004
|
-
| `afterRead` | After reading from DB | Modified
|
|
1001
|
+
| `afterRead` | After reading from DB | Modified row (`Record<string, unknown>`) | No |
|
|
1005
1002
|
| `beforeDelete` | Before deletion | `void \| boolean` | Yes (throw to block) |
|
|
1006
1003
|
| `afterDelete` | After successful deletion | `void` | No |
|
|
1007
1004
|
|
|
@@ -1012,10 +1009,10 @@ const jobSubmissionsCollection: PostgresCollection = {
|
|
|
1012
1009
|
| Prop | Type | Description |
|
|
1013
1010
|
|------|------|-------------|
|
|
1014
1011
|
| `values` | `Partial<EntityValues<M>>` | Entity values being saved |
|
|
1015
|
-
| `
|
|
1012
|
+
| `id` | `string \| number` (optional in `beforeSave`) | Entity ID (`undefined` for new entities in `beforeSave`) |
|
|
1016
1013
|
| `previousValues` | `Partial<EntityValues<M>> \| undefined` | Previous values (for updates) |
|
|
1017
1014
|
| `status` | `EntityStatus` | `"new"`, `"existing"`, or `"copy"` |
|
|
1018
|
-
| `collection` | `
|
|
1015
|
+
| `collection` | `CollectionConfig<M>` | The collection definition |
|
|
1019
1016
|
| `path` | `string` | Collection path |
|
|
1020
1017
|
| `context` | `RebaseCallContext<USER>` | Context with `client`, `data`, `storageSource`, `user` |
|
|
1021
1018
|
|
|
@@ -1023,8 +1020,8 @@ const jobSubmissionsCollection: PostgresCollection = {
|
|
|
1023
1020
|
|
|
1024
1021
|
| Prop | Type | Description |
|
|
1025
1022
|
|------|------|-------------|
|
|
1026
|
-
| `
|
|
1027
|
-
| `collection` | `
|
|
1023
|
+
| `row` | `Record<string, unknown>` | The fetched row (flat — `{ id, ...columns }`) |
|
|
1024
|
+
| `collection` | `CollectionConfig<M>` | The collection definition |
|
|
1028
1025
|
| `path` | `string` | Collection path |
|
|
1029
1026
|
| `context` | `RebaseCallContext<USER>` | Context |
|
|
1030
1027
|
|
|
@@ -1032,9 +1029,9 @@ const jobSubmissionsCollection: PostgresCollection = {
|
|
|
1032
1029
|
|
|
1033
1030
|
| Prop | Type | Description |
|
|
1034
1031
|
|------|------|-------------|
|
|
1035
|
-
| `
|
|
1036
|
-
| `
|
|
1037
|
-
| `collection` | `
|
|
1032
|
+
| `row` | `Record<string, unknown>` | The row being deleted (flat — `{ id, ...columns }`) |
|
|
1033
|
+
| `id` | `string \| number` | Entity ID |
|
|
1034
|
+
| `collection` | `CollectionConfig<M>` | The collection definition |
|
|
1038
1035
|
| `path` | `string` | Collection path |
|
|
1039
1036
|
| `context` | `RebaseCallContext<USER>` | Context |
|
|
1040
1037
|
|
|
@@ -1068,7 +1065,7 @@ title: {
|
|
|
1068
1065
|
- **Cascade operations** — Use `afterDelete` to clean up related records in other collections
|
|
1069
1066
|
- **Data enrichment** — Use `afterRead` to add computed/virtual fields for display
|
|
1070
1067
|
|
|
1071
|
-
> **See full documentation:** [
|
|
1068
|
+
> **See full documentation:** [Collection Callbacks](https://rebase.pro/docs/collections/callbacks)
|
|
1072
1069
|
|
|
1073
1070
|
## Entity Actions (Custom UI Buttons)
|
|
1074
1071
|
|
|
@@ -1077,7 +1074,7 @@ title: {
|
|
|
1077
1074
|
Add an `entityActions` array to any collection definition:
|
|
1078
1075
|
|
|
1079
1076
|
```typescript
|
|
1080
|
-
const jobSubmissionsCollection:
|
|
1077
|
+
const jobSubmissionsCollection: PostgresCollectionConfig = {
|
|
1081
1078
|
name: "Job Submissions",
|
|
1082
1079
|
slug: "job_submissions",
|
|
1083
1080
|
table: "job_submissions",
|
|
@@ -1134,13 +1131,13 @@ The `onClick` and `isEnabled` handlers receive:
|
|
|
1134
1131
|
| `entity` | `Entity<M> \| undefined` | The current entity |
|
|
1135
1132
|
| `context` | `RebaseContext<USER>` | Full context (includes `snackbarController`, `authController`, etc.) |
|
|
1136
1133
|
| `path` | `string \| undefined` | Collection path |
|
|
1137
|
-
| `collection` | `
|
|
1134
|
+
| `collection` | `CollectionConfig<M> \| undefined` | Collection definition |
|
|
1138
1135
|
| `formContext` | `FormContext \| undefined` | Form state (when called from a form) |
|
|
1139
|
-
| `sideEntityController` | `
|
|
1136
|
+
| `sideEntityController` | `SidePanelController \| undefined` | Side panel control |
|
|
1140
1137
|
| `selectionController` | `SelectionController \| undefined` | Multi-select state (collection view) |
|
|
1141
1138
|
| `view` | `"collection" \| "form"` | Where the action was triggered |
|
|
1142
1139
|
| `openEntityMode` | `"side_panel" \| "full_screen" \| "split" \| "dialog"` | How the entity form is opened |
|
|
1143
|
-
| `highlightEntity` | `(entity) => void` | Highlight
|
|
1140
|
+
| `highlightEntity` | `(entity) => void` | Highlight a entity row |
|
|
1144
1141
|
| `unhighlightEntity` | `(entity) => void` | Remove highlight |
|
|
1145
1142
|
| `navigateBack` | `() => void` | Navigate back (e.g., after deleting) |
|
|
1146
1143
|
| `onCollectionChange` | `() => void` | Refresh the collection view |
|
|
@@ -1167,7 +1164,7 @@ const entityViews = [
|
|
|
1167
1164
|
<RebaseCMS collections={collections} entityViews={entityViews}/>
|
|
1168
1165
|
|
|
1169
1166
|
// Per-collection reference in collection definition
|
|
1170
|
-
const postsCollection:
|
|
1167
|
+
const postsCollection: PostgresCollectionConfig = {
|
|
1171
1168
|
name: "Posts",
|
|
1172
1169
|
slug: "posts",
|
|
1173
1170
|
table: "posts",
|
|
@@ -1216,9 +1213,9 @@ You can override built-in UI components for a specific collection by adding a `c
|
|
|
1216
1213
|
Only collection-scoped components can be overridden here. App-level components (such as `Shell.AppBar` or `HomePage`) must be overridden globally at the `<Rebase>` root.
|
|
1217
1214
|
|
|
1218
1215
|
```typescript
|
|
1219
|
-
import {
|
|
1216
|
+
import { PostgresCollectionConfig } from "@rebasepro/types";
|
|
1220
1217
|
|
|
1221
|
-
const productsCollection:
|
|
1218
|
+
const productsCollection: PostgresCollectionConfig = {
|
|
1222
1219
|
name: "Products",
|
|
1223
1220
|
slug: "products",
|
|
1224
1221
|
table: "products",
|
|
@@ -1264,9 +1261,9 @@ You can mark a collection as an authentication collection by setting the `auth`
|
|
|
1264
1261
|
This is the collection used for user credentials, password hashing, and user management. Rebase auto-injects required auth actions (like resetting passwords) and routes them through this config.
|
|
1265
1262
|
|
|
1266
1263
|
```typescript
|
|
1267
|
-
import {
|
|
1264
|
+
import { PostgresCollectionConfig } from "@rebasepro/types";
|
|
1268
1265
|
|
|
1269
|
-
const customUsersCollection:
|
|
1266
|
+
const customUsersCollection: PostgresCollectionConfig = {
|
|
1270
1267
|
name: "Members",
|
|
1271
1268
|
slug: "members",
|
|
1272
1269
|
table: "members",
|
|
@@ -1330,7 +1327,7 @@ location: {
|
|
|
1330
1327
|
Collections support **Row Level Security** via the `securityRules` array. This generates PostgreSQL RLS policies:
|
|
1331
1328
|
|
|
1332
1329
|
```typescript
|
|
1333
|
-
const postsCollection:
|
|
1330
|
+
const postsCollection: PostgresCollectionConfig = {
|
|
1334
1331
|
name: "Posts",
|
|
1335
1332
|
slug: "posts",
|
|
1336
1333
|
table: "posts",
|
|
@@ -230,8 +230,8 @@ 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
|
|
234
|
-
> -
|
|
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 Collection Callbacks). This means:
|
|
234
|
+
> - Collection Callbacks will see `context.user.uid === "service"` and `context.user.roles` containing `"admin"`
|
|
235
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
236
|
> - The service identity bypasses RLS policies
|
|
237
237
|
|
|
@@ -771,7 +771,7 @@ Schedule automated PostgreSQL backups via cron on the host:
|
|
|
771
771
|
0 3 * * * root docker compose -f /path/to/docker-compose.yml exec -T db pg_dump -U rebase rebase | gzip > /backups/rebase-$(date +\%Y\%m\%d).sql.gz
|
|
772
772
|
```
|
|
773
773
|
|
|
774
|
-
> **TIP:** Hetzner also offers managed
|
|
774
|
+
> **TIP:** Hetzner also offers managed entities (server-level) and the option to attach a Hetzner Volume for data that survives server rebuilds. For production, strongly consider both.
|
|
775
775
|
|
|
776
776
|
---
|
|
777
777
|
|
|
@@ -408,7 +408,7 @@ auth: {
|
|
|
408
408
|
|
|
409
409
|
## `rebase.email` Singleton
|
|
410
410
|
|
|
411
|
-
When email is configured, the email service is automatically attached to the `rebase` server-side singleton as `rebase.email`. This allows sending emails programmatically from custom functions, cron jobs, and
|
|
411
|
+
When email is configured, the email service is automatically attached to the `rebase` server-side singleton as `rebase.email`. This allows sending emails programmatically from custom functions, cron jobs, and collection callbacks.
|
|
412
412
|
|
|
413
413
|
```typescript
|
|
414
414
|
import { rebase } from "@rebasepro/server-core";
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: rebase-entity-history
|
|
3
|
-
description: Guide for tracking and reverting entity changes with Rebase's built-in history/audit-log system. Use this skill when the user needs entity versioning, change tracking, audit logs, reverting to previous versions, or understanding how history
|
|
3
|
+
description: Guide for tracking and reverting entity changes with Rebase's built-in history/audit-log system. Use this skill when the user needs entity versioning, change tracking, audit logs, reverting to previous versions, or understanding how history entities work.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Rebase Entity History
|
|
7
7
|
|
|
8
|
-
Rebase includes a built-in entity history system that records
|
|
8
|
+
Rebase includes a built-in entity history system that records entities of every create, update, and delete operation. History entries are stored in a dedicated PostgreSQL table and exposed via REST API endpoints. The Studio admin panel provides a visual history tab with one-click revert.
|
|
9
9
|
|
|
10
10
|
> **IMPORTANT FOR AGENTS:** Entity history requires **two** things to be enabled: (1) the global `history` flag in the backend config, AND (2) `history: true` on each individual collection. If either is missing, no history is recorded for that collection.
|
|
11
11
|
|
|
@@ -49,9 +49,9 @@ history: { retention: 30 },
|
|
|
49
49
|
On each collection that should be tracked, set `history: true`:
|
|
50
50
|
|
|
51
51
|
```typescript
|
|
52
|
-
import {
|
|
52
|
+
import { PostgresCollectionConfig } from "@rebasepro/types";
|
|
53
53
|
|
|
54
|
-
const productsCollection:
|
|
54
|
+
const productsCollection: PostgresCollectionConfig = {
|
|
55
55
|
slug: "products",
|
|
56
56
|
name: "Products",
|
|
57
57
|
table: "products",
|
|
@@ -100,7 +100,7 @@ Each history entry has the following structure:
|
|
|
100
100
|
| `entity_id` | `string` | The ID of the entity this entry belongs to |
|
|
101
101
|
| `action` | `"create" \| "update" \| "delete"` | What operation was performed |
|
|
102
102
|
| `changed_fields` | `string[] \| null` | Array of field names that changed (only for updates) |
|
|
103
|
-
| `values` | `Record<string, unknown> \| null` | Full entity values
|
|
103
|
+
| `values` | `Record<string, unknown> \| null` | Full entity values entity at the time of the change |
|
|
104
104
|
| `previous_values` | `Record<string, unknown> \| null` | Previous entity values (only for updates) |
|
|
105
105
|
| `updated_by` | `string \| null` | UID of the user who made the change |
|
|
106
106
|
| `updated_at` | `string` (ISO 8601) | Timestamp of when the change was recorded |
|
|
@@ -292,7 +292,7 @@ CREATE TABLE IF NOT EXISTS rebase.entity_history (
|
|
|
292
292
|
entity_id TEXT NOT NULL,
|
|
293
293
|
action TEXT NOT NULL, -- 'create', 'update', or 'delete'
|
|
294
294
|
changed_fields TEXT[], -- array of changed field names (updates only)
|
|
295
|
-
"values" JSONB, -- full entity values
|
|
295
|
+
"values" JSONB, -- full entity values entity
|
|
296
296
|
previous_values JSONB, -- previous values (updates only)
|
|
297
297
|
updated_by TEXT, -- user UID who made the change
|
|
298
298
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
@@ -310,7 +310,7 @@ CREATE INDEX IF NOT EXISTS idx_history_time
|
|
|
310
310
|
|
|
311
311
|
### Storage Characteristics
|
|
312
312
|
|
|
313
|
-
- **Values are stored as JSONB** — full
|
|
313
|
+
- **Values are stored as JSONB** — full entities, not diffs. This makes revert simple and reliable but uses more storage.
|
|
314
314
|
- **All collections share one table** — the `table_name` column distinguishes between collections.
|
|
315
315
|
- **Entity IDs are stored as text** — regardless of the original column type.
|
|
316
316
|
- **Indexes** cover `(table_name, entity_id)` and `(table_name, entity_id, updated_at DESC)` for fast per-entity lookups.
|
|
@@ -382,7 +382,7 @@ No additional configuration is needed — the History tab appears automatically
|
|
|
382
382
|
|
|
383
383
|
There is currently **no dedicated client SDK method** for entity history. History is accessed via the REST API directly (as shown in the endpoints section above).
|
|
384
384
|
|
|
385
|
-
The Studio admin panel uses an internal `
|
|
385
|
+
The Studio admin panel uses an internal `useHistory` React hook that:
|
|
386
386
|
- Fetches history from `GET /api/data/:slug/:entityId/history`
|
|
387
387
|
- Supports pagination via offset-based loading
|
|
388
388
|
- Provides a `revert()` function that calls `POST /api/data/:slug/:entityId/history/:historyId/revert`
|
|
@@ -462,7 +462,7 @@ console.log(`Server running at http://localhost:${env.PORT}`);
|
|
|
462
462
|
|
|
463
463
|
### Storage Growth
|
|
464
464
|
|
|
465
|
-
- Each history entry stores **full JSONB
|
|
465
|
+
- Each history entry stores **full JSONB entities** of both current and previous values. For entities with large text fields or complex nested structures, this can grow quickly.
|
|
466
466
|
- Default retention limits: **200 entries per entity** and **90 days TTL**. Adjust these based on your use case.
|
|
467
467
|
- All collections share the single `rebase.entity_history` table — monitor its size with:
|
|
468
468
|
|
|
@@ -341,11 +341,11 @@ ws.onmessage = (event) => {
|
|
|
341
341
|
| `broadcast` | `{ channel, event, payload }` | Send a message to channel members |
|
|
342
342
|
| `presence_track` | `{ channel, state }` | Track presence with custom state (auto-joins channel) |
|
|
343
343
|
| `presence_untrack` | `{ channel }` | Stop tracking presence |
|
|
344
|
-
| `presence_state` | `{ channel }` | Request full presence
|
|
344
|
+
| `presence_state` | `{ channel }` | Request full presence entity |
|
|
345
345
|
| `FETCH_COLLECTION` | `FetchCollectionProps` | One-shot collection fetch (request/response) |
|
|
346
346
|
| `FETCH_ENTITY` | `FetchEntityProps` | One-shot entity fetch |
|
|
347
|
-
| `SAVE_ENTITY` | `SaveEntityProps` | Create or update
|
|
348
|
-
| `DELETE_ENTITY` | `DeleteEntityProps` | Delete
|
|
347
|
+
| `SAVE_ENTITY` | `SaveEntityProps` | Create or update a entity |
|
|
348
|
+
| `DELETE_ENTITY` | `DeleteEntityProps` | Delete a entity |
|
|
349
349
|
| `COUNT_ENTITIES` | `FetchCollectionProps` | Count entities matching criteria |
|
|
350
350
|
| `CHECK_UNIQUE_FIELD` | `{ path, name, value, entityId?, collection? }` | Check field uniqueness |
|
|
351
351
|
|
|
@@ -359,7 +359,7 @@ ws.onmessage = (event) => {
|
|
|
359
359
|
| `entity_update` | `{ subscriptionId, entity }` | Single entity data (entity or `null` if deleted) |
|
|
360
360
|
| `collection_entity_patch` | `{ subscriptionId, entityId, entity }` | Instant single-entity patch for a collection subscription |
|
|
361
361
|
| `broadcast` | `{ channel, event, payload }` | Broadcast from another channel member |
|
|
362
|
-
| `presence_state` | `{ channel, presences }` | Full presence
|
|
362
|
+
| `presence_state` | `{ channel, presences }` | Full presence entity |
|
|
363
363
|
| `presence_diff` | `{ channel, joins, leaves }` | Incremental presence update |
|
|
364
364
|
| `ERROR` | `{ requestId?, payload: { error: { message, code } } }` | General error |
|
|
365
365
|
| `FETCH_COLLECTION_SUCCESS` | `{ requestId, payload: { entities } }` | Response to FETCH_COLLECTION |
|
|
@@ -687,7 +687,7 @@ If 10 entities change within 300ms, only **one** database query is executed for
|
|
|
687
687
|
|
|
688
688
|
## Nested Relation Updates
|
|
689
689
|
|
|
690
|
-
The realtime engine handles nested relation paths (e.g., `"posts/70/tags"`). When
|
|
690
|
+
The realtime engine handles nested relation paths (e.g., `"posts/70/tags"`). When a entity changes at a nested path:
|
|
691
691
|
|
|
692
692
|
1. The exact path is notified (`"posts/70/tags"`).
|
|
693
693
|
2. All parent paths are also notified (`"posts"`, `"posts/70"`).
|