cojson 0.8.36 → 0.8.38
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/CHANGELOG.md +13 -0
- package/dist/native/PriorityBasedMessageQueue.js +20 -11
- package/dist/native/PriorityBasedMessageQueue.js.map +1 -1
- package/dist/native/coValues/coMap.js +64 -46
- package/dist/native/coValues/coMap.js.map +1 -1
- package/dist/native/coValues/group.js +9 -3
- package/dist/native/coValues/group.js.map +1 -1
- package/dist/web/PriorityBasedMessageQueue.js +20 -11
- package/dist/web/PriorityBasedMessageQueue.js.map +1 -1
- package/dist/web/coValues/coMap.js +64 -46
- package/dist/web/coValues/coMap.js.map +1 -1
- package/dist/web/coValues/group.js +9 -3
- package/dist/web/coValues/group.js.map +1 -1
- package/package.json +5 -1
- package/src/PriorityBasedMessageQueue.ts +24 -12
- package/src/coValues/coMap.ts +96 -71
- package/src/coValues/group.ts +11 -3
- package/src/tests/PriorityBasedMessageQueue.test.ts +110 -4
- package/src/tests/coMap.test.ts +28 -13
- package/src/tests/group.test.ts +0 -2
- package/src/tests/permissions.test.ts +45 -15
- package/src/tests/testUtils.ts +20 -7
package/src/coValues/coMap.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CoID, RawCoValue } from "../coValue.js";
|
|
2
|
-
import { CoValueCore } from "../coValueCore.js";
|
|
2
|
+
import { CoValueCore, DecryptedTransaction } from "../coValueCore.js";
|
|
3
3
|
import { AgentID, TransactionID } from "../ids.js";
|
|
4
4
|
import { JsonObject, JsonValue } from "../jsonValue.js";
|
|
5
5
|
import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfromSessionID.js";
|
|
@@ -49,6 +49,9 @@ export class RawCoMapView<
|
|
|
49
49
|
cachedOps?: {
|
|
50
50
|
[Key in keyof Shape & string]?: MapOp<Key, Shape[Key]>[];
|
|
51
51
|
};
|
|
52
|
+
/** @internal */
|
|
53
|
+
validSortedTransactions?: DecryptedTransaction[];
|
|
54
|
+
|
|
52
55
|
/** @internal */
|
|
53
56
|
options?: { ignorePrivateTransactions: boolean; atTime?: number };
|
|
54
57
|
/** @internal */
|
|
@@ -59,27 +62,58 @@ export class RawCoMapView<
|
|
|
59
62
|
/** @internal */
|
|
60
63
|
constructor(
|
|
61
64
|
core: CoValueCore,
|
|
62
|
-
options?: {
|
|
65
|
+
options?: {
|
|
66
|
+
ignorePrivateTransactions: boolean;
|
|
67
|
+
atTime?: number;
|
|
68
|
+
validSortedTransactions?: DecryptedTransaction[];
|
|
69
|
+
cachedOps?: {
|
|
70
|
+
[Key in keyof Shape & string]?: MapOp<Key, Shape[Key]>[];
|
|
71
|
+
};
|
|
72
|
+
},
|
|
63
73
|
) {
|
|
64
74
|
this.id = core.id as CoID<this>;
|
|
65
75
|
this.core = core;
|
|
66
76
|
this.latest = {};
|
|
67
77
|
this.latestTxMadeAt = 0;
|
|
68
78
|
this.options = options;
|
|
79
|
+
this.cachedOps = options?.cachedOps;
|
|
80
|
+
this.validSortedTransactions = options?.validSortedTransactions;
|
|
69
81
|
|
|
70
82
|
this.processLatestTransactions();
|
|
71
83
|
}
|
|
72
84
|
|
|
73
|
-
|
|
85
|
+
/** @internal */
|
|
86
|
+
private getValidSortedTransactions() {
|
|
87
|
+
if (this.validSortedTransactions) {
|
|
88
|
+
return this.validSortedTransactions;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const validSortedTransactions = this.core.getValidSortedTransactions({
|
|
92
|
+
ignorePrivateTransactions:
|
|
93
|
+
this.options?.ignorePrivateTransactions ?? false,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
this.validSortedTransactions = validSortedTransactions;
|
|
97
|
+
|
|
98
|
+
return validSortedTransactions;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private resetCachedValues() {
|
|
102
|
+
this.validSortedTransactions = undefined;
|
|
103
|
+
this.cachedOps = undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private processLatestTransactions() {
|
|
107
|
+
// Reset all internal state and cached values
|
|
74
108
|
this.latest = {};
|
|
75
109
|
this.latestTxMadeAt = 0;
|
|
76
110
|
|
|
77
|
-
const {
|
|
111
|
+
const { latest } = this;
|
|
112
|
+
|
|
113
|
+
const atTimeFilter = this.options?.atTime;
|
|
78
114
|
|
|
79
|
-
for (const { txID, changes, madeAt } of
|
|
80
|
-
|
|
81
|
-
})) {
|
|
82
|
-
if (options?.atTime && madeAt > options.atTime) {
|
|
115
|
+
for (const { txID, changes, madeAt } of this.getValidSortedTransactions()) {
|
|
116
|
+
if (atTimeFilter && madeAt > atTimeFilter) {
|
|
83
117
|
continue;
|
|
84
118
|
}
|
|
85
119
|
|
|
@@ -92,15 +126,14 @@ export class RawCoMapView<
|
|
|
92
126
|
keyof Shape & string,
|
|
93
127
|
Shape[keyof Shape & string]
|
|
94
128
|
>;
|
|
95
|
-
|
|
129
|
+
const entry = latest[change.key];
|
|
96
130
|
if (!entry) {
|
|
97
|
-
|
|
131
|
+
latest[change.key] = {
|
|
98
132
|
txID,
|
|
99
133
|
madeAt,
|
|
100
134
|
changeIdx,
|
|
101
135
|
change,
|
|
102
136
|
};
|
|
103
|
-
latest[change.key] = entry;
|
|
104
137
|
} else if (madeAt >= entry.madeAt) {
|
|
105
138
|
entry.txID = txID;
|
|
106
139
|
entry.madeAt = madeAt;
|
|
@@ -111,6 +144,11 @@ export class RawCoMapView<
|
|
|
111
144
|
}
|
|
112
145
|
}
|
|
113
146
|
|
|
147
|
+
revalidateTransactions() {
|
|
148
|
+
this.resetCachedValues();
|
|
149
|
+
this.processLatestTransactions();
|
|
150
|
+
}
|
|
151
|
+
|
|
114
152
|
private getOps() {
|
|
115
153
|
if (this.cachedOps) {
|
|
116
154
|
return this.cachedOps;
|
|
@@ -120,11 +158,7 @@ export class RawCoMapView<
|
|
|
120
158
|
[Key in keyof Shape & string]?: MapOp<Key, Shape[Key]>[];
|
|
121
159
|
} = {};
|
|
122
160
|
|
|
123
|
-
for (const {
|
|
124
|
-
txID,
|
|
125
|
-
changes,
|
|
126
|
-
madeAt,
|
|
127
|
-
} of this.core.getValidSortedTransactions(this.options)) {
|
|
161
|
+
for (const { txID, changes, madeAt } of this.getValidSortedTransactions()) {
|
|
128
162
|
for (let changeIdx = 0; changeIdx < changes.length; changeIdx++) {
|
|
129
163
|
const change = changes[changeIdx] as MapOpPayload<
|
|
130
164
|
keyof Shape & string,
|
|
@@ -168,6 +202,8 @@ export class RawCoMapView<
|
|
|
168
202
|
ignorePrivateTransactions:
|
|
169
203
|
this.options?.ignorePrivateTransactions ?? false,
|
|
170
204
|
atTime: time,
|
|
205
|
+
cachedOps: this.cachedOps,
|
|
206
|
+
validSortedTransactions: this.validSortedTransactions,
|
|
171
207
|
});
|
|
172
208
|
Object.setPrototypeOf(clone, this);
|
|
173
209
|
return clone as this;
|
|
@@ -182,10 +218,10 @@ export class RawCoMapView<
|
|
|
182
218
|
return undefined;
|
|
183
219
|
}
|
|
184
220
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
);
|
|
221
|
+
const atTimeFilter = this.options?.atTime;
|
|
222
|
+
|
|
223
|
+
if (atTimeFilter) {
|
|
224
|
+
return this.getOps()[key]?.filter((op) => op.madeAt <= atTimeFilter);
|
|
189
225
|
} else {
|
|
190
226
|
return this.getOps()[key];
|
|
191
227
|
}
|
|
@@ -197,17 +233,13 @@ export class RawCoMapView<
|
|
|
197
233
|
* @category 1. Reading */
|
|
198
234
|
keys<K extends keyof Shape & string = keyof Shape & string>(): K[] {
|
|
199
235
|
return (Object.keys(this.latest) as K[]).filter((key) => {
|
|
200
|
-
const
|
|
201
|
-
if (!latest) {
|
|
202
|
-
return undefined;
|
|
203
|
-
}
|
|
236
|
+
const latestChange = this.latest[key];
|
|
204
237
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
: latest!;
|
|
238
|
+
if (!latestChange) {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
209
241
|
|
|
210
|
-
if (
|
|
242
|
+
if (latestChange.change.op === "del") {
|
|
211
243
|
return false;
|
|
212
244
|
} else {
|
|
213
245
|
return true;
|
|
@@ -221,22 +253,15 @@ export class RawCoMapView<
|
|
|
221
253
|
* @category 1. Reading
|
|
222
254
|
**/
|
|
223
255
|
get<K extends keyof Shape & string>(key: K): Shape[K] | undefined {
|
|
224
|
-
const
|
|
225
|
-
if (!
|
|
256
|
+
const latestChange = this.latest[key];
|
|
257
|
+
if (!latestChange) {
|
|
226
258
|
return undefined;
|
|
227
259
|
}
|
|
228
260
|
|
|
229
|
-
|
|
230
|
-
const lastEntry = includeUntil
|
|
231
|
-
? this.timeFilteredOps(key)?.findLast(
|
|
232
|
-
(entry) => entry.madeAt <= includeUntil,
|
|
233
|
-
)
|
|
234
|
-
: latest;
|
|
235
|
-
|
|
236
|
-
if (lastEntry?.change.op === "del") {
|
|
261
|
+
if (latestChange.change.op === "del") {
|
|
237
262
|
return undefined;
|
|
238
263
|
} else {
|
|
239
|
-
return
|
|
264
|
+
return latestChange.change.value as Shape[K];
|
|
240
265
|
}
|
|
241
266
|
}
|
|
242
267
|
|
|
@@ -248,7 +273,7 @@ export class RawCoMapView<
|
|
|
248
273
|
[K in keyof Shape & string]: Shape[K];
|
|
249
274
|
}> = {};
|
|
250
275
|
|
|
251
|
-
for (const key of
|
|
276
|
+
for (const key of Object.keys(this.latest) as (keyof Shape & string)[]) {
|
|
252
277
|
const value = this.get(key);
|
|
253
278
|
if (value !== undefined) {
|
|
254
279
|
object[key] = value;
|
|
@@ -268,34 +293,21 @@ export class RawCoMapView<
|
|
|
268
293
|
}
|
|
269
294
|
|
|
270
295
|
/** @category 5. Edit history */
|
|
271
|
-
nthEditAt<K extends keyof Shape & string>(
|
|
272
|
-
key
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
at: Date;
|
|
279
|
-
value?: Shape[K];
|
|
280
|
-
}
|
|
281
|
-
| undefined {
|
|
282
|
-
const ops = this.timeFilteredOps(key);
|
|
283
|
-
if (!ops || ops.length <= n) {
|
|
296
|
+
nthEditAt<K extends keyof Shape & string>(key: K, n: number) {
|
|
297
|
+
const ops = this.getOps()[key];
|
|
298
|
+
|
|
299
|
+
const atTimeFilter = this.options?.atTime;
|
|
300
|
+
const entry = ops?.[n];
|
|
301
|
+
|
|
302
|
+
if (!entry) {
|
|
284
303
|
return undefined;
|
|
285
304
|
}
|
|
286
305
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (this.atTimeFilter && entry.madeAt > this.atTimeFilter) {
|
|
306
|
+
if (atTimeFilter && entry.madeAt > atTimeFilter) {
|
|
290
307
|
return undefined;
|
|
291
308
|
}
|
|
292
309
|
|
|
293
|
-
return
|
|
294
|
-
by: accountOrAgentIDfromSessionID(entry.txID.sessionID),
|
|
295
|
-
tx: entry.txID,
|
|
296
|
-
at: new Date(entry.madeAt),
|
|
297
|
-
value: entry.change.op === "del" ? undefined : entry.change.value,
|
|
298
|
-
};
|
|
310
|
+
return operationToEditEntry(entry);
|
|
299
311
|
}
|
|
300
312
|
|
|
301
313
|
/** @category 5. Edit history */
|
|
@@ -310,10 +322,13 @@ export class RawCoMapView<
|
|
|
310
322
|
}
|
|
311
323
|
| undefined {
|
|
312
324
|
const ops = this.timeFilteredOps(key);
|
|
313
|
-
|
|
325
|
+
const lastEntry = ops?.[ops.length - 1];
|
|
326
|
+
|
|
327
|
+
if (!lastEntry) {
|
|
314
328
|
return undefined;
|
|
315
329
|
}
|
|
316
|
-
|
|
330
|
+
|
|
331
|
+
return operationToEditEntry(lastEntry);
|
|
317
332
|
}
|
|
318
333
|
|
|
319
334
|
/** @category 5. Edit history */
|
|
@@ -323,8 +338,8 @@ export class RawCoMapView<
|
|
|
323
338
|
return;
|
|
324
339
|
}
|
|
325
340
|
|
|
326
|
-
for (
|
|
327
|
-
yield
|
|
341
|
+
for (const entry of ops) {
|
|
342
|
+
yield operationToEditEntry(entry);
|
|
328
343
|
}
|
|
329
344
|
}
|
|
330
345
|
|
|
@@ -370,8 +385,7 @@ export class RawCoMap<
|
|
|
370
385
|
privacy,
|
|
371
386
|
);
|
|
372
387
|
|
|
373
|
-
this.
|
|
374
|
-
this.cachedOps = undefined;
|
|
388
|
+
this.revalidateTransactions();
|
|
375
389
|
}
|
|
376
390
|
|
|
377
391
|
/** Delete the given key (setting it to undefined).
|
|
@@ -396,7 +410,18 @@ export class RawCoMap<
|
|
|
396
410
|
privacy,
|
|
397
411
|
);
|
|
398
412
|
|
|
399
|
-
this.
|
|
400
|
-
this.cachedOps = undefined;
|
|
413
|
+
this.revalidateTransactions();
|
|
401
414
|
}
|
|
402
415
|
}
|
|
416
|
+
|
|
417
|
+
export function operationToEditEntry<
|
|
418
|
+
K extends string,
|
|
419
|
+
V extends JsonValue | undefined,
|
|
420
|
+
>(op: MapOp<K, V>) {
|
|
421
|
+
return {
|
|
422
|
+
by: accountOrAgentIDfromSessionID(op.txID.sessionID),
|
|
423
|
+
tx: op.txID,
|
|
424
|
+
at: new Date(op.madeAt),
|
|
425
|
+
value: op.change.op === "del" ? undefined : op.change.value,
|
|
426
|
+
};
|
|
427
|
+
}
|
package/src/coValues/group.ts
CHANGED
|
@@ -81,6 +81,7 @@ export class RawGroup<
|
|
|
81
81
|
accountID: RawAccountID | AgentID | typeof EVERYONE,
|
|
82
82
|
): { role: Role; via: CoID<RawGroup> | undefined } | undefined {
|
|
83
83
|
const roleHere = this.get(accountID);
|
|
84
|
+
|
|
84
85
|
if (roleHere === "revoked") {
|
|
85
86
|
return undefined;
|
|
86
87
|
}
|
|
@@ -92,7 +93,7 @@ export class RawGroup<
|
|
|
92
93
|
}
|
|
93
94
|
| undefined = roleHere && { role: roleHere, via: undefined };
|
|
94
95
|
|
|
95
|
-
const parentGroups = this.getParentGroups();
|
|
96
|
+
const parentGroups = this.getParentGroups(this.options?.atTime);
|
|
96
97
|
|
|
97
98
|
for (const parentGroup of parentGroups) {
|
|
98
99
|
const roleInParent = parentGroup.roleOfInternal(accountID);
|
|
@@ -109,7 +110,7 @@ export class RawGroup<
|
|
|
109
110
|
return roleInfo;
|
|
110
111
|
}
|
|
111
112
|
|
|
112
|
-
getParentGroups() {
|
|
113
|
+
getParentGroups(atTime?: number) {
|
|
113
114
|
const groups: RawGroup[] = [];
|
|
114
115
|
|
|
115
116
|
for (const key of this.keys()) {
|
|
@@ -118,7 +119,14 @@ export class RawGroup<
|
|
|
118
119
|
getParentGroupId(key),
|
|
119
120
|
"Expected parent group to be loaded",
|
|
120
121
|
);
|
|
121
|
-
|
|
122
|
+
|
|
123
|
+
const parentGroup = expectGroup(parent.getCurrentContent());
|
|
124
|
+
|
|
125
|
+
if (atTime) {
|
|
126
|
+
groups.push(parentGroup.atTime(atTime));
|
|
127
|
+
} else {
|
|
128
|
+
groups.push(parentGroup);
|
|
129
|
+
}
|
|
122
130
|
}
|
|
123
131
|
}
|
|
124
132
|
|
|
@@ -1,14 +1,90 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { metrics } from "@opentelemetry/api";
|
|
2
|
+
import {
|
|
3
|
+
AggregationTemporality,
|
|
4
|
+
InMemoryMetricExporter,
|
|
5
|
+
MeterProvider,
|
|
6
|
+
MetricReader,
|
|
7
|
+
type MetricReaderOptions,
|
|
8
|
+
type PushMetricExporter,
|
|
9
|
+
} from "@opentelemetry/sdk-metrics";
|
|
10
|
+
import { afterEach, describe, expect, test } from "vitest";
|
|
2
11
|
import { PriorityBasedMessageQueue } from "../PriorityBasedMessageQueue.js";
|
|
3
12
|
import { CO_VALUE_PRIORITY } from "../priority.js";
|
|
4
|
-
import { SyncMessage } from "../sync.js";
|
|
13
|
+
import type { SyncMessage } from "../sync.js";
|
|
14
|
+
|
|
15
|
+
interface A extends MetricReaderOptions {
|
|
16
|
+
exporter: PushMetricExporter;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* This is a test metric reader that uses an in-memory metric exporter and exposes a method to get the value of a metric given its name and attributes.
|
|
21
|
+
*
|
|
22
|
+
* This is useful for testing the values of metrics that are collected by the SDK.
|
|
23
|
+
*
|
|
24
|
+
* TODO: We could move this to a separate file and make it a utility class that can be used in other tests.
|
|
25
|
+
* TODO: We may want to rethink how we access metrics (see `getMetricValue` method) to make it more flexible.
|
|
26
|
+
*/
|
|
27
|
+
class TestMetricReader extends MetricReader {
|
|
28
|
+
private _exporter = new InMemoryMetricExporter(
|
|
29
|
+
AggregationTemporality.CUMULATIVE,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
protected onShutdown(): Promise<void> {
|
|
33
|
+
throw new Error("Method not implemented.");
|
|
34
|
+
}
|
|
35
|
+
protected onForceFlush(): Promise<void> {
|
|
36
|
+
throw new Error("Method not implemented.");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async getMetricValue(
|
|
40
|
+
name: string,
|
|
41
|
+
attributes: { [key: string]: string | number } = {},
|
|
42
|
+
) {
|
|
43
|
+
await this.collectAndExport();
|
|
44
|
+
const metric = this._exporter
|
|
45
|
+
.getMetrics()[0]
|
|
46
|
+
?.scopeMetrics[0]?.metrics.find((m) => m.descriptor.name === name);
|
|
47
|
+
|
|
48
|
+
const dp = metric?.dataPoints.find(
|
|
49
|
+
(dp) => JSON.stringify(dp.attributes) === JSON.stringify(attributes),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
this._exporter.reset();
|
|
53
|
+
|
|
54
|
+
return dp?.value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async collectAndExport(): Promise<void> {
|
|
58
|
+
const result = await this.collect();
|
|
59
|
+
await new Promise<void>((resolve, reject) => {
|
|
60
|
+
this._exporter.export(result.resourceMetrics, (result) => {
|
|
61
|
+
if (result.error != null) {
|
|
62
|
+
reject(result.error);
|
|
63
|
+
} else {
|
|
64
|
+
resolve();
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
5
70
|
|
|
6
71
|
function setup() {
|
|
72
|
+
const metricReader = new TestMetricReader();
|
|
73
|
+
metrics.setGlobalMeterProvider(
|
|
74
|
+
new MeterProvider({
|
|
75
|
+
readers: [metricReader],
|
|
76
|
+
}),
|
|
77
|
+
);
|
|
78
|
+
|
|
7
79
|
const queue = new PriorityBasedMessageQueue(CO_VALUE_PRIORITY.MEDIUM);
|
|
8
|
-
return { queue };
|
|
80
|
+
return { queue, metricReader };
|
|
9
81
|
}
|
|
10
82
|
|
|
11
83
|
describe("PriorityBasedMessageQueue", () => {
|
|
84
|
+
afterEach(() => {
|
|
85
|
+
metrics.disable();
|
|
86
|
+
});
|
|
87
|
+
|
|
12
88
|
test("should initialize with correct properties", () => {
|
|
13
89
|
const { queue } = setup();
|
|
14
90
|
expect(queue["defaultPriority"]).toBe(CO_VALUE_PRIORITY.MEDIUM);
|
|
@@ -43,7 +119,7 @@ describe("PriorityBasedMessageQueue", () => {
|
|
|
43
119
|
});
|
|
44
120
|
|
|
45
121
|
test("should pull messages in priority order", async () => {
|
|
46
|
-
const { queue } = setup();
|
|
122
|
+
const { queue, metricReader } = setup();
|
|
47
123
|
const lowPriorityMsg: SyncMessage = {
|
|
48
124
|
action: "content",
|
|
49
125
|
id: "co_zlow",
|
|
@@ -64,12 +140,42 @@ describe("PriorityBasedMessageQueue", () => {
|
|
|
64
140
|
};
|
|
65
141
|
|
|
66
142
|
void queue.push(lowPriorityMsg);
|
|
143
|
+
expect(
|
|
144
|
+
await metricReader.getMetricValue("jazz.messagequeue.size", {
|
|
145
|
+
priority: lowPriorityMsg.priority,
|
|
146
|
+
}),
|
|
147
|
+
).toBe(1);
|
|
67
148
|
void queue.push(mediumPriorityMsg);
|
|
149
|
+
expect(
|
|
150
|
+
await metricReader.getMetricValue("jazz.messagequeue.size", {
|
|
151
|
+
priority: mediumPriorityMsg.priority,
|
|
152
|
+
}),
|
|
153
|
+
).toBe(1);
|
|
68
154
|
void queue.push(highPriorityMsg);
|
|
155
|
+
expect(
|
|
156
|
+
await metricReader.getMetricValue("jazz.messagequeue.size", {
|
|
157
|
+
priority: highPriorityMsg.priority,
|
|
158
|
+
}),
|
|
159
|
+
).toBe(1);
|
|
69
160
|
|
|
70
161
|
expect(queue.pull()?.msg).toEqual(highPriorityMsg);
|
|
162
|
+
expect(
|
|
163
|
+
await metricReader.getMetricValue("jazz.messagequeue.size", {
|
|
164
|
+
priority: highPriorityMsg.priority,
|
|
165
|
+
}),
|
|
166
|
+
).toBe(0);
|
|
71
167
|
expect(queue.pull()?.msg).toEqual(mediumPriorityMsg);
|
|
168
|
+
expect(
|
|
169
|
+
await metricReader.getMetricValue("jazz.messagequeue.size", {
|
|
170
|
+
priority: mediumPriorityMsg.priority,
|
|
171
|
+
}),
|
|
172
|
+
).toBe(0);
|
|
72
173
|
expect(queue.pull()?.msg).toEqual(lowPriorityMsg);
|
|
174
|
+
expect(
|
|
175
|
+
await metricReader.getMetricValue("jazz.messagequeue.size", {
|
|
176
|
+
priority: lowPriorityMsg.priority,
|
|
177
|
+
}),
|
|
178
|
+
).toBe(0);
|
|
73
179
|
});
|
|
74
180
|
|
|
75
181
|
test("should return undefined when pulling from empty queue", () => {
|
package/src/tests/coMap.test.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { expect, test } from "vitest";
|
|
2
2
|
import { expectMap } from "../coValue.js";
|
|
3
|
+
import { operationToEditEntry } from "../coValues/coMap.js";
|
|
3
4
|
import { WasmCrypto } from "../crypto/WasmCrypto.js";
|
|
4
5
|
import { LocalNode } from "../localNode.js";
|
|
5
6
|
import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfromSessionID.js";
|
|
6
|
-
import { randomAnonymousAccountAndSessionID } from "./testUtils.js";
|
|
7
|
+
import { hotSleep, randomAnonymousAccountAndSessionID } from "./testUtils.js";
|
|
7
8
|
|
|
8
9
|
const Crypto = await WasmCrypto.create();
|
|
9
10
|
|
|
@@ -62,26 +63,40 @@ test("Can get CoMap entry values at different points in time", () => {
|
|
|
62
63
|
|
|
63
64
|
expect(content.type).toEqual("comap");
|
|
64
65
|
|
|
65
|
-
const beforeA =
|
|
66
|
-
while (Date.now() < beforeA + 10) {
|
|
67
|
-
/* hot sleep */
|
|
68
|
-
}
|
|
66
|
+
const beforeA = hotSleep(10);
|
|
69
67
|
content.set("hello", "A", "trusting");
|
|
70
|
-
const beforeB =
|
|
71
|
-
while (Date.now() < beforeB + 10) {
|
|
72
|
-
/* hot sleep */
|
|
73
|
-
}
|
|
68
|
+
const beforeB = hotSleep(10);
|
|
74
69
|
content.set("hello", "B", "trusting");
|
|
75
|
-
const beforeC =
|
|
76
|
-
while (Date.now() < beforeC + 10) {
|
|
77
|
-
/* hot sleep */
|
|
78
|
-
}
|
|
70
|
+
const beforeC = hotSleep(10);
|
|
79
71
|
content.set("hello", "C", "trusting");
|
|
80
72
|
expect(content.get("hello")).toEqual("C");
|
|
81
73
|
expect(content.atTime(Date.now()).get("hello")).toEqual("C");
|
|
82
74
|
expect(content.atTime(beforeA).get("hello")).toEqual(undefined);
|
|
83
75
|
expect(content.atTime(beforeB).get("hello")).toEqual("A");
|
|
84
76
|
expect(content.atTime(beforeC).get("hello")).toEqual("B");
|
|
77
|
+
|
|
78
|
+
const ops = content.timeFilteredOps("hello");
|
|
79
|
+
|
|
80
|
+
expect(content.atTime(beforeC).lastEditAt("hello")).toEqual(
|
|
81
|
+
operationToEditEntry(ops![1]!),
|
|
82
|
+
);
|
|
83
|
+
expect(content.atTime(beforeC).nthEditAt("hello", 0)).toEqual(
|
|
84
|
+
operationToEditEntry(ops![0]!),
|
|
85
|
+
);
|
|
86
|
+
expect(content.atTime(beforeC).nthEditAt("hello", 2)).toEqual(undefined);
|
|
87
|
+
|
|
88
|
+
expect([...content.atTime(beforeC).editsAt("hello")]).toEqual([
|
|
89
|
+
operationToEditEntry(ops![0]!),
|
|
90
|
+
operationToEditEntry(ops![1]!),
|
|
91
|
+
]);
|
|
92
|
+
|
|
93
|
+
expect(content.atTime(beforeB).asObject()).toEqual({
|
|
94
|
+
hello: "A",
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(content.atTime(beforeC).asObject()).toEqual({
|
|
98
|
+
hello: "B",
|
|
99
|
+
});
|
|
85
100
|
});
|
|
86
101
|
|
|
87
102
|
test("Can get all historic values of key in CoMap", () => {
|
package/src/tests/group.test.ts
CHANGED
|
@@ -7,10 +7,8 @@ import { WasmCrypto } from "../crypto/WasmCrypto.js";
|
|
|
7
7
|
import { LocalNode } from "../localNode.js";
|
|
8
8
|
import {
|
|
9
9
|
createThreeConnectedNodes,
|
|
10
|
-
createTwoConnectedNodes,
|
|
11
10
|
loadCoValueOrFail,
|
|
12
11
|
randomAnonymousAccountAndSessionID,
|
|
13
|
-
waitFor,
|
|
14
12
|
} from "./testUtils.js";
|
|
15
13
|
|
|
16
14
|
const Crypto = await WasmCrypto.create();
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
createTwoConnectedNodes,
|
|
8
8
|
groupWithTwoAdmins,
|
|
9
9
|
groupWithTwoAdminsHighLevel,
|
|
10
|
+
hotSleep,
|
|
11
|
+
loadCoValueOrFail,
|
|
10
12
|
newGroup,
|
|
11
13
|
newGroupHighLevel,
|
|
12
14
|
} from "./testUtils.js";
|
|
@@ -1802,29 +1804,25 @@ test("Admins can set child extensions", () => {
|
|
|
1802
1804
|
});
|
|
1803
1805
|
|
|
1804
1806
|
test("Admins can set child extensions when the admin role is inherited", async () => {
|
|
1805
|
-
const { node1, node2 } = createTwoConnectedNodes("server", "server");
|
|
1807
|
+
const { node1, node2 } = await createTwoConnectedNodes("server", "server");
|
|
1806
1808
|
|
|
1807
|
-
const
|
|
1808
|
-
|
|
1809
|
+
const node2AccountOnNode1 = await loadCoValueOrFail(
|
|
1810
|
+
node1.node,
|
|
1811
|
+
node2.accountID,
|
|
1812
|
+
);
|
|
1809
1813
|
|
|
1810
|
-
group.
|
|
1814
|
+
const group = node1.node.createGroup();
|
|
1811
1815
|
|
|
1812
|
-
|
|
1816
|
+
group.addMember(node2AccountOnNode1, "admin");
|
|
1813
1817
|
|
|
1814
|
-
|
|
1815
|
-
throw new Error("Group not found on node2");
|
|
1816
|
-
}
|
|
1818
|
+
const groupOnNode2 = await loadCoValueOrFail(node2.node, group.id);
|
|
1817
1819
|
|
|
1818
|
-
const childGroup = node2.createGroup();
|
|
1820
|
+
const childGroup = node2.node.createGroup();
|
|
1819
1821
|
childGroup.extend(groupOnNode2);
|
|
1820
1822
|
|
|
1821
|
-
const childGroupOnNode1 = await node1.
|
|
1823
|
+
const childGroupOnNode1 = await loadCoValueOrFail(node1.node, childGroup.id);
|
|
1822
1824
|
|
|
1823
|
-
|
|
1824
|
-
throw new Error("Child group not found on node1");
|
|
1825
|
-
}
|
|
1826
|
-
|
|
1827
|
-
const grandChildGroup = node2.createGroup();
|
|
1825
|
+
const grandChildGroup = node2.node.createGroup();
|
|
1828
1826
|
grandChildGroup.extend(childGroupOnNode1);
|
|
1829
1827
|
|
|
1830
1828
|
expect(childGroupOnNode1.get(`child_${grandChildGroup.id}`)).toEqual(
|
|
@@ -2315,6 +2313,38 @@ test("When rotating the key of a parent group, the keys of all child groups are
|
|
|
2315
2313
|
expect(newChildReadKeyID).not.toEqual(currentChildReadKeyID);
|
|
2316
2314
|
});
|
|
2317
2315
|
|
|
2316
|
+
test("When rotating the key of a parent group, the old transactions should still be valid", async () => {
|
|
2317
|
+
const { node1, node2 } = await createTwoConnectedNodes("server", "server");
|
|
2318
|
+
|
|
2319
|
+
const group = node1.node.createGroup();
|
|
2320
|
+
const parentGroup = node1.node.createGroup();
|
|
2321
|
+
|
|
2322
|
+
group.extend(parentGroup);
|
|
2323
|
+
|
|
2324
|
+
const node2AccountOnNode1 = await loadCoValueOrFail(
|
|
2325
|
+
node1.node,
|
|
2326
|
+
node2.accountID,
|
|
2327
|
+
);
|
|
2328
|
+
|
|
2329
|
+
parentGroup.addMember(node2AccountOnNode1, "writer");
|
|
2330
|
+
|
|
2331
|
+
const map = group.createMap();
|
|
2332
|
+
map.set("from", "node1", "private");
|
|
2333
|
+
|
|
2334
|
+
const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
|
|
2335
|
+
mapOnNode2.set("from", "node2", "private");
|
|
2336
|
+
|
|
2337
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
2338
|
+
|
|
2339
|
+
parentGroup.removeMember(node2AccountOnNode1);
|
|
2340
|
+
|
|
2341
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
2342
|
+
|
|
2343
|
+
const mapOnNode1 = await loadCoValueOrFail(node1.node, map.id);
|
|
2344
|
+
|
|
2345
|
+
expect(mapOnNode1.get("from")).toEqual("node2");
|
|
2346
|
+
});
|
|
2347
|
+
|
|
2318
2348
|
test("When rotating the key of a grand-parent group, the keys of all child and grand-child groups are also rotated", () => {
|
|
2319
2349
|
const { group, node } = newGroupHighLevel();
|
|
2320
2350
|
const grandParentGroup = node.createGroup();
|