Something went wrong. Try again.
a tool for shared writing and social publishing
Something went wrong. Try again.
5.2 kB · 162 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163import { Replicache, WriteTransaction } from "replicache";import * as Y from "yjs";import * as base64 from "base64-js";import { FactWithIndexes, scanIndex } from "./utils";import { Attribute, Attributes, FilterAttributes } from "./attributes";import type { Fact, ReplicacheMutators } from ".";import { FactInput, MutationContext } from "./mutations";import { supabaseBrowserClient } from "supabase/browserClient";import { v7 } from "uuid";import { UndoManager } from "src/undoManager";
export function clientMutationContext( tx: WriteTransaction, { rep, undoManager, ignoreUndo, defaultEntitySet, permission_token_id, }: { undoManager: UndoManager; rep: Replicache<ReplicacheMutators>; ignoreUndo: boolean; defaultEntitySet: string; permission_token_id: string; },) { let ctx: MutationContext = { permission_token_id, async runOnServer(cb) {}, async runOnClient(cb) { let supabase = supabaseBrowserClient(); return cb({ supabase, tx }); }, async createEntity({ entityID }) { tx.set(entityID, true); return true; }, scanIndex: { async eav(entity, attribute) { return scanIndex(tx).eav(entity, attribute); }, }, async assertFact(f) { let attribute = Attributes[f.attribute as Attribute]; if (!attribute) return; let id = f.id || v7(); let data = { ...f.data }; let existingFact = [] as Fact<any>[]; if (attribute.cardinality === "one") { existingFact = await scanIndex(tx).eav(f.entity, f.attribute); if (existingFact[0]) { id = existingFact[0].id; if (attribute.type === "text") { const oldUpdate = base64.toByteArray( ( existingFact[0]?.data as Fact< keyof FilterAttributes<{ type: "text" }> >["data"] ).value, ); let textData = data as Fact< keyof FilterAttributes<{ type: "text" }> >["data"]; const newUpdate = base64.toByteArray(textData.value); const updateBytes = Y.mergeUpdates([oldUpdate, newUpdate]); textData.value = base64.fromByteArray(updateBytes); } } } else if (f.id) { // For cardinality "many" with an explicit ID, fetch the existing fact // so undo can restore it instead of deleting let fact = await tx.get(f.id); if (fact) { existingFact = [fact as Fact<any>]; } } if (!ignoreUndo) undoManager.add({ undo: () => { if (existingFact[0]) { rep.mutate.assertFact({ ignoreUndo: true, ...existingFact[0] }); } else { if (attribute.cardinality === "one" && !f.id) rep.mutate.retractAttribute({ ignoreUndo: true, attribute: f.attribute as keyof FilterAttributes<{ cardinality: "one"; }>, entity: f.entity, }); rep.mutate.retractFact({ ignoreUndo: true, factID: id }); } }, redo: () => { rep.mutate.assertFact({ ignoreUndo: true, ...(f as Fact<any>) }); }, }); await tx.set(id, FactWithIndexes({ id, ...f, data })); }, async retractFact(id) { let fact = await tx.get(id); if (!ignoreUndo) undoManager.add({ undo: () => { if (fact) { rep.mutate.assertFact({ ignoreUndo: true, ...(fact as Fact<any>), }); } else { rep.mutate.retractFact({ ignoreUndo: true, factID: id }); } }, redo: () => { rep.mutate.retractFact({ factID: id }); }, }); await tx.del(id); }, async deleteEntity(entity) { let existingFacts = await tx .scan<Fact<Attribute>>({ indexName: "eav", prefix: `${entity}`, }) .toArray(); let references = await tx .scan<Fact<Attribute>>({ indexName: "vae", prefix: entity, }) .toArray(); let facts = [...existingFacts, ...references]; await Promise.all(facts.map((f) => tx.del(f.id))); if (!ignoreUndo && facts.length > 0) { undoManager.add({ undo: async () => { let input: FactInput[] & { ignoreUndo?: true } = facts.map( (f) => ({ id: f.id, attribute: f.attribute, entity: f.entity, data: f.data, }) as FactInput, ); input.ignoreUndo = true; await rep.mutate.createEntity([ { entityID: entity, permission_set: defaultEntitySet }, ]); await rep.mutate.assertFact(input); }, redo: () => { rep.mutate.deleteEntity({ entity, ignoreUndo: true }); }, }); } }, }; return ctx;}