11# Convex TableHistory Component
22
3- [ ![ npm version] ( https://badge.fury.io/js/@ convex-dev%2Ftable -history.svg )] ( https://badge.fury.io/js/@ convex-dev%2Ftable -history )
3+ [ ![ npm version] ( https://badge.fury.io/js/convex-table -history.svg )] ( https://badge.fury.io/js/convex-table -history )
44
55<!-- START: Include on https://convex.dev/components -->
66
77## History on a Convex table
88
9- Attach a history component to your most important Convex tables to keep track of changes.
9+ Attach a history component to your Convex table to keep track of changes.
1010
1111- View an audit log of all table changes in the Convex Dashboard or in a custom React component.
12- - Answer questions like "did user A join the team before user B?"
12+ - Answer questions like "was document A updated before document B?"
1313- View an audit log of changes to a single document
1414 - Answer questions like "what was the user's email address before they changed it?"
1515- Look at a snapshot of the table at any point in time.
1616
1717``` ts
18- // Paginate through all history on the "users " table
19- userAuditLog .listHistory (ctx , args .maxTs , args .paginationOpts );
18+ // Paginate through all history on the "documents " table, from newest to oldest.
19+ documentAuditLog .listHistory (ctx , args .maxTs , args .paginationOpts );
2020
21- // Paginate through all history for a specific user
22- userAuditLog .listDocumentHistory (ctx , args .userId , args .maxTs , args .paginationOpts );
21+ // Paginate through all history for a specific document, from newest to oldest.
22+ documentAuditLog .listDocumentHistory (ctx , args .documentId , args .maxTs , args .paginationOpts );
2323
24- // Paginate through all users in the "users " table at a specific timestamp
25- userAuditLog .listSnapshot (ctx , args .snapshotTs , args .currentTs , args .paginationOpts );
24+ // Paginate through all documents in the "documents " table at a specific timestamp.
25+ documentAuditLog .listSnapshot (ctx , args .snapshotTs , args .currentTs , args .paginationOpts );
2626```
2727
28- Found a bug? Feature request? [ File it here] ( https://github.com/get-convex /table-history/issues ) .
28+ Found a bug? Feature request? [ File it here] ( https://github.com/ldanilek /table-history/issues ) .
2929
3030## Pre-requisite: Convex
3131
@@ -40,18 +40,18 @@ Run `npm create convex` or follow any of the [quickstarts](https://docs.convex.d
4040Install the component package:
4141
4242``` ts
43- npm install @ convex - dev / table - history
43+ npm install convex - table - history
4444```
4545
4646Create a ` convex.config.ts ` file in your app's ` convex/ ` folder and install the component by calling ` use ` :
4747
4848``` ts
4949// convex/convex.config.ts
5050import { defineApp } from " convex/server" ;
51- import tableHistory from " @ convex-dev/ table-history/convex.config" ;
51+ import tableHistory from " convex-table-history/convex.config" ;
5252
5353const app = defineApp ();
54- app .use (tableHistory , { name: " userAuditLog " });
54+ app .use (tableHistory , { name: " documentAuditLog " });
5555
5656export default app ;
5757```
@@ -64,28 +64,26 @@ different names. They will be available in your app as
6464
6565``` ts
6666import { components } from " ./_generated/api" ;
67- import { TableHistory } from " @ convex-dev/ table-history" ;
67+ import { TableHistory } from " convex-table-history" ;
6868
69- const userAuditLog = new TableHistory <DataModel , " users" >(components .userAuditLog , {
70- serializability: " wallclock" ,
71- });
69+ const documentAuditLog = new TableHistory <DataModel , " documents" >(components .documentAuditLog );
7270```
7371
7472Add an item to the history table when a document changes:
7573
7674``` ts
77- async function patchUser (ctx : MutationCtx , userId : Id <" users " >, patch : Partial <Doc <" users " >>) {
78- await ctx .db .patch (userId , patch );
79- const userDoc = await ctx .db .get (userId );
80- await userAuditLog .update (ctx , userDoc . _id , userDoc );
75+ async function patchDocument (ctx : MutationCtx , documentId : Id <" documents " >, patch : Partial <Doc <" documents " >>) {
76+ await ctx .db .patch (documentId , patch );
77+ const document = await ctx .db .get (documentId );
78+ await documentAuditLog .update (ctx , documentId , document );
8179}
8280```
8381
8482Or attach a [ trigger] ( https://docs.convex.dev/triggers ) to automatically write to the history table when a mutation changes a document:
8583
8684``` ts
8785const triggers = new Triggers <DataModel >();
88- triggers .register (" users " , userAuditLog .trigger ());
86+ triggers .register (" documents " , documentAuditLog .trigger ());
8987export const mutation = customMutation (rawMutation , customCtx (triggers .wrapDB ));
9088```
9189
@@ -96,8 +94,9 @@ The pages consist of `HistoryEntry` objects, which have the following fields:
9694
9795- ` id ` : the id of the document that was changed
9896- ` doc ` : the new version of the document that was changed, or null if the document was deleted
99- - ` ts ` : the timestamp of the change
97+ - ` ts ` : the timestamp of the change. See [ serializability ] ( #serializability ) .
10098- ` isDeleted ` : whether the document was deleted
99+ - ` attribution ` : an optional arbitrary object that will be stored with the history entry
101100
102101See more example usage in [ example.ts] ( ./example/convex/example.ts ) .
103102
@@ -120,29 +119,48 @@ You can configure the serializability of the history table by setting the
120119- ` "document" ` -- i.e. the latest ts for the document, plus one
121120 - history entries are serializable with other updates on the same document.
122121- ` "wallclock" ` -- i.e. Date.now()
123- - history entry timestamps don't have guarantees, but they also take no extra dependencies and are usually sufficient.
122+ - history entry timestamps don't have guarantees, but they also take no extra
123+ dependencies and are usually sufficient.
124124
125- ### ` currentTs ` and ` maxTs ` are required
125+ The default serializability is ` "wallclock" ` .
126126
127- - For ` listSnapshot ` , ` currentTs ` should be a stable recent timestamp.
128- - If the timestamp isn't recent, the queries might read too much data in
129- a single page and throw an error.
130- - For ` listHistory ` and ` listDocumentHistory ` , ` maxTs ` should be stable but
131- doesn't need to be recent.
127+ ### ` currentTs ` and ` maxTs ` are required
132128
133129``` ts
134130const [currentTs] = useState (Date .now ()); // stable and recent
135- const [yesterday] = useState (Date .now () - 24 * 60 * 60 * 1000 );
136- const usersYesterday = usePaginatedQuery (
137- api .users .listSnapshot ,
131+ const [yesterday] = useState (Date .now () - 24 * 60 * 60 * 1000 ); // stable
132+ const snapshotOfUsersYesterday = usePaginatedQuery (
133+ api .documents .listSnapshot ,
138134 {
139135 currentTs ,
140136 snapshotTs: yesterday ,
141137 },
142138 { initialNumItems: 100 },
143139);
140+ const auditLogBeforeYesterday = usePaginatedQuery (
141+ api .documents .listHistory ,
142+ {
143+ maxTs: yesterday ,
144+ },
145+ { initialNumItems: 100 },
146+ );
144147```
145148
149+ - For ` listSnapshot ` , ` currentTs ` should be a stable recent timestamp.
150+ - "Stable" means it should have the same value for all pages.
151+ - To keep a stable timestamp for all pages, pick a value on the client and
152+ pass it as an arg of ` usePaginatedQuery ` .
153+ - "Recent" is relative to how often the table gets new inserts. The amount of
154+ extra work performed by the query is proportional to the number of
155+ ` ctx.db.insert(tableName, doc) ` calls since the ` currentTs ` .
156+ - If the timestamp isn't recent, the queries might read too much data in
157+ a single page and throw an error.
158+ - Don't pick a timestamp in the future, or gaps will appear between pages
159+ as new documents are inserted. The timestamp should be ` Date.now() ` or
160+ slightly in the past.
161+ - For ` listHistory ` and ` listDocumentHistory ` , ` maxTs ` should be stable but
162+ doesn't need to be recent.
163+
146164** Why is this necessary?**
147165
148166The TableHistory component supports paginated queries with
@@ -157,7 +175,27 @@ Concretely, `usePaginatedQuery` results should not have gaps or duplicates.
157175In order to implement this feature without the built-in ` .paginate ` method,
158176the TableHistory component assumes its own data model is append-only (which is
159177true, except when vacuuming), and takes in a stable recent timestamp. Then it
160- only looks at history entries from before that timestamp.
178+ ignores history entries created after that timestamp.
179+
180+ ### Attribution
181+
182+ Store an update's ` attribution ` to track information like which user made the
183+ change, or what mutation made the change.
184+
185+ ``` ts
186+ await documentAuditLog .update (ctx , documentId , document , {
187+ attribution: {
188+ actorIdentity: await ctx .auth .getUserIdentity (),
189+ mutationName: " patchDocument" ,
190+ source: " web" ,
191+ },
192+ });
193+ ```
194+
195+ The default attribution when using ` TableHistory.update ` is ` null ` .
196+
197+ The default attribution when using ` TableHistory.trigger ` is the mutation's
198+ ` ctx.auth.getUserIdentity() ` .
161199
162200### Vacuuming
163201
@@ -167,13 +205,14 @@ schedule background jobs to delete old history entries.
167205The entries which will be deleted are those which are not visible
168206at snapshots ` >=minTsToKeep ` .
169207
208+ After vacuuming up to ` minTsToKeep ` , you can no longer call ` listSnapshot `
209+ with a snapshot timestamp less than ` minTsToKeep ` .
210+
170211### Limitations
171212
172- - No attribution: there is no way to add attribution to a history entry, e.g. which ` ctx.auth `
173- made the change.
174- - Workaround: you can add attribution to the document itself.
175213- No indexes: you can't use an index to change the sort order or get a subset of results.
176214 - Workaround: you can paginate until ` isDone ` returns true, and sort or filter
177215 the results yourself, either on the client or in an action.
216+ Consider [ manual pagination] ( https://docs.convex.dev/database/pagination#paginating-manually ) .
178217
179218<!-- END: Include on https://convex.dev/components -->
0 commit comments