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}