a tool for shared writing and social publishing
298
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 8f68be5863d8e7c1944cc5ce848a9a9fc6a032aa 162 lines 5.2 kB view raw
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 } else if (f.id) { 71 // For cardinality "many" with an explicit ID, fetch the existing fact 72 // so undo can restore it instead of deleting 73 let fact = await tx.get(f.id); 74 if (fact) { 75 existingFact = [fact as Fact<any>]; 76 } 77 } 78 if (!ignoreUndo) 79 undoManager.add({ 80 undo: () => { 81 if (existingFact[0]) { 82 rep.mutate.assertFact({ ignoreUndo: true, ...existingFact[0] }); 83 } else { 84 if (attribute.cardinality === "one" && !f.id) 85 rep.mutate.retractAttribute({ 86 ignoreUndo: true, 87 attribute: f.attribute as keyof FilterAttributes<{ 88 cardinality: "one"; 89 }>, 90 entity: f.entity, 91 }); 92 rep.mutate.retractFact({ ignoreUndo: true, factID: id }); 93 } 94 }, 95 redo: () => { 96 rep.mutate.assertFact({ ignoreUndo: true, ...(f as Fact<any>) }); 97 }, 98 }); 99 await tx.set(id, FactWithIndexes({ id, ...f, data })); 100 }, 101 async retractFact(id) { 102 let fact = await tx.get(id); 103 if (!ignoreUndo) 104 undoManager.add({ 105 undo: () => { 106 if (fact) { 107 rep.mutate.assertFact({ 108 ignoreUndo: true, 109 ...(fact as Fact<any>), 110 }); 111 } else { 112 rep.mutate.retractFact({ ignoreUndo: true, factID: id }); 113 } 114 }, 115 redo: () => { 116 rep.mutate.retractFact({ factID: id }); 117 }, 118 }); 119 await tx.del(id); 120 }, 121 async deleteEntity(entity) { 122 let existingFacts = await tx 123 .scan<Fact<Attribute>>({ 124 indexName: "eav", 125 prefix: `${entity}`, 126 }) 127 .toArray(); 128 let references = await tx 129 .scan<Fact<Attribute>>({ 130 indexName: "vae", 131 prefix: entity, 132 }) 133 .toArray(); 134 let facts = [...existingFacts, ...references]; 135 await Promise.all(facts.map((f) => tx.del(f.id))); 136 if (!ignoreUndo && facts.length > 0) { 137 undoManager.add({ 138 undo: async () => { 139 let input: FactInput[] & { ignoreUndo?: true } = facts.map( 140 (f) => 141 ({ 142 id: f.id, 143 attribute: f.attribute, 144 entity: f.entity, 145 data: f.data, 146 }) as FactInput, 147 ); 148 input.ignoreUndo = true; 149 await rep.mutate.createEntity([ 150 { entityID: entity, permission_set: defaultEntitySet }, 151 ]); 152 await rep.mutate.assertFact(input); 153 }, 154 redo: () => { 155 rep.mutate.deleteEntity({ entity, ignoreUndo: true }); 156 }, 157 }); 158 } 159 }, 160 }; 161 return ctx; 162}