1
1
import { v } from "convex/values" ;
2
- import { components } from "./_generated/api" ;
3
- import { mutation } from "./_generated/server" ;
2
+ import { components , internal } from "./_generated/api" ;
3
+ import { action , internalMutation , mutation } from "./_generated/server" ;
4
4
import { ProsemirrorSync } from "@convex-dev/prosemirror-sync" ;
5
5
import { getSchema } from "@tiptap/core" ;
6
6
import { Transform , Step } from "@tiptap/pm/transform" ;
@@ -23,37 +23,58 @@ export const {
23
23
// const user = await userFromAuth(ctx);
24
24
// ...validate that the user can write to this document
25
25
} ,
26
- onSnapshot ( ctx , id , snapshot , version ) {
26
+ async onSnapshot ( ctx , id , snapshot , version ) {
27
27
// ...do something with the snapshot, like store a copy in another table,
28
28
// save a text version of the document for text search, or generate
29
29
// embeddings for vector search.
30
+ const node = getSchema ( extensions ) . nodeFromJSON ( JSON . parse ( snapshot ) ) ;
31
+ await ctx . scheduler . runAfter ( 0 , internal . example . updateDocSearchIndex , {
32
+ id,
33
+ content : node . textContent ,
34
+ } ) ;
30
35
} ,
31
36
} ) ;
32
37
33
- // This is an example of how to modify a document using the transform fn.
34
- export const transformExample = mutation ( {
35
- args : {
36
- id : v . string ( ) ,
38
+ async function generateContent ( doc : string ) {
39
+ return `Overall length: ${ doc . length } ` ;
40
+ }
41
+
42
+ // We keep a text search index on the documents table for easy search.
43
+ // But we don't update that full version all the time, for efficiency.
44
+ export const updateDocSearchIndex = internalMutation ( {
45
+ args : { id : v . string ( ) , content : v . string ( ) } ,
46
+ handler : async ( ctx , { id, content } ) => {
47
+ const existing = await ctx . db
48
+ . query ( "documents" )
49
+ . withIndex ( "docId" , ( q ) => q . eq ( "docId" , id ) )
50
+ . unique ( ) ;
51
+ if ( ! existing ) {
52
+ await ctx . db . insert ( "documents" , { docId : id , content } ) ;
53
+ } else {
54
+ await ctx . db . patch ( existing . _id , { content } ) ;
55
+ }
37
56
} ,
57
+ } ) ;
58
+
59
+ // This is an example of how to modify a document using the transform fn.
60
+ export const transformExample = action ( {
61
+ args : { id : v . string ( ) } ,
38
62
handler : async ( ctx , { id } ) => {
39
63
const schema = getSchema ( extensions ) ;
40
64
const { doc, version } = await prosemirrorSync . getDoc ( ctx , id , schema ) ;
41
- // You could do something more complex here, like generating an AI summary.
42
- const newContent = `Overall length: ${ doc . textContent . length } ` ;
65
+ const newContent = await generateContent ( doc . textContent ) ;
43
66
const node = await prosemirrorSync . transform ( ctx , id , schema , ( doc , v ) => {
44
67
if ( v !== version ) {
45
- console . log ( "FYI the text has changed" ) ;
46
68
// If we wanted to avoid making changes, we could return null here.
47
- // return null;
69
+ // Or we could rebase our changes on top of the new document.
48
70
}
49
71
const tr = EditorState . create ( { doc } ) . tr ;
50
- tr . insertText ( newContent , 0 ) ;
51
- // Alternatively, you could use a Transform object directly:
52
- // const tr = new Transform(node);
53
- // tr.insert(0, schema.text("Hello world"));
54
- return tr ;
72
+ return tr . insertText ( newContent , 0 ) ;
73
+ } ) ;
74
+ await ctx . scheduler . runAfter ( 0 , internal . example . updateDocSearchIndex , {
75
+ id ,
76
+ content : node . textContent ,
55
77
} ) ;
56
- return node . toJSON ( ) ;
57
78
} ,
58
79
} ) ;
59
80
0 commit comments