cojson 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/account.d.ts +6 -3
- package/dist/account.js +4 -2
- package/dist/account.js.map +1 -1
- package/dist/coValue.d.ts +44 -80
- package/dist/coValue.js +4 -348
- package/dist/coValue.js.map +1 -1
- package/dist/coValueCore.d.ts +84 -0
- package/dist/coValueCore.js +356 -0
- package/dist/coValueCore.js.map +1 -0
- package/dist/coValues/coList.d.ts +114 -0
- package/dist/{contentTypes → coValues}/coList.js +59 -19
- package/dist/coValues/coList.js.map +1 -0
- package/dist/{contentTypes → coValues}/coMap.d.ts +25 -7
- package/dist/{contentTypes → coValues}/coMap.js +34 -15
- package/dist/coValues/coMap.js.map +1 -0
- package/dist/coValues/coStream.d.ts +69 -0
- package/dist/coValues/coStream.js +131 -0
- package/dist/coValues/coStream.js.map +1 -0
- package/dist/coValues/static.d.ts +14 -0
- package/dist/coValues/static.js +20 -0
- package/dist/coValues/static.js.map +1 -0
- package/dist/group.d.ts +57 -9
- package/dist/group.js +94 -28
- package/dist/group.js.map +1 -1
- package/dist/index.d.ts +19 -10
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +59 -5
- package/dist/node.js +36 -15
- package/dist/node.js.map +1 -1
- package/dist/permissions.d.ts +2 -2
- package/dist/permissions.js +1 -1
- package/dist/permissions.js.map +1 -1
- package/dist/sync.d.ts +3 -3
- package/dist/sync.js +2 -2
- package/dist/sync.js.map +1 -1
- package/dist/testUtils.d.ts +2 -2
- package/dist/testUtils.js +1 -1
- package/dist/testUtils.js.map +1 -1
- package/package.json +2 -2
- package/src/account.test.ts +1 -1
- package/src/account.ts +8 -5
- package/src/coValue.test.ts +335 -129
- package/src/coValue.ts +52 -576
- package/src/coValueCore.test.ts +180 -0
- package/src/coValueCore.ts +592 -0
- package/src/{contentTypes → coValues}/coList.ts +91 -42
- package/src/{contentTypes → coValues}/coMap.ts +40 -20
- package/src/coValues/coStream.ts +249 -0
- package/src/coValues/static.ts +31 -0
- package/src/group.test.ts +47 -0
- package/src/group.ts +120 -50
- package/src/index.ts +43 -28
- package/src/node.ts +48 -27
- package/src/permissions.test.ts +32 -32
- package/src/permissions.ts +5 -5
- package/src/sync.test.ts +77 -77
- package/src/sync.ts +5 -5
- package/src/testUtils.ts +1 -1
- package/tsconfig.json +1 -2
- package/dist/contentType.d.ts +0 -15
- package/dist/contentType.js +0 -7
- package/dist/contentType.js.map +0 -1
- package/dist/contentTypes/coList.d.ts +0 -77
- package/dist/contentTypes/coList.js.map +0 -1
- package/dist/contentTypes/coMap.js.map +0 -1
- package/dist/contentTypes/coStream.d.ts +0 -11
- package/dist/contentTypes/coStream.js +0 -16
- package/dist/contentTypes/coStream.js.map +0 -1
- package/dist/contentTypes/static.d.ts +0 -11
- package/dist/contentTypes/static.js +0 -14
- package/dist/contentTypes/static.js.map +0 -1
- package/src/contentType.test.ts +0 -284
- package/src/contentType.ts +0 -26
- package/src/contentTypes/coStream.ts +0 -24
- package/src/contentTypes/static.ts +0 -22
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { JsonObject, JsonValue } from "../jsonValue.js";
|
|
2
|
-
import { CoID } from "../
|
|
3
|
-
import {
|
|
2
|
+
import { CoID, ReadableCoValue, WriteableCoValue } from "../coValue.js";
|
|
3
|
+
import { CoValueCore, accountOrAgentIDfromSessionID } from "../coValueCore.js";
|
|
4
4
|
import { SessionID, TransactionID } from "../ids.js";
|
|
5
|
-
import {
|
|
6
|
-
import { isAccountID } from "../account.js";
|
|
5
|
+
import { Group } from "../group.js";
|
|
6
|
+
import { AccountID, isAccountID } from "../account.js";
|
|
7
7
|
|
|
8
8
|
type OpID = TransactionID & { changeIdx: number };
|
|
9
9
|
|
|
@@ -39,15 +39,17 @@ type DeletionEntry = {
|
|
|
39
39
|
deletionID: OpID;
|
|
40
40
|
} & DeletionOpPayload;
|
|
41
41
|
|
|
42
|
-
export class CoList<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
> {
|
|
42
|
+
export class CoList<T extends JsonValue, Meta extends JsonObject | null = null>
|
|
43
|
+
implements ReadableCoValue
|
|
44
|
+
{
|
|
46
45
|
id: CoID<CoList<T, Meta>>;
|
|
47
46
|
type = "colist" as const;
|
|
48
|
-
|
|
47
|
+
core: CoValueCore;
|
|
48
|
+
/** @internal */
|
|
49
49
|
afterStart: OpID[];
|
|
50
|
+
/** @internal */
|
|
50
51
|
beforeEnd: OpID[];
|
|
52
|
+
/** @internal */
|
|
51
53
|
insertions: {
|
|
52
54
|
[sessionID: SessionID]: {
|
|
53
55
|
[txIdx: number]: {
|
|
@@ -55,6 +57,7 @@ export class CoList<
|
|
|
55
57
|
};
|
|
56
58
|
};
|
|
57
59
|
};
|
|
60
|
+
/** @internal */
|
|
58
61
|
deletionsByInsertion: {
|
|
59
62
|
[deletedSessionID: SessionID]: {
|
|
60
63
|
[deletedTxIdx: number]: {
|
|
@@ -63,9 +66,10 @@ export class CoList<
|
|
|
63
66
|
};
|
|
64
67
|
};
|
|
65
68
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
this.
|
|
69
|
+
/** @internal */
|
|
70
|
+
constructor(core: CoValueCore) {
|
|
71
|
+
this.id = core.id as CoID<CoList<T, Meta>>;
|
|
72
|
+
this.core = core;
|
|
69
73
|
this.afterStart = [];
|
|
70
74
|
this.beforeEnd = [];
|
|
71
75
|
this.insertions = {};
|
|
@@ -74,15 +78,15 @@ export class CoList<
|
|
|
74
78
|
this.fillOpsFromCoValue();
|
|
75
79
|
}
|
|
76
80
|
|
|
77
|
-
|
|
78
81
|
get meta(): Meta {
|
|
79
|
-
return this.
|
|
82
|
+
return this.core.header.meta as Meta;
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
get group(): Group {
|
|
83
|
-
return this.
|
|
86
|
+
return this.core.getGroup();
|
|
84
87
|
}
|
|
85
88
|
|
|
89
|
+
/** @internal */
|
|
86
90
|
protected fillOpsFromCoValue() {
|
|
87
91
|
this.insertions = {};
|
|
88
92
|
this.deletionsByInsertion = {};
|
|
@@ -93,7 +97,7 @@ export class CoList<
|
|
|
93
97
|
txID,
|
|
94
98
|
changes,
|
|
95
99
|
madeAt,
|
|
96
|
-
} of this.
|
|
100
|
+
} of this.core.getValidSortedTransactions()) {
|
|
97
101
|
for (const [changeIdx, changeUntyped] of changes.entries()) {
|
|
98
102
|
const change = changeUntyped as ListOpPayload<T>;
|
|
99
103
|
|
|
@@ -195,6 +199,20 @@ export class CoList<
|
|
|
195
199
|
}
|
|
196
200
|
}
|
|
197
201
|
|
|
202
|
+
/** Get the item currently at `idx`. */
|
|
203
|
+
get(idx: number): T | undefined {
|
|
204
|
+
const entry = this.entries()[idx];
|
|
205
|
+
if (!entry) {
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
return entry.value;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Returns the current items in the CoList as an array. */
|
|
212
|
+
asArray(): T[] {
|
|
213
|
+
return this.entries().map((entry) => entry.value);
|
|
214
|
+
}
|
|
215
|
+
|
|
198
216
|
entries(): { value: T; madeAt: number; opID: OpID }[] {
|
|
199
217
|
const arr: { value: T; madeAt: number; opID: OpID }[] = [];
|
|
200
218
|
for (const opID of this.afterStart) {
|
|
@@ -206,6 +224,7 @@ export class CoList<
|
|
|
206
224
|
return arr;
|
|
207
225
|
}
|
|
208
226
|
|
|
227
|
+
/** @internal */
|
|
209
228
|
private fillArrayFromOpID(
|
|
210
229
|
opID: OpID,
|
|
211
230
|
arr: { value: T; madeAt: number; opID: OpID }[]
|
|
@@ -234,6 +253,7 @@ export class CoList<
|
|
|
234
253
|
}
|
|
235
254
|
}
|
|
236
255
|
|
|
256
|
+
/** Returns the accountID of the account that inserted value at the given index. */
|
|
237
257
|
whoInserted(idx: number): AccountID | undefined {
|
|
238
258
|
const entry = this.entries()[idx];
|
|
239
259
|
if (!entry) {
|
|
@@ -247,19 +267,16 @@ export class CoList<
|
|
|
247
267
|
}
|
|
248
268
|
}
|
|
249
269
|
|
|
270
|
+
/** Returns the current items in the CoList as an array. (alias of `asArray`) */
|
|
250
271
|
toJSON(): T[] {
|
|
251
272
|
return this.asArray();
|
|
252
273
|
}
|
|
253
274
|
|
|
254
|
-
asArray(): T[] {
|
|
255
|
-
return this.entries().map((entry) => entry.value);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
275
|
map<U>(mapper: (value: T, idx: number) => U): U[] {
|
|
259
276
|
return this.entries().map((entry, idx) => mapper(entry.value, idx));
|
|
260
277
|
}
|
|
261
278
|
|
|
262
|
-
filter<U extends T>(predicate: (value: T, idx: number) => value is U): U[]
|
|
279
|
+
filter<U extends T>(predicate: (value: T, idx: number) => value is U): U[];
|
|
263
280
|
filter(predicate: (value: T, idx: number) => boolean): T[] {
|
|
264
281
|
return this.entries()
|
|
265
282
|
.filter((entry, idx) => predicate(entry.value, idx))
|
|
@@ -271,31 +288,45 @@ export class CoList<
|
|
|
271
288
|
initialValue: U
|
|
272
289
|
): U {
|
|
273
290
|
return this.entries().reduce(
|
|
274
|
-
(accumulator, entry, idx) =>
|
|
275
|
-
reducer(accumulator, entry.value, idx),
|
|
291
|
+
(accumulator, entry, idx) => reducer(accumulator, entry.value, idx),
|
|
276
292
|
initialValue
|
|
277
293
|
);
|
|
278
294
|
}
|
|
279
295
|
|
|
296
|
+
subscribe(listener: (coMap: CoList<T, Meta>) => void): () => void {
|
|
297
|
+
return this.core.subscribe((content) => {
|
|
298
|
+
listener(content as CoList<T, Meta>);
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
280
302
|
edit(
|
|
281
303
|
changer: (editable: WriteableCoList<T, Meta>) => void
|
|
282
304
|
): CoList<T, Meta> {
|
|
283
|
-
const editable = new WriteableCoList<T, Meta>(this.
|
|
305
|
+
const editable = new WriteableCoList<T, Meta>(this.core);
|
|
284
306
|
changer(editable);
|
|
285
|
-
return new CoList(this.
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
subscribe(listener: (coMap: CoList<T, Meta>) => void): () => void {
|
|
289
|
-
return this.coValue.subscribe((content) => {
|
|
290
|
-
listener(content as CoList<T, Meta>);
|
|
291
|
-
});
|
|
307
|
+
return new CoList(this.core);
|
|
292
308
|
}
|
|
293
309
|
}
|
|
294
310
|
|
|
295
311
|
export class WriteableCoList<
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
>
|
|
312
|
+
T extends JsonValue,
|
|
313
|
+
Meta extends JsonObject | null = null
|
|
314
|
+
>
|
|
315
|
+
extends CoList<T, Meta>
|
|
316
|
+
implements WriteableCoValue
|
|
317
|
+
{
|
|
318
|
+
/** @internal */
|
|
319
|
+
edit(
|
|
320
|
+
_changer: (editable: WriteableCoList<T, Meta>) => void
|
|
321
|
+
): CoList<T, Meta> {
|
|
322
|
+
throw new Error("Already editing.");
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** Appends a new item after index `after`.
|
|
326
|
+
*
|
|
327
|
+
* If `privacy` is `"private"` **(default)**, both `value` is encrypted in the transaction, only readable by other members of the group this `CoList` belongs to. Not even sync servers can see the content in plaintext.
|
|
328
|
+
*
|
|
329
|
+
* If `privacy` is `"trusting"`, both `value` is stored in plaintext in the transaction, visible to everyone who gets a hold of it, including sync servers. */
|
|
299
330
|
append(
|
|
300
331
|
after: number,
|
|
301
332
|
value: T,
|
|
@@ -315,7 +346,7 @@ export class WriteableCoList<
|
|
|
315
346
|
}
|
|
316
347
|
opIDBefore = "start";
|
|
317
348
|
}
|
|
318
|
-
this.
|
|
349
|
+
this.core.makeTransaction(
|
|
319
350
|
[
|
|
320
351
|
{
|
|
321
352
|
op: "app",
|
|
@@ -329,12 +360,28 @@ export class WriteableCoList<
|
|
|
329
360
|
this.fillOpsFromCoValue();
|
|
330
361
|
}
|
|
331
362
|
|
|
363
|
+
/** Pushes a new item to the end of the list.
|
|
364
|
+
*
|
|
365
|
+
* If `privacy` is `"private"` **(default)**, both `value` is encrypted in the transaction, only readable by other members of the group this `CoList` belongs to. Not even sync servers can see the content in plaintext.
|
|
366
|
+
*
|
|
367
|
+
* If `privacy` is `"trusting"`, both `value` is stored in plaintext in the transaction, visible to everyone who gets a hold of it, including sync servers. */
|
|
332
368
|
push(value: T, privacy: "private" | "trusting" = "private"): void {
|
|
333
369
|
// TODO: optimize
|
|
334
370
|
const entries = this.entries();
|
|
335
|
-
this.append(
|
|
371
|
+
this.append(
|
|
372
|
+
entries.length > 0 ? entries.length - 1 : 0,
|
|
373
|
+
value,
|
|
374
|
+
privacy
|
|
375
|
+
);
|
|
336
376
|
}
|
|
337
377
|
|
|
378
|
+
/**
|
|
379
|
+
* Prepends a new item before index `before`.
|
|
380
|
+
*
|
|
381
|
+
* If `privacy` is `"private"` **(default)**, both `value` is encrypted in the transaction, only readable by other members of the group this `CoList` belongs to. Not even sync servers can see the content in plaintext.
|
|
382
|
+
*
|
|
383
|
+
* If `privacy` is `"trusting"`, both `value` is stored in plaintext in the transaction, visible to everyone who gets a hold of it, including sync servers.
|
|
384
|
+
*/
|
|
338
385
|
prepend(
|
|
339
386
|
before: number,
|
|
340
387
|
value: T,
|
|
@@ -358,7 +405,7 @@ export class WriteableCoList<
|
|
|
358
405
|
}
|
|
359
406
|
opIDAfter = "end";
|
|
360
407
|
}
|
|
361
|
-
this.
|
|
408
|
+
this.core.makeTransaction(
|
|
362
409
|
[
|
|
363
410
|
{
|
|
364
411
|
op: "pre",
|
|
@@ -372,16 +419,18 @@ export class WriteableCoList<
|
|
|
372
419
|
this.fillOpsFromCoValue();
|
|
373
420
|
}
|
|
374
421
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
422
|
+
/** Deletes the item at index `at` from the list.
|
|
423
|
+
*
|
|
424
|
+
* If `privacy` is `"private"` **(default)**, the fact of this deletion is encrypted in the transaction, only readable by other members of the group this `CoList` belongs to. Not even sync servers can see the content in plaintext.
|
|
425
|
+
*
|
|
426
|
+
* If `privacy` is `"trusting"`, the fact of this deletion is stored in plaintext in the transaction, visible to everyone who gets a hold of it, including sync servers. */
|
|
427
|
+
delete(at: number, privacy: "private" | "trusting" = "private"): void {
|
|
379
428
|
const entries = this.entries();
|
|
380
429
|
const entry = entries[at];
|
|
381
430
|
if (!entry) {
|
|
382
431
|
throw new Error("Invalid index " + at);
|
|
383
432
|
}
|
|
384
|
-
this.
|
|
433
|
+
this.core.makeTransaction(
|
|
385
434
|
[
|
|
386
435
|
{
|
|
387
436
|
op: "del",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { JsonObject, JsonValue } from '../jsonValue.js';
|
|
2
2
|
import { TransactionID } from '../ids.js';
|
|
3
|
-
import { CoID } from '../
|
|
4
|
-
import {
|
|
3
|
+
import { CoID, ReadableCoValue, WriteableCoValue } from '../coValue.js';
|
|
4
|
+
import { CoValueCore, accountOrAgentIDfromSessionID } from '../coValueCore.js';
|
|
5
5
|
import { AccountID, isAccountID } from '../account.js';
|
|
6
6
|
import { Group } from '../group.js';
|
|
7
7
|
|
|
@@ -28,37 +28,41 @@ export type MapM<M extends { [key: string]: JsonValue; }> = {
|
|
|
28
28
|
[KK in MapK<M>]: M[KK];
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/** A collaborative map with precise shape `M` and optional static metadata `Meta` */
|
|
31
32
|
export class CoMap<
|
|
32
33
|
M extends { [key: string]: JsonValue; },
|
|
33
34
|
Meta extends JsonObject | null = null,
|
|
34
|
-
> {
|
|
35
|
+
> implements ReadableCoValue {
|
|
35
36
|
id: CoID<CoMap<MapM<M>, Meta>>;
|
|
36
|
-
coValue: CoValue;
|
|
37
37
|
type = "comap" as const;
|
|
38
|
+
core: CoValueCore;
|
|
39
|
+
/** @internal */
|
|
38
40
|
ops: {
|
|
39
41
|
[KK in MapK<M>]?: MapOp<KK, M[KK]>[];
|
|
40
42
|
};
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
this.
|
|
44
|
+
/** @internal */
|
|
45
|
+
constructor(core: CoValueCore) {
|
|
46
|
+
this.id = core.id as CoID<CoMap<MapM<M>, Meta>>;
|
|
47
|
+
this.core = core;
|
|
45
48
|
this.ops = {};
|
|
46
49
|
|
|
47
50
|
this.fillOpsFromCoValue();
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
get meta(): Meta {
|
|
51
|
-
return this.
|
|
54
|
+
return this.core.header.meta as Meta;
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
get group(): Group {
|
|
55
|
-
return this.
|
|
58
|
+
return this.core.getGroup();
|
|
56
59
|
}
|
|
57
60
|
|
|
61
|
+
/** @internal */
|
|
58
62
|
protected fillOpsFromCoValue() {
|
|
59
63
|
this.ops = {};
|
|
60
64
|
|
|
61
|
-
for (const { txID, changes, madeAt } of this.
|
|
65
|
+
for (const { txID, changes, madeAt } of this.core.getValidSortedTransactions()) {
|
|
62
66
|
for (const [changeIdx, changeUntyped] of (
|
|
63
67
|
changes
|
|
64
68
|
).entries()) {
|
|
@@ -82,6 +86,7 @@ export class CoMap<
|
|
|
82
86
|
return Object.keys(this.ops) as MapK<M>[];
|
|
83
87
|
}
|
|
84
88
|
|
|
89
|
+
/** Returns the current value for the given key. */
|
|
85
90
|
get<K extends MapK<M>>(key: K): M[K] | undefined {
|
|
86
91
|
const ops = this.ops[key];
|
|
87
92
|
if (!ops) {
|
|
@@ -116,6 +121,7 @@ export class CoMap<
|
|
|
116
121
|
}
|
|
117
122
|
}
|
|
118
123
|
|
|
124
|
+
/** Returns the accountID of the last account to modify the value for the given key. */
|
|
119
125
|
whoEdited<K extends MapK<M>>(key: K): AccountID | undefined {
|
|
120
126
|
const tx = this.getLastTxID(key);
|
|
121
127
|
if (!tx) {
|
|
@@ -187,26 +193,35 @@ export class CoMap<
|
|
|
187
193
|
return json;
|
|
188
194
|
}
|
|
189
195
|
|
|
190
|
-
edit(changer: (editable: WriteableCoMap<M, Meta>) => void): CoMap<M, Meta> {
|
|
191
|
-
const editable = new WriteableCoMap<M, Meta>(this.coValue);
|
|
192
|
-
changer(editable);
|
|
193
|
-
return new CoMap(this.coValue);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
196
|
subscribe(listener: (coMap: CoMap<M, Meta>) => void): () => void {
|
|
197
|
-
return this.
|
|
197
|
+
return this.core.subscribe((content) => {
|
|
198
198
|
listener(content as CoMap<M, Meta>);
|
|
199
199
|
});
|
|
200
200
|
}
|
|
201
|
+
|
|
202
|
+
edit(changer: (editable: WriteableCoMap<M, Meta>) => void): CoMap<M, Meta> {
|
|
203
|
+
const editable = new WriteableCoMap<M, Meta>(this.core);
|
|
204
|
+
changer(editable);
|
|
205
|
+
return new CoMap(this.core);
|
|
206
|
+
}
|
|
201
207
|
}
|
|
202
208
|
|
|
203
209
|
export class WriteableCoMap<
|
|
204
210
|
M extends { [key: string]: JsonValue; },
|
|
205
211
|
Meta extends JsonObject | null = null,
|
|
212
|
+
> extends CoMap<M, Meta> implements WriteableCoValue {
|
|
213
|
+
/** @internal */
|
|
214
|
+
edit(_changer: (editable: WriteableCoMap<M, Meta>) => void): CoMap<M, Meta> {
|
|
215
|
+
throw new Error("Already editing.");
|
|
216
|
+
}
|
|
206
217
|
|
|
207
|
-
|
|
218
|
+
/** Sets a new value for the given key.
|
|
219
|
+
*
|
|
220
|
+
* If `privacy` is `"private"` **(default)**, both `key` and `value` are encrypted in the transaction, only readable by other members of the group this `CoMap` belongs to. Not even sync servers can see the content in plaintext.
|
|
221
|
+
*
|
|
222
|
+
* If `privacy` is `"trusting"`, both `key` and `value` are stored in plaintext in the transaction, visible to everyone who gets a hold of it, including sync servers. */
|
|
208
223
|
set<K extends MapK<M>>(key: K, value: M[K], privacy: "private" | "trusting" = "private"): void {
|
|
209
|
-
this.
|
|
224
|
+
this.core.makeTransaction([
|
|
210
225
|
{
|
|
211
226
|
op: "set",
|
|
212
227
|
key,
|
|
@@ -217,8 +232,13 @@ export class WriteableCoMap<
|
|
|
217
232
|
this.fillOpsFromCoValue();
|
|
218
233
|
}
|
|
219
234
|
|
|
235
|
+
/** Deletes the value for the given key (setting it to undefined).
|
|
236
|
+
*
|
|
237
|
+
* If `privacy` is `"private"` **(default)**, `key` is encrypted in the transaction, only readable by other members of the group this `CoMap` belongs to. Not even sync servers can see the content in plaintext.
|
|
238
|
+
*
|
|
239
|
+
* If `privacy` is `"trusting"`, `key` is stored in plaintext in the transaction, visible to everyone who gets a hold of it, including sync servers. */
|
|
220
240
|
delete(key: MapK<M>, privacy: "private" | "trusting" = "private"): void {
|
|
221
|
-
this.
|
|
241
|
+
this.core.makeTransaction([
|
|
222
242
|
{
|
|
223
243
|
op: "del",
|
|
224
244
|
key,
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { JsonObject, JsonValue } from "../jsonValue.js";
|
|
2
|
+
import { CoID, ReadableCoValue, WriteableCoValue } from "../coValue.js";
|
|
3
|
+
import { CoValueCore } from "../coValueCore.js";
|
|
4
|
+
import { Group } from "../group.js";
|
|
5
|
+
import { SessionID } from "../ids.js";
|
|
6
|
+
import { base64url } from "@scure/base";
|
|
7
|
+
|
|
8
|
+
export type BinaryChunkInfo = {
|
|
9
|
+
mimeType: string;
|
|
10
|
+
fileName?: string;
|
|
11
|
+
totalSizeBytes?: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type BinaryStreamStart = {
|
|
15
|
+
type: "start";
|
|
16
|
+
} & BinaryChunkInfo;
|
|
17
|
+
|
|
18
|
+
export type BinaryStreamChunk = {
|
|
19
|
+
type: "chunk";
|
|
20
|
+
chunk: `U${string}`;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type BinaryStreamEnd = {
|
|
24
|
+
type: "end";
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type BinaryCoStreamMeta = JsonObject & { type: "binary" };
|
|
28
|
+
|
|
29
|
+
export type BinaryStreamItem =
|
|
30
|
+
| BinaryStreamStart
|
|
31
|
+
| BinaryStreamChunk
|
|
32
|
+
| BinaryStreamEnd;
|
|
33
|
+
|
|
34
|
+
export class CoStream<
|
|
35
|
+
T extends JsonValue,
|
|
36
|
+
Meta extends JsonObject | null = null
|
|
37
|
+
> implements ReadableCoValue
|
|
38
|
+
{
|
|
39
|
+
id: CoID<CoStream<T, Meta>>;
|
|
40
|
+
type = "costream" as const;
|
|
41
|
+
core: CoValueCore;
|
|
42
|
+
items: {
|
|
43
|
+
[key: SessionID]: T[];
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
constructor(core: CoValueCore) {
|
|
47
|
+
this.id = core.id as CoID<CoStream<T, Meta>>;
|
|
48
|
+
this.core = core;
|
|
49
|
+
this.items = {};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get meta(): Meta {
|
|
53
|
+
return this.core.header.meta as Meta;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get group(): Group {
|
|
57
|
+
return this.core.getGroup();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** @internal */
|
|
61
|
+
protected fillFromCoValue() {
|
|
62
|
+
this.items = {};
|
|
63
|
+
|
|
64
|
+
for (const {
|
|
65
|
+
txID,
|
|
66
|
+
changes,
|
|
67
|
+
} of this.core.getValidSortedTransactions()) {
|
|
68
|
+
for (const changeUntyped of changes) {
|
|
69
|
+
const change = changeUntyped as T;
|
|
70
|
+
let entries = this.items[txID.sessionID];
|
|
71
|
+
if (!entries) {
|
|
72
|
+
entries = [];
|
|
73
|
+
this.items[txID.sessionID] = entries;
|
|
74
|
+
}
|
|
75
|
+
entries.push(change);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
getSingleStream(): T[] | undefined {
|
|
81
|
+
if (Object.keys(this.items).length === 0) {
|
|
82
|
+
return undefined;
|
|
83
|
+
} else if (Object.keys(this.items).length !== 1) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
"CoStream.getSingleStream() can only be called when there is exactly one stream"
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return Object.values(this.items)[0];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
toJSON(): {
|
|
93
|
+
[key: SessionID]: T[];
|
|
94
|
+
} {
|
|
95
|
+
return this.items;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
subscribe(listener: (coMap: CoStream<T, Meta>) => void): () => void {
|
|
99
|
+
return this.core.subscribe((content) => {
|
|
100
|
+
listener(content as CoStream<T, Meta>);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
edit(
|
|
105
|
+
changer: (editable: WriteableCoStream<T, Meta>) => void
|
|
106
|
+
): CoStream<T, Meta> {
|
|
107
|
+
const editable = new WriteableCoStream<T, Meta>(this.core);
|
|
108
|
+
changer(editable);
|
|
109
|
+
return new CoStream(this.core);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export class BinaryCoStream<
|
|
114
|
+
Meta extends BinaryCoStreamMeta = { type: "binary" }
|
|
115
|
+
>
|
|
116
|
+
extends CoStream<BinaryStreamItem, Meta>
|
|
117
|
+
implements ReadableCoValue
|
|
118
|
+
{
|
|
119
|
+
id!: CoID<BinaryCoStream<Meta>>;
|
|
120
|
+
|
|
121
|
+
getBinaryChunks():
|
|
122
|
+
| (BinaryChunkInfo & { chunks: Uint8Array[]; finished: boolean })
|
|
123
|
+
| undefined {
|
|
124
|
+
const items = this.getSingleStream();
|
|
125
|
+
|
|
126
|
+
if (!items) return;
|
|
127
|
+
|
|
128
|
+
const start = items[0];
|
|
129
|
+
|
|
130
|
+
if (start?.type !== "start") {
|
|
131
|
+
console.error("Invalid binary stream start", start);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const chunks: Uint8Array[] = [];
|
|
136
|
+
|
|
137
|
+
for (const item of items.slice(1)) {
|
|
138
|
+
if (item.type === "end") {
|
|
139
|
+
return {
|
|
140
|
+
mimeType: start.mimeType,
|
|
141
|
+
fileName: start.fileName,
|
|
142
|
+
totalSizeBytes: start.totalSizeBytes,
|
|
143
|
+
chunks,
|
|
144
|
+
finished: true,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (item.type !== "chunk") {
|
|
149
|
+
console.error("Invalid binary stream chunk", item);
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
chunks.push(base64url.decode(item.chunk.slice(1)));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
mimeType: start.mimeType,
|
|
158
|
+
fileName: start.fileName,
|
|
159
|
+
totalSizeBytes: start.totalSizeBytes,
|
|
160
|
+
chunks,
|
|
161
|
+
finished: false,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
edit(
|
|
166
|
+
changer: (editable: WriteableBinaryCoStream<Meta>) => void
|
|
167
|
+
): BinaryCoStream<Meta> {
|
|
168
|
+
const editable = new WriteableBinaryCoStream<Meta>(this.core);
|
|
169
|
+
changer(editable);
|
|
170
|
+
return new BinaryCoStream(this.core);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export class WriteableCoStream<
|
|
175
|
+
T extends JsonValue,
|
|
176
|
+
Meta extends JsonObject | null = null
|
|
177
|
+
>
|
|
178
|
+
extends CoStream<T, Meta>
|
|
179
|
+
implements WriteableCoValue
|
|
180
|
+
{
|
|
181
|
+
/** @internal */
|
|
182
|
+
edit(
|
|
183
|
+
_changer: (editable: WriteableCoStream<T, Meta>) => void
|
|
184
|
+
): CoStream<T, Meta> {
|
|
185
|
+
throw new Error("Already editing.");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
push(item: T, privacy: "private" | "trusting" = "private") {
|
|
189
|
+
this.core.makeTransaction([item], privacy);
|
|
190
|
+
this.fillFromCoValue();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export class WriteableBinaryCoStream<
|
|
195
|
+
Meta extends BinaryCoStreamMeta = { type: "binary" }
|
|
196
|
+
>
|
|
197
|
+
extends BinaryCoStream<Meta>
|
|
198
|
+
implements WriteableCoValue
|
|
199
|
+
{
|
|
200
|
+
/** @internal */
|
|
201
|
+
edit(
|
|
202
|
+
_changer: (editable: WriteableBinaryCoStream<Meta>) => void
|
|
203
|
+
): BinaryCoStream<Meta> {
|
|
204
|
+
throw new Error("Already editing.");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** @internal */
|
|
208
|
+
push(
|
|
209
|
+
item: BinaryStreamItem,
|
|
210
|
+
privacy: "private" | "trusting" = "private"
|
|
211
|
+
) {
|
|
212
|
+
WriteableCoStream.prototype.push.call(this, item, privacy);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
startBinaryStream(
|
|
216
|
+
settings: BinaryChunkInfo,
|
|
217
|
+
privacy: "private" | "trusting" = "private"
|
|
218
|
+
) {
|
|
219
|
+
this.push(
|
|
220
|
+
{
|
|
221
|
+
type: "start",
|
|
222
|
+
...settings,
|
|
223
|
+
} satisfies BinaryStreamStart,
|
|
224
|
+
privacy
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
pushBinaryStreamChunk(
|
|
229
|
+
chunk: Uint8Array,
|
|
230
|
+
privacy: "private" | "trusting" = "private"
|
|
231
|
+
) {
|
|
232
|
+
this.push(
|
|
233
|
+
{
|
|
234
|
+
type: "chunk",
|
|
235
|
+
chunk: `U${base64url.encode(chunk)}`,
|
|
236
|
+
} satisfies BinaryStreamChunk,
|
|
237
|
+
privacy
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
endBinaryStream(privacy: "private" | "trusting" = "private") {
|
|
242
|
+
this.push(
|
|
243
|
+
{
|
|
244
|
+
type: "end",
|
|
245
|
+
} satisfies BinaryStreamEnd,
|
|
246
|
+
privacy
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { JsonObject } from '../jsonValue.js';
|
|
2
|
+
import { CoID, ReadableCoValue } from '../coValue.js';
|
|
3
|
+
import { CoValueCore } from '../coValueCore.js';
|
|
4
|
+
import { Group } from '../index.js';
|
|
5
|
+
|
|
6
|
+
export class Static<T extends JsonObject> implements ReadableCoValue{
|
|
7
|
+
id: CoID<Static<T>>;
|
|
8
|
+
type = "static" as const;
|
|
9
|
+
core: CoValueCore;
|
|
10
|
+
|
|
11
|
+
constructor(core: CoValueCore) {
|
|
12
|
+
this.id = core.id as CoID<Static<T>>;
|
|
13
|
+
this.core = core;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get meta(): T {
|
|
17
|
+
return this.core.header.meta as T;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get group(): Group {
|
|
21
|
+
return this.core.getGroup();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
toJSON(): JsonObject {
|
|
25
|
+
throw new Error("Method not implemented.");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
subscribe(_listener: (coMap: Static<T>) => void): () => void {
|
|
29
|
+
throw new Error("Method not implemented.");
|
|
30
|
+
}
|
|
31
|
+
}
|