@convex-dev/table-history 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +231 -0
  3. package/dist/esm/client/index.d.ts +116 -0
  4. package/dist/esm/client/index.d.ts.map +1 -0
  5. package/dist/esm/client/index.js +93 -0
  6. package/dist/esm/client/index.js.map +1 -0
  7. package/dist/esm/component/_generated/api.d.ts +34 -0
  8. package/dist/esm/component/_generated/api.d.ts.map +1 -0
  9. package/dist/esm/component/_generated/api.js +31 -0
  10. package/dist/esm/component/_generated/api.js.map +1 -0
  11. package/dist/esm/component/_generated/component.d.ts +105 -0
  12. package/dist/esm/component/_generated/component.d.ts.map +1 -0
  13. package/dist/esm/component/_generated/component.js +11 -0
  14. package/dist/esm/component/_generated/component.js.map +1 -0
  15. package/dist/esm/component/_generated/dataModel.d.ts +46 -0
  16. package/dist/esm/component/_generated/dataModel.d.ts.map +1 -0
  17. package/dist/esm/component/_generated/dataModel.js +11 -0
  18. package/dist/esm/component/_generated/dataModel.js.map +1 -0
  19. package/dist/esm/component/_generated/server.d.ts +121 -0
  20. package/dist/esm/component/_generated/server.d.ts.map +1 -0
  21. package/dist/esm/component/_generated/server.js +78 -0
  22. package/dist/esm/component/_generated/server.js.map +1 -0
  23. package/dist/esm/component/convex.config.d.ts +3 -0
  24. package/dist/esm/component/convex.config.d.ts.map +1 -0
  25. package/dist/esm/component/convex.config.js +3 -0
  26. package/dist/esm/component/convex.config.js.map +1 -0
  27. package/dist/esm/component/lib.d.ts +137 -0
  28. package/dist/esm/component/lib.d.ts.map +1 -0
  29. package/dist/esm/component/lib.js +316 -0
  30. package/dist/esm/component/lib.js.map +1 -0
  31. package/dist/esm/component/schema.d.ts +25 -0
  32. package/dist/esm/component/schema.d.ts.map +1 -0
  33. package/dist/esm/component/schema.js +17 -0
  34. package/dist/esm/component/schema.js.map +1 -0
  35. package/dist/esm/react/index.d.ts +2 -0
  36. package/dist/esm/react/index.d.ts.map +1 -0
  37. package/dist/esm/react/index.js +8 -0
  38. package/dist/esm/react/index.js.map +1 -0
  39. package/package.json +84 -0
  40. package/src/client/index.ts +174 -0
  41. package/src/component/_generated/api.ts +50 -0
  42. package/src/component/_generated/component.ts +136 -0
  43. package/src/component/_generated/dataModel.ts +60 -0
  44. package/src/component/_generated/server.ts +161 -0
  45. package/src/component/convex.config.ts +3 -0
  46. package/src/component/lib.test.ts +285 -0
  47. package/src/component/lib.ts +337 -0
  48. package/src/component/schema.ts +17 -0
  49. package/src/react/index.ts +8 -0
