-
Notifications
You must be signed in to change notification settings - Fork 0
/
server.ts
219 lines (176 loc) · 6.9 KB
/
server.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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
var settingsShare = Deno.env.get("SETTINGSSHARE");
import * as Earthstar from "https://deno.land/x/earthstar@v10.2.2/mod.ts";
const kv = await Deno.openKv();
const listenToPort = 8081 // probably irrelevant for deno deploy
let logger = new Earthstar.Logger("storage driver denoKVStorage", "gold");
// modded localstorage es5 docdriver to support denokv ================================================================================
type SerializedDriverDocs = {
byPathAndAuthor: Record<string, Earthstar.DocBase<string>>;
byPathNewestFirst: Record<Earthstar.Path, Earthstar.DocBase<string>[]>;
latestDocsByPath: Record<string, Earthstar.DocBase<string>>;
};
function isSerializedDriverDocs(value: any): value is SerializedDriverDocs {
// check if data we've loaded from denoKVStorage is actually in the format we expect
if (typeof value !== "object") {
return false;
}
return ("byPathAndAuthor" in value && "byPathNewestFirst" in value);
}
/** A replica driver which persists to DenoKVStorage, which stores a maximum of five megabytes per domain. If you're storing multiple shares, this limit will be divided among all their replicas.
* Works in browsers and Deno.
*/
export class DocDriverDenoKVStorage extends Earthstar.DocDriverMemory {
_denoKVStorageKeyConfig: string;
_denoKVStorageKeyDocs: string;
/**
* @param share - The address of the share the replica belongs to.
* @param key - An optional key you can use to differentiate storage for the same share on the same device.
*/
constructor(share: Earthstar.ShareAddress, key?: string) {
super(share);
logger.debug("constructor");
// each config item starts with this prefix and gets its own entry in deno kv
this._denoKVStorageKeyConfig = `earthstar:config:${share}${
key ? `:${key}` : ""
}`;
// but all docs are stored together inside this one item, as a giant JSON object (copied localstorage driver, maybe a different design is better)
this._denoKVStorageKeyDocs = `earthstar:documents:${share}${
key ? `:${key}` : ""
}`;
let existingData;
kv.get([this._denoKVStorageKeyDocs]).then(result => {
existingData = result.value;
});
if (existingData !== undefined) {
logger.debug("...constructor: loading data from denoKVStorage");
const parsed = JSON.parse(existingData);
if (!isSerializedDriverDocs(parsed)) {
console.warn(
`denoKVStorage data could not be parsed for share ${share}`,
);
return;
}
this.docByPathAndAuthor = new Map(
Object.entries(parsed.byPathAndAuthor),
);
this.docsByPathNewestFirst = new Map(
Object.entries(parsed.byPathNewestFirst),
);
this.latestDocsByPath = new Map(Object.entries(parsed.latestDocsByPath));
const localIndexes = Array.from(this.docByPathAndAuthor.values()).map((
doc,
) => doc._localIndex as number);
this._maxLocalIndex = Math.max(...localIndexes);
} else {
logger.debug(
"...constructor: there was no existing data in denoKVStorage",
);
}
logger.debug("...constructor is done.");
}
//--------------------------------------------------
// LIFECYCLE
// isClosed(): inherited
async close(erase: boolean) {
logger.debug("close");
if (this._isClosed) throw new Earthstar.ReplicaIsClosedError();
if (erase) {
logger.debug("...close: and erase");
this._configKv = {};
this._maxLocalIndex = -1;
this.docsByPathNewestFirst.clear();
this.docByPathAndAuthor.clear();
logger.debug("...close: erasing denoKVStorage");
await kv.delete([this._denoKVStorageKeyDocs]);
for (const key of this._listConfigKeysSync()) {
this._deleteConfigSync(key);
}
logger.debug("...close: erasing is done");
}
this._isClosed = true;
logger.debug("...close is done.");
return Promise.resolve();
}
//--------------------------------------------------
// CONFIG
async getConfig(key: string): Promise<string | undefined> {
if (this._isClosed) throw new Earthstar.ReplicaIsClosedError();
key = [this._denoKVStorageKeyConfig,key];
let result = await kv.get(key);
return result.value === null ? undefined : result;
}
async setConfig(key: string, value: string): Promise<void> {
if (this._isClosed) throw new Earthstar.ReplicaIsClosedError();
key = [this._denoKVStorageKeyConfig,key];
await kv.set(key, value);
}
async listConfigKeys(): Promise<string[]> {
if (this._isClosed) throw new Earthstar.ReplicaIsClosedError();
let keys = await kv.list()
.filter((key) => key[0]=this._denoKVStorageKeyConfig);
keys.sort();
return keys;
}
async deleteConfig(key: string): Promise<boolean> {
if (this._isClosed) throw new Earthstar.ReplicaIsClosedError();
let hadIt = await this.getConfig(key);
key = [this._denoKVStorageKeyConfig,key];
await kv.delete(key);
return hadIt !== undefined;
}
//--------------------------------------------------
// GET
// getMaxLocalIndex(): inherited
// queryDocs(query: Query): inherited
//--------------------------------------------------
// SET
async upsert<FormatType extends string, DocType extends Earthstar.DocBase<FormatType>>(
doc: DocType,
): Promise<DocType> {
if (this._isClosed) throw new Earthstar.ReplicaIsClosedError();
const upsertedDoc = await super.upsert(doc);
// After every upsert, for now, we save everything
// to denoKVStorage as a single giant JSON attachment.
// TODO: debounce this, only do it every 1 second or something
const docsToBeSerialised: SerializedDriverDocs = {
byPathAndAuthor: Object.fromEntries(this.docByPathAndAuthor),
byPathNewestFirst: Object.fromEntries(this.docsByPathNewestFirst),
latestDocsByPath: Object.fromEntries(this.latestDocsByPath),
};
await kv.set(
[this._denoKVStorageKeyDocs],
JSON.stringify(docsToBeSerialised),
);
return upsertedDoc;
}
}
//-------------------
class ReplicaDriverDenoKVStorage implements IReplicaDriver {
docDriver: IReplicaDocDriver;
attachmentDriver: IReplicaAttachmentDriver;
constructor(shareAddress: ShareAddress, namespace?: string) {
this.docDriver = new DocDriverDenoKVStorage(shareAddress, namespace);
this.attachmentDriver = new Earthstar.AttachmentDriverMemory(
shareAddress,
namespace,
);
}
}
//-------------------
settingsShare= settingsShare.trim();
if (settingsShare.startsWith("earthstar://")) { //then it's an invite
const p = await Earthstar.parseInvitationURL(settingsShare);
settingsShare=p.shareAddress;
}
new Earthstar.Server([
new Earthstar.ExtensionServerSettings({
settingsShare: settingsShare,
onCreateReplica: (address) => {
console.log(`Creating replica for ${address}...`);
return new Earthstar.Replica({
driver: new ReplicaDriverDenoKVStorage(address),
});
},
}), new Earthstar.ExtensionSyncWeb(),
],{ port: listenToPort }
);