-
Notifications
You must be signed in to change notification settings - Fork 3
/
data.ts
138 lines (123 loc) · 3.65 KB
/
data.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import Replicache, {
JSONObject,
ReadTransaction,
WriteTransaction,
} from "replicache";
import { useSubscribe } from "replicache-react-util";
import { Shape } from "../shared/shape";
import { ClientState } from "../shared/client-state";
import {
createShape,
CreateShapeArgs,
moveShape,
MoveShapeArgs,
overShape,
OverShapeArgs,
} from "../shared/mutators";
import type { MutatorStorage } from "../shared/mutators";
import { newID } from "../shared/id";
import { Type } from "io-ts";
/**
* Abstracts Replicache storage (key/value pairs) to entities (Shape).
*/
export class Data {
private rep: Replicache;
readonly clientID: string;
constructor(rep: Replicache) {
this.rep = rep;
// TODO: Use clientID from Replicache:
// https://github.com/rocicorp/replicache-sdk-js/issues/275
this.clientID = localStorage.clientID;
if (!this.clientID) {
this.clientID = localStorage.clientID = newID();
}
this.createShape = rep.register(
"createShape",
async (tx: WriteTransaction, args: CreateShapeArgs) => {
await createShape(this.mutatorStorage(tx), args);
}
);
this.moveShape = rep.register(
"moveShape",
async (tx: WriteTransaction, args: MoveShapeArgs) => {
await moveShape(this.mutatorStorage(tx), args);
}
);
this.overShape = rep.register(
"overShape",
async (tx: WriteTransaction, args: OverShapeArgs) => {
await overShape(this.mutatorStorage(tx), args);
}
);
}
// TODO: Is there a way for Typescript to infer this from the assignment in constructor?
readonly createShape: (args: CreateShapeArgs) => Promise<void>;
readonly moveShape: (args: MoveShapeArgs) => Promise<void>;
readonly overShape: (args: OverShapeArgs) => Promise<void>;
useShapeIDs(): Array<string> {
return useSubscribe(
this.rep,
async (tx: ReadTransaction) => {
const shapes = await tx.scanAll({ prefix: "shape-" });
return shapes.map(([k, _]) => k.split("-")[1]);
},
[]
);
}
useShapeByID(id: string): Shape | null {
return useSubscribe(
this.rep,
(tx: ReadTransaction) => {
return this.getShape(tx, id);
},
null
);
}
useOverShapeID(): string | null {
return useSubscribe(this.rep, async (tx: ReadTransaction) => {
return (await this.getClientState(tx, this.clientID)).overID;
});
}
private async getShape(
tx: ReadTransaction,
id: string
): Promise<Shape | null> {
// TODO: validate returned shape - can be wrong in case app reboots with
// new code and old storage. We can decode, but then what?
// See https://github.com/rocicorp/replicache-sdk-js/issues/285.
return ((await tx.get(`shape-${id}`)) as unknown) as Shape | null;
}
private async putShape(tx: WriteTransaction, id: string, shape: Shape) {
return await tx.put(`shape-${id}`, (shape as unknown) as JSONObject);
}
private async getClientState(
tx: ReadTransaction,
id: string
): Promise<ClientState> {
return (
(((await tx.get(
`client-state-${id}`
)) as unknown) as ClientState | null) || {
overID: "",
}
);
}
private async putClientState(
tx: WriteTransaction,
id: string,
client: ClientState
) {
return await tx.put(
`client-state-${id}`,
(client as unknown) as JSONObject
);
}
private mutatorStorage(tx: WriteTransaction): MutatorStorage {
return {
getShape: this.getShape.bind(null, tx),
putShape: this.putShape.bind(null, tx),
getClientState: this.getClientState.bind(null, tx),
putClientState: this.putClientState.bind(null, tx),
};
}
}