@@ -0,0 +1,316 @@
1
+ import { v } from "convex/values";
2
+ import { internalMutation, mutation, query } from "./_generated/server";
3
+ import { paginator } from "convex-helpers/server/pagination";
4
+ import schema from "./schema.js";
5
+ import { paginationOptsValidator } from "convex/server";
6
+ import { internal } from "./_generated/api";
7
+ export const serializabilityValidator = v.union(
8
+ /// "table" serializability means all writes to the table are serialized,
9
+ /// so the timestamps are in causal order. This gives the strictest guarantees
10
+ /// but can cause OCC conflicts if the table updates frequently.
11
+ /// Writes to different tables are not serialized.
12
+ v.literal("table"),
13
+ /// "document" serializability means all writes to the same document are serialized,
14
+ /// but writes to different documents may have out-of-order timestamps.
15
+ v.literal("document"),
16
+ /// "wallclock" serializability means the timestamp is set to the current time
17
+ /// according to the server's clock. This provides no guarantees, but it's
18
+ /// usually in causal order and causes no OCC conflicts.
19
+ /// Wallclock serializability is the default.
20
+ v.literal("wallclock"));
21
+ export const historyEntryValidator = v.object({
22
+ id: v.string(),
23
+ doc: v.any(),
24
+ ts: v.number(),
25
+ isDeleted: v.boolean(),
26
+ attribution: v.any(),
27
+ });
28
+ async function newTimestamp(ctx, serializability, id) {
29
+ switch (serializability) {
30
+ case "table": {
31
+ const latest = await ctx.db.query("history").withIndex("ts").order("desc").first();
32
+ if (latest) {
33
+ return Math.max(latest.ts + 1, Date.now());
34
+ }
35
+ else {
36
+ return Date.now();
37
+ }
38
+ }
39
+ case "document": {
40
+ const latest = await ctx.db.query("history").withIndex("id", (q) => q.eq("id", id)).order("desc").first();
41
+ if (latest) {
42
+ return Math.max(latest.ts + 1, Date.now());
43
+ }
44
+ else {
45
+ return Date.now();
46
+ }
47
+ }
48
+ case "wallclock": {
49
+ return Date.now();
50
+ }
51
+ }
52
+ }
53
+ export const update = mutation({
54
+ args: {
55
+ id: v.string(),
56
+ doc: v.union(v.any(), v.null()),
57
+ serializability: serializabilityValidator,
58
+ attribution: v.any(),
59
+ },
60
+ returns: v.number(),
61
+ handler: async (ctx, args) => {
62
+ let ts = await newTimestamp(ctx, args.serializability, args.id);
63
+ const existing = await ctx.db.query("history").withIndex("id", (q) => q.eq("id", args.id).eq("ts", ts)).first();
64
+ if (existing) {
65
+ ts = existing.ts + 1;
66
+ }
67
+ await ctx.db.insert("history", {
68
+ id: args.id,
69
+ doc: args.doc,
70
+ ts,
71
+ isDeleted: args.doc === null,
72
+ attribution: args.attribution,
73
+ });
74
+ return ts;
75
+ },
76
+ });
77
+ function paginationResultValidator(itemValidator) {
78
+ return v.object({
79
+ continueCursor: v.string(),
80
+ isDone: v.boolean(),
81
+ page: v.array(itemValidator),
82
+ pageStatus: v.optional(v.union(v.null(), v.literal("SplitRequired"), v.literal("SplitRecommended"))),
83
+ splitCursor: v.optional(v.union(v.null(), v.string())),
84
+ });
85
+ }
86
+ export const listHistory = query({
87
+ args: {
88
+ maxTs: v.number(),
89
+ paginationOpts: paginationOptsValidator,
90
+ },
91
+ returns: paginationResultValidator(historyEntryValidator),
92
+ handler: async (ctx, args) => {
93
+ const results = await paginator(ctx.db, schema)
94
+ .query("history")
95
+ .withIndex("ts", (q) => q.lte("ts", args.maxTs))
96
+ .order("desc")
97
+ .paginate(args.paginationOpts);
98
+ return {
99
+ ...results,
100
+ page: results.page.map(extractHistoryEntry),
101
+ };
102
+ },
103
+ });
104
+ function extractHistoryEntry(h) {
105
+ return {
106
+ id: h.id,
107
+ doc: h.doc,
108
+ ts: h.ts,
109
+ isDeleted: h.isDeleted,
110
+ attribution: h.attribution,
111
+ };
112
+ }
113
+ export const listDocumentHistory = query({
114
+ args: {
115
+ id: v.string(),
116
+ maxTs: v.number(),
117
+ paginationOpts: paginationOptsValidator,
118
+ },
119
+ returns: paginationResultValidator(historyEntryValidator),
120
+ handler: async (ctx, args) => {
121
+ const results = await paginator(ctx.db, schema)
122
+ .query("history")
123
+ .withIndex("id", (q) => q.eq("id", args.id).lte("ts", args.maxTs))
124
+ .order("desc")
125
+ .paginate(args.paginationOpts);
126
+ return {
127
+ ...results,
128
+ page: results.page.map(extractHistoryEntry),
129
+ };
130
+ },
131
+ });
132
+ // Sentinel value for end of cursor.
133
+ const END_CURSOR = "END_CURSOR";
134
+ export const listSnapshot = query({
135
+ args: {
136
+ snapshotTs: v.number(),
137
+ currentTs: v.number(),
138
+ paginationOpts: paginationOptsValidator,
139
+ },
140
+ returns: paginationResultValidator(historyEntryValidator),
141
+ handler: async (ctx, args) => {
142
+ const pageSize = args.paginationOpts.numItems;
143
+ const page = [];
144
+ if (args.paginationOpts.cursor === END_CURSOR) {
145
+ return {
146
+ continueCursor: END_CURSOR,
147
+ isDone: true,
148
+ page: [],
149
+ };
150
+ }
151
+ if (pageSize <= 0) {
152
+ throw new Error("pageSize must be positive");
153
+ }
154
+ if (args.currentTs < args.snapshotTs) {
155
+ throw new Error("currentTs must be >= snapshotTs");
156
+ }
157
+ const vacuumed = await ctx.db.query("vacuumed").first();
158
+ if (vacuumed && vacuumed.minTsToKeep > args.snapshotTs) {
159
+ throw new Error("invalid snapshotTs, snapshot has been vacuumed");
160
+ }
161
+ const targetEndCursor = args.paginationOpts.endCursor ?? null;
162
+ let prevId = args.paginationOpts.cursor;
163
+ const allIdsSeen = [];
164
+ const allIdsBeforeCurrentTs = [];
165
+ while (allIdsBeforeCurrentTs.length < pageSize || targetEndCursor !== null) {
166
+ const itemWithNextId = await ctx.db.query("history").withIndex("id", (q) => prevId !== null ? q.lt("id", prevId) : q).order("desc").first();
167
+ if (itemWithNextId === null) {
168
+ return {
169
+ continueCursor: END_CURSOR,
170
+ isDone: true,
171
+ page,
172
+ ...maybeSplit(allIdsSeen, pageSize),
173
+ };
174
+ }
175
+ allIdsSeen.push(itemWithNextId.id);
176
+ prevId = itemWithNextId.id;
177
+ if (targetEndCursor !== null && targetEndCursor !== END_CURSOR && itemWithNextId.id < targetEndCursor) {
178
+ // We've reached the end of the page.
179
+ return {
180
+ continueCursor: targetEndCursor,
181
+ isDone: targetEndCursor === END_CURSOR,
182
+ page,
183
+ ...maybeSplit(allIdsSeen, pageSize),
184
+ };
185
+ }
186
+ let revision = itemWithNextId;
187
+ if (itemWithNextId.ts > args.snapshotTs) {
188
+ // Find the revision as it existed at args.ts
189
+ const itemAtSnapshotTs = await ctx.db.query("history").withIndex("id", (q) => q.eq("id", itemWithNextId.id).lte("ts", args.snapshotTs)).order("desc").first();
190
+ if (itemAtSnapshotTs === null) {
191
+ // The item doesn't exist in the snapshotTs snapshot.
192
+ // Check if it exists as of currentTs
193
+ const itemAtCurrentTs = await ctx.db.query("history").withIndex("id", (q) => q.eq("id", itemWithNextId.id).lte("ts", args.currentTs)).order("desc").first();
194
+ if (itemAtCurrentTs === null) {
195
+ // It was created after currentTs, so we should treat it like it doesn't exist.
196
+ // prevId has advanced, but it never gets returned.
197
+ continue;
198
+ }
199
+ else {
200
+ // It was created between snapshotTs and currentTs, so it counts toward the limit and can be in the cursor, but it's not in the page.
201
+ revision = null;
202
+ }
203
+ }
204
+ else {
205
+ revision = itemAtSnapshotTs;
206
+ }
207
+ }
208
+ if (revision && revision.isDeleted) {
209
+ // If it's deleted, we don't want to include it in the page, but it counts toward the limit and can be in the cursor.
210
+ revision = null;
211
+ }
212
+ allIdsBeforeCurrentTs.push(itemWithNextId.id);
213
+ if (revision) {
214
+ page.push(extractHistoryEntry(revision));
215
+ }
216
+ }
217
+ const output = {
218
+ continueCursor: allIdsBeforeCurrentTs[allIdsBeforeCurrentTs.length - 1],
219
+ isDone: false,
220
+ page,
221
+ ...maybeSplit(allIdsSeen, pageSize),
222
+ };
223
+ return output;
224
+ },
225
+ });
226
+ function maybeSplit(allIdsSeen, pageSize) {
227
+ if (allIdsSeen.length >= pageSize * 2) {
228
+ return {
229
+ splitCursor: allIdsSeen[pageSize - 1],
230
+ pageStatus: "SplitRecommended",
231
+ };
232
+ }
233
+ return {};
234
+ }
235
+ /**
236
+ * Deletes history of state that was gone (overwritten or deleted) before
237
+ * minTsToKeep.
238
+ *
239
+ * After `vacuumHistory` is called, `listSnapshot` with `ts` before `minTsToKeep` will not
240
+ * necessarily be correct.
241
+ *
242
+ * This mutation does not delete history atomically. It may take a while with
243
+ * async operations.
244
+ *
245
+ * NOTE: `usePaginatedQuery` on `listSnapshot` may yield pages that have gaps or
246
+ * overlap if a reactive query is subscribed when `vacuumHistory` runs.
247
+ */
248
+ export const vacuumHistory = mutation({
249
+ args: {
250
+ minTsToKeep: v.number(),
251
+ },
252
+ handler: async (ctx, args) => {
253
+ // Ensure that no one relies on vacuuming running immediately by waiting
254
+ // 100ms.
255
+ // This also avoids race conditions where `args.minTsToKeep` is so recent
256
+ // that new entries with earlier timestamps are still being added to the
257
+ // history table.
258
+ await ctx.scheduler.runAfter(100, internal.lib.vacuumHistoryRecursive, {
259
+ minTsToKeep: args.minTsToKeep,
260
+ paginationOpts: {
261
+ numItems: 100,
262
+ cursor: null,
263
+ },
264
+ });
265
+ },
266
+ });
267
+ export const vacuumHistoryRecursive = internalMutation({
268
+ args: {
269
+ minTsToKeep: v.number(),
270
+ paginationOpts: paginationOptsValidator,
271
+ },
272
+ handler: async (ctx, args) => {
273
+ const vacuumed = await ctx.db.query("vacuumed").first();
274
+ const startTs = vacuumed?.minTsToKeep ?? 0;
275
+ if (startTs >= args.minTsToKeep) {
276
+ return;
277
+ }
278
+ const toDelete = await paginator(ctx.db, schema)
279
+ .query("history")
280
+ .withIndex("ts", (q) => q.gt("ts", startTs).lte("ts", args.minTsToKeep))
281
+ .order("asc")
282
+ .paginate(args.paginationOpts);
283
+ let maxTs = startTs;
284
+ for (const h of toDelete.page) {
285
+ const prevRev = await ctx.db.query("history").withIndex("id", (q) => q.eq("id", h.id).lt("ts", h.ts)).order("desc").first();
286
+ if (prevRev !== null) {
287
+ await ctx.db.delete(prevRev._id);
288
+ }
289
+ if (h.isDeleted) {
290
+ await ctx.db.delete(h._id);
291
+ }
292
+ maxTs = Math.max(maxTs, h.ts);
293
+ }
294
+ if (vacuumed === null) {
295
+ await ctx.db.insert("vacuumed", {
296
+ minTsToKeep: maxTs,
297
+ });
298
+ }
299
+ else {
300
+ await ctx.db.patch(vacuumed._id, {
301
+ minTsToKeep: maxTs,
302
+ });
303
+ }
304
+ if (toDelete.isDone) {
305
+ return;
306
+ }
307
+ await ctx.scheduler.runAfter(0, internal.lib.vacuumHistoryRecursive, {
308
+ minTsToKeep: args.minTsToKeep,
309
+ paginationOpts: {
310
+ numItems: args.paginationOpts.numItems,
311
+ cursor: toDelete.continueCursor,
312
+ }
313
+ });
314
+ },
315
+ });
316
+ //# sourceMappingURL=lib.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lib.js","sourceRoot":"","sources":["../../../src/component/lib.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ declare const _default: import("convex/server").SchemaDefinition<{
2
+ history: import("convex/server").TableDefinition<import("convex/values").VObject<{
3
+ id: string;
4
+ ts: number;
5
+ doc: any;
6
+ isDeleted: boolean;
7
+ attribution: any;
8
+ }, {
9
+ id: import("convex/values").VString<string, "required">;
10
+ ts: import("convex/values").VFloat64<number, "required">;
11
+ doc: import("convex/values").VUnion<any, [import("convex/values").VAny<any, "required", string>, import("convex/values").VNull<null, "required">], "required", string>;
12
+ isDeleted: import("convex/values").VBoolean<boolean, "required">;
13
+ attribution: import("convex/values").VAny<any, "required", string>;
14
+ }, "required", "id" | "ts" | "doc" | "isDeleted" | "attribution" | `doc.${string}` | `attribution.${string}`>, {
15
+ ts: ["ts", "_creationTime"];
16
+ id: ["id", "ts", "_creationTime"];
17
+ }, {}, {}>;
18
+ vacuumed: import("convex/server").TableDefinition<import("convex/values").VObject<{
19
+ minTsToKeep: number;
20
+ }, {
21
+ minTsToKeep: import("convex/values").VFloat64<number, "required">;
22
+ }, "required", "minTsToKeep">, {}, {}, {}>;
23
+ }, true>;
24
+ export default _default;
25
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/component/schema.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAGA,wBAaG"}
@@ -0,0 +1,17 @@
1
+ import { defineSchema, defineTable } from "convex/server";
2
+ import { v } from "convex/values";
3
+ export default defineSchema({
4
+ history: defineTable({
5
+ id: v.string(),
6
+ ts: v.number(),
7
+ doc: v.union(v.any(), v.null()),
8
+ isDeleted: v.boolean(),
9
+ attribution: v.any(),
10
+ })
11
+ .index("ts", ["ts"])
12
+ .index("id", ["id", "ts"]),
13
+ vacuumed: defineTable({
14
+ minTsToKeep: v.number(),
15
+ }),
16
+ });
17
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/component/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,CAAC,EAAE,MAAM,eAAe,CAAC;AAElC,eAAe,YAAY,CAAC;IAC1B,OAAO,EAAE,WAAW,CAAC;QACnB,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;QACd,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;QACd,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC/B,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE;QACtB,WAAW,EAAE,CAAC,CAAC,GAAG,EAAE;KACrB,CAAC;SACC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC;SACnB,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC5B,QAAQ,EAAE,WAAW,CAAC;QACpB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;KACxB,CAAC;CACH,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function subtract(a: number, b: number): number;
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/react/index.ts"],"names":[],"mappings":"AAKA,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAErD"}
@@ -0,0 +1,8 @@
1
+ // This is where React components go.
2
+ if (typeof window === "undefined") {
3
+ throw new Error("this is frontend code, but it's running somewhere else!");
4
+ }
5
+ export function subtract(a, b) {
6
+ return a - b;
7
+ }
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/react/index.ts"],"names":[],"mappings":"AAAA,qCAAqC;AACrC,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;IAClC,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;AAC7E,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,CAAS,EAAE,CAAS;IAC3C,OAAO,CAAC,GAAG,CAAC,CAAC;AACf,CAAC"}
package/package.json ADDED
@@ -0,0 +1,84 @@
1
+ {
2
+ "name": "@convex-dev/table-history",
3
+ "description": "A table history component for Convex.",
4
+ "repository": "github:get-convex/table-history",
5
+ "homepage": "https://github.com/get-convex/table-history#readme",
6
+ "bugs": {
7
+ "email": "support@convex.dev",
8
+ "url": "https://github.com/get-convex/table-history/issues"
9
+ },
10
+ "version": "0.1.3",
11
+ "license": "Apache-2.0",
12
+ "keywords": [
13
+ "convex",
14
+ "component"
15
+ ],
16
+ "type": "module",
17
+ "scripts": {
18
+ "build": "npm run build:esm",
19
+ "build:esm": "tsc --project ./esm.json",
20
+ "dev": "convex dev",
21
+ "dev:frontend": "cd example && vite",
22
+ "typecheck": "tsc --noEmit",
23
+ "prepare": "npm run build",
24
+ "test": "vitest run",
25
+ "test:debug": "vitest --inspect-brk --no-file-parallelism",
26
+ "test:coverage": "vitest run --coverage --coverage.reporter=text"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "src"
31
+ ],
32
+ "exports": {
33
+ "./package.json": "./package.json",
34
+ ".": {
35
+ "types": "./dist/esm/client/index.d.ts",
36
+ "default": "./dist/esm/client/index.js"
37
+ },
38
+ "./react": {
39
+ "types": "./dist/esm/react/index.d.ts",
40
+ "default": "./dist/esm/react/index.js"
41
+ },
42
+ "./_generated/component.js": {
43
+ "types": "./dist/esm/component/_generated/component.d.ts",
44
+ "default": "./dist/esm/component/_generated/component.js"
45
+ },
46
+ "./convex.config": {
47
+ "types": "./dist/esm/component/convex.config.d.ts",
48
+ "default": "./dist/esm/component/convex.config.js"
49
+ },
50
+ "./convex.config.js": {
51
+ "types": "./dist/esm/component/convex.config.d.ts",
52
+ "default": "./dist/esm/component/convex.config.js"
53
+ }
54
+ },
55
+ "dependencies": {
56
+ "convex-helpers": "^0.1.100"
57
+ },
58
+ "peerDependencies": {
59
+ "convex": ">= 1.18.0"
60
+ },
61
+ "devDependencies": {
62
+ "@eslint/eslintrc": "^3.1.0",
63
+ "@eslint/js": "^9.9.1",
64
+ "@types/node": "^20.19.37",
65
+ "@types/react": "^18.3.3",
66
+ "@types/react-dom": "^18.3.0",
67
+ "@vitejs/plugin-react": "^4.3.1",
68
+ "convex": "1.34.1",
69
+ "convex-test": "^0.0.33",
70
+ "eslint": "^9.9.1",
71
+ "eslint-plugin-react-hooks": "^5.1.0-rc.0",
72
+ "eslint-plugin-react-refresh": "^0.4.9",
73
+ "globals": "^15.9.0",
74
+ "prettier": "3.2.5",
75
+ "react": "^18.3.1",
76
+ "react-dom": "^18.3.1",
77
+ "typescript": "5.8.3",
78
+ "typescript-eslint": "^8.4.0",
79
+ "vite": "^5.4.1",
80
+ "vitest": "^2.1.4"
81
+ },
82
+ "types": "./dist/esm/client/index.d.ts",
83
+ "module": "./dist/esm/client/index.js"
84
+ }
@@ -0,0 +1,174 @@
1
+ import {
2
+ DocumentByName,
3
+ GenericDataModel,
4
+ GenericMutationCtx,
5
+ GenericQueryCtx,
6
+ PaginationOptions,
7
+ TableNamesInDataModel,
8
+ } from "convex/server";
9
+ import { GenericId } from "convex/values";
10
+ import type { Serializability } from "../component/lib.js";
11
+ import type { ComponentApi } from "../component/_generated/component.js";
12
+
13
+ export class TableHistory<
14
+ DataModel extends GenericDataModel,
15
+ TableName extends TableNamesInDataModel<DataModel>,
16
+ > {
17
+ public options: {
18
+ serializability: Serializability;
19
+ };
20
+ constructor(
21
+ public component: ComponentApi,
22
+ options?: {
23
+ serializability?: Serializability;
24
+ }
25
+ ) {
26
+ this.options = {
27
+ serializability: options?.serializability ?? "wallclock",
28
+ };
29
+ }
30
+
31
+ /**
32
+ * Write a new history entry.
33
+ *
34
+ * @argument attribution an arbitrary object that will be stored with the
35
+ * history entry. Attribution can include actor/user identity, reason for
36
+ * change, etc.
37
+ */
38
+ async update(
39
+ ctx: RunMutationCtx,
40
+ id: GenericId<TableName>,
41
+ doc: DocumentByName<DataModel, TableName> | null,
42
+ attribution: unknown = null
43
+ ) {
44
+ return ctx.runMutation(this.component.lib.update, {
45
+ id,
46
+ doc,
47
+ serializability: this.options.serializability,
48
+ attribution,
49
+ });
50
+ }
51
+
52
+ /**
53
+ * Paginate the history of the table, from newest to oldest.
54
+ *
55
+ * @argument maxTs To keep pages contiguous, set `maxTs` to a fixed timestamp
56
+ * (milliseconds since epoch, like Date.now()) and keep
57
+ * it the same for subsequent pages.
58
+ */
59
+ async listHistory(
60
+ ctx: RunQueryCtx,
61
+ maxTs: number,
62
+ paginationOpts: PaginationOptions
63
+ ) {
64
+ return ctx.runQuery(this.component.lib.listHistory, {
65
+ maxTs,
66
+ paginationOpts,
67
+ });
68
+ }
69
+ /**
70
+ * Paginate the history of a single document, from newest to oldest.
71
+ *
72
+ * @argument maxTs To keep pages contiguous, set `maxTs` to a fixed timestamp
73
+ * (milliseconds since epoch, like Date.now()) and keep
74
+ * it the same for subsequent pages.
75
+ */
76
+ async listDocumentHistory(
77
+ ctx: RunQueryCtx,
78
+ id: GenericId<TableName>,
79
+ maxTs: number,
80
+ paginationOpts: PaginationOptions
81
+ ) {
82
+ return ctx.runQuery(this.component.lib.listDocumentHistory, {
83
+ id,
84
+ maxTs,
85
+ paginationOpts,
86
+ });
87
+ }
88
+ /**
89
+ * Paginate a snapshot of the table at a fixed timestamp.
90
+ *
91
+ * @argument ts the snapshot at which you want to list the table (milliseconds since epoch)
92
+ * @argument currentTs a fixed recent timestamp (milliseconds since epoch, like Date.now())
93
+ * which should be the same for subsequent pages.
94
+ */
95
+ async listSnapshot(
96
+ ctx: RunQueryCtx,
97
+ snapshotTs: number,
98
+ currentTs: number,
99
+ paginationOpts: PaginationOptions
100
+ ) {
101
+ return ctx.runQuery(this.component.lib.listSnapshot, {
102
+ snapshotTs,
103
+ currentTs,
104
+ paginationOpts,
105
+ });
106
+ }
107
+
108
+ /**
109
+ * Delete old history entries.
110
+ *
111
+ * @argument minTsToKeep the timestamp (milliseconds since epoch) of the oldest
112
+ * snapshot of history that should be kept.
113
+ */
114
+ async vacuumHistory(ctx: RunMutationCtx, minTsToKeep: number) {
115
+ return ctx.runMutation(this.component.lib.vacuumHistory, { minTsToKeep });
116
+ }
117
+
118
+ /**
119
+ * For use with `Triggers` from "convex-helpers/server/triggers".
120
+ */
121
+ trigger<Ctx extends RunMutationCtx>(): Trigger<Ctx, DataModel, TableName> {
122
+ return async (ctx, change) => {
123
+ let attribution: unknown = null;
124
+ if (
125
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
126
+ (ctx as any).auth &&
127
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
128
+ typeof (ctx as any).auth.getUserIdentity === "function"
129
+ ) {
130
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
131
+ attribution = await (ctx as any).auth.getUserIdentity();
132
+ }
133
+ await this.update(ctx, change.id, change.newDoc, attribution);
134
+ };
135
+ }
136
+ }
137
+
138
+ /* Type utils follow */
139
+
140
+ export type Trigger<
141
+ Ctx,
142
+ DataModel extends GenericDataModel,
143
+ TableName extends TableNamesInDataModel<DataModel>,
144
+ > = (ctx: Ctx, change: Change<DataModel, TableName>) => Promise<void>;
145
+
146
+ export type Change<
147
+ DataModel extends GenericDataModel,
148
+ TableName extends TableNamesInDataModel<DataModel>,
149
+ > = {
150
+ id: GenericId<TableName>;
151
+ } & (
152
+ | {
153
+ operation: "insert";
154
+ oldDoc: null;
155
+ newDoc: DocumentByName<DataModel, TableName>;
156
+ }
157
+ | {
158
+ operation: "update";
159
+ oldDoc: DocumentByName<DataModel, TableName>;
160
+ newDoc: DocumentByName<DataModel, TableName>;
161
+ }
162
+ | {
163
+ operation: "delete";
164
+ oldDoc: DocumentByName<DataModel, TableName>;
165
+ newDoc: null;
166
+ }
167
+ );
168
+
169
+ type RunQueryCtx = {
170
+ runQuery: GenericQueryCtx<GenericDataModel>["runQuery"];
171
+ };
172
+ type RunMutationCtx = {
173
+ runMutation: GenericMutationCtx<GenericDataModel>["runMutation"];
174
+ };