a tool for shared writing and social publishing
1import { Replicache, WriteTransaction } from "replicache"; 2import * as Y from "yjs"; 3import * as base64 from "base64-js"; 4import { FactWithIndexes, scanIndex } from "./utils"; 5import { Attribute, Attributes, FilterAttributes } from "./attributes"; 6import type { Fact, ReplicacheMutators } from "."; 7import { FactInput, MutationContext } from "./mutations"; 8import { supabaseBrowserClient } from "supabase/browserClient"; 9import { v7 } from "uuid"; 10import { UndoManager } from "src/undoManager"; 11 12export function clientMutationContext( 13 tx: WriteTransaction, 14 { 15 rep, 16 undoManager, 17 ignoreUndo, 18 defaultEntitySet, 19 permission_token_id, 20 }: { 21 undoManager: UndoManager; 22 rep: Replicache<ReplicacheMutators>; 23 ignoreUndo: boolean; 24 defaultEntitySet: string; 25 permission_token_id: string; 26 }, 27) { 28 let ctx: MutationContext = { 29 permission_token_id, 30 async runOnServer(cb) {}, 31 async runOnClient(cb) { 32 let supabase = supabaseBrowserClient(); 33 return cb({ supabase, tx }); 34 }, 35 async createEntity({ entityID }) { 36 tx.set(entityID, true); 37 return true; 38 }, 39 scanIndex: { 40 async eav(entity, attribute) { 41 return scanIndex(tx).eav(entity, attribute); 42 }, 43 }, 44 async assertFact(f) { 45 let attribute = Attributes[f.attribute as Attribute]; 46 if (!attribute) return; 47 let id = f.id || v7(); 48 let data = { ...f.data }; 49 let existingFact = [] as Fact<any>[]; 50 if (attribute.cardinality === "one") { 51 existingFact = await scanIndex(tx).eav(f.entity, f.attribute); 52 if (existingFact[0]) { 53 id = existingFact[0].id; 54 if (attribute.type === "text") { 55 const oldUpdate = base64.toByteArray( 56 ( 57 existingFact[0]?.data as Fact< 58 keyof FilterAttributes<{ type: "text" }> 59 >["data"] 60 ).value, 61 ); 62 let textData = data as Fact< 63 keyof FilterAttributes<{ type: "text" }> 64 >["data"]; 65 const newUpdate = base64.toByteArray(textData.value); 66 const updateBytes = Y.mergeUpdates([oldUpdate, newUpdate]); 67 textData.value = base64.fromByteArray(updateBytes); 68 } 69 } 70 } 71 if (!ignoreUndo) 72 undoManager.add({ 73 undo: () => { 74 if (existingFact[0]) { 75 rep.mutate.assertFact({ ignoreUndo: true, ...existingFact[0] }); 76 } else { 77 if (attribute.cardinality === "one" && !f.id) 78 rep.mutate.retractAttribute({ 79 ignoreUndo: true, 80 attribute: f.attribute as keyof FilterAttributes<{ 81 cardinality: "one"; 82 }>, 83 entity: f.entity, 84 }); 85 rep.mutate.retractFact({ ignoreUndo: true, factID: id }); 86 } 87 }, 88 redo: () => { 89 rep.mutate.assertFact({ ignoreUndo: true, ...(f as Fact<any>) }); 90 }, 91 }); 92 await tx.set(id, FactWithIndexes({ id, ...f, data })); 93 }, 94 async retractFact(id) { 95 let fact = await tx.get(id); 96 if (!ignoreUndo) 97 undoManager.add({ 98 undo: () => { 99 if (fact) { 100 rep.mutate.assertFact({ 101 ignoreUndo: true, 102 ...(fact as Fact<any>), 103 }); 104 } else { 105 rep.mutate.retractFact({ ignoreUndo: true, factID: id }); 106 } 107 }, 108 redo: () => { 109 rep.mutate.retractFact({ factID: id }); 110 }, 111 }); 112 await tx.del(id); 113 }, 114 async deleteEntity(entity) { 115 let existingFacts = await tx 116 .scan<Fact<Attribute>>({ 117 indexName: "eav", 118 prefix: `${entity}`, 119 }) 120 .toArray(); 121 let references = await tx 122 .scan<Fact<Attribute>>({ 123 indexName: "vae", 124 prefix: entity, 125 }) 126 .toArray(); 127 let facts = [...existingFacts, ...references]; 128 await Promise.all(facts.map((f) => tx.del(f.id))); 129 if (!ignoreUndo && facts.length > 0) { 130 undoManager.add({ 131 undo: async () => { 132 let input: FactInput[] & { ignoreUndo?: true } = facts.map( 133 (f) => 134 ({ 135 id: f.id, 136 attribute: f.attribute, 137 entity: f.entity, 138 data: f.data, 139 }) as FactInput, 140 ); 141 input.ignoreUndo = true; 142 await rep.mutate.createEntity([ 143 { entityID: entity, permission_set: defaultEntitySet }, 144 ]); 145 await rep.mutate.assertFact(input); 146 }, 147 redo: () => { 148 rep.mutate.deleteEntity({ entity, ignoreUndo: true }); 149 }, 150 }); 151 } 152 }, 153 }; 154 return ctx; 155}