@dxos/echo-db 2.29.2-dev.8c2ad8e5 → 2.29.2-dev.f6ed60b8
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/src/api/database.d.ts +11 -7
- package/dist/src/api/database.d.ts.map +1 -1
- package/dist/src/api/database.js +22 -24
- package/dist/src/api/database.js.map +1 -1
- package/dist/src/api/database.test.js +13 -13
- package/dist/src/api/database.test.js.map +1 -1
- package/dist/src/api/item.d.ts.map +1 -1
- package/dist/src/api/item.js +1 -1
- package/dist/src/api/item.js.map +1 -1
- package/dist/src/api/result-set.d.ts.map +1 -1
- package/dist/src/api/result-set.js +1 -0
- package/dist/src/api/result-set.js.map +1 -1
- package/dist/src/api/selection/index.d.ts +5 -0
- package/dist/src/api/selection/index.d.ts.map +1 -0
- package/dist/src/api/selection/index.js +20 -0
- package/dist/src/api/selection/index.js.map +1 -0
- package/dist/src/api/selection/queries.d.ts +51 -0
- package/dist/src/api/selection/queries.d.ts.map +1 -0
- package/dist/src/api/selection/queries.js +70 -0
- package/dist/src/api/selection/queries.js.map +1 -0
- package/dist/src/api/selection/result.d.ts +50 -0
- package/dist/src/api/selection/result.d.ts.map +1 -0
- package/dist/src/api/selection/result.js +91 -0
- package/dist/src/api/selection/result.js.map +1 -0
- package/dist/src/api/selection/selection.d.ts +96 -0
- package/dist/src/api/selection/selection.d.ts.map +1 -0
- package/dist/src/api/selection/selection.js +164 -0
- package/dist/src/api/selection/selection.js.map +1 -0
- package/dist/src/api/{selection.test.d.ts → selection/selection.test.d.ts} +0 -0
- package/dist/src/api/selection/selection.test.d.ts.map +1 -0
- package/dist/src/api/{selection.test.js → selection/selection.test.js} +46 -44
- package/dist/src/api/selection/selection.test.js.map +1 -0
- package/dist/src/api/selection/util.d.ts +7 -0
- package/dist/src/api/selection/util.d.ts.map +1 -0
- package/dist/src/api/selection/util.js +25 -0
- package/dist/src/api/selection/util.js.map +1 -0
- package/dist/src/echo.test.js +20 -20
- package/dist/src/echo.test.js.map +1 -1
- package/dist/src/halo/contact-manager.js +2 -2
- package/dist/src/halo/contact-manager.js.map +1 -1
- package/dist/src/halo/halo.d.ts +1 -1
- package/dist/src/halo/halo.d.ts.map +1 -1
- package/dist/src/halo/halo.js +2 -2
- package/dist/src/halo/halo.js.map +1 -1
- package/dist/src/halo/preferences.js +6 -6
- package/dist/src/halo/preferences.js.map +1 -1
- package/dist/src/parties/party-core.test.js +3 -3
- package/dist/src/parties/party-core.test.js.map +1 -1
- package/dist/src/parties/party-factory.d.ts.map +1 -1
- package/dist/src/parties/party-factory.js +3 -3
- package/dist/src/parties/party-factory.js.map +1 -1
- package/dist/src/parties/party-internal.js +3 -3
- package/dist/src/parties/party-internal.js.map +1 -1
- package/dist/src/parties/party-manager.js +1 -1
- package/dist/src/parties/party-manager.js.map +1 -1
- package/dist/src/parties/party-manager.test.js +8 -10
- package/dist/src/parties/party-manager.test.js.map +1 -1
- package/dist/src/testing/testing-factories.d.ts +2 -2
- package/dist/src/testing/testing-factories.d.ts.map +1 -1
- package/dist/src/testing/testing-factories.js +1 -1
- package/dist/src/testing/testing-factories.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -17
- package/src/api/database.test.ts +13 -13
- package/src/api/database.ts +30 -30
- package/src/api/item.ts +2 -2
- package/src/api/result-set.ts +1 -0
- package/src/api/selection/index.ts +8 -0
- package/src/api/selection/queries.ts +108 -0
- package/src/api/selection/result.ts +112 -0
- package/src/api/{selection.test.ts → selection/selection.test.ts} +50 -48
- package/src/api/{selection.ts → selection/selection.ts} +30 -231
- package/src/api/selection/util.ts +27 -0
- package/src/echo.test.ts +20 -20
- package/src/halo/contact-manager.ts +2 -2
- package/src/halo/halo.ts +2 -3
- package/src/halo/preferences.ts +6 -6
- package/src/parties/party-core.test.ts +3 -3
- package/src/parties/party-factory.ts +3 -4
- package/src/parties/party-internal.ts +3 -3
- package/src/parties/party-manager.test.ts +8 -10
- package/src/parties/party-manager.ts +1 -1
- package/src/testing/testing-factories.ts +3 -3
- package/dist/src/api/selection.d.ts +0 -183
- package/dist/src/api/selection.d.ts.map +0 -1
- package/dist/src/api/selection.js +0 -308
- package/dist/src/api/selection.js.map +0 -1
- package/dist/src/api/selection.test.d.ts.map +0 -1
- package/dist/src/api/selection.test.js.map +0 -1
|
@@ -2,82 +2,23 @@
|
|
|
2
2
|
// Copyright 2020 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import assert from 'assert';
|
|
6
|
-
|
|
7
5
|
import { Event } from '@dxos/async';
|
|
8
|
-
import { ItemID } from '@dxos/echo-protocol';
|
|
9
|
-
|
|
10
|
-
import { Database } from './database';
|
|
11
|
-
import { Entity } from './entity';
|
|
12
|
-
import { Item } from './item';
|
|
13
|
-
import { Link } from './link';
|
|
14
|
-
|
|
15
|
-
export type OneOrMultiple<T> = T | T[]
|
|
16
|
-
|
|
17
|
-
//
|
|
18
|
-
// Filters
|
|
19
|
-
//
|
|
20
6
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
export type RootFilter = ItemIdFilter | ItemFilter | Predicate<Item>
|
|
37
|
-
|
|
38
|
-
export type RootSelector<R = void> = (filter?: RootFilter) => Selection<Item<any>, R>
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Controls how deleted items are filtered.
|
|
42
|
-
*/
|
|
43
|
-
export enum ItemFilterDeleted {
|
|
44
|
-
/**
|
|
45
|
-
* Do not return deleted items. Default behaviour.
|
|
46
|
-
*/
|
|
47
|
-
HIDE_DELETED = 0,
|
|
48
|
-
/**
|
|
49
|
-
* Return deleted and regular items.
|
|
50
|
-
*/
|
|
51
|
-
SHOW_DELETED = 1,
|
|
52
|
-
/**
|
|
53
|
-
* Return only deleted items.
|
|
54
|
-
*/
|
|
55
|
-
SHOW_DELETED_ONLY = 2
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export type QueryOptions = {
|
|
59
|
-
/**
|
|
60
|
-
* Controls how deleted items are filtered.
|
|
61
|
-
*/
|
|
62
|
-
deleted?: ItemFilterDeleted
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Represents where the selection has started.
|
|
67
|
-
*/
|
|
68
|
-
export type SelectionRoot = Database | Entity;
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Returned from each stage of the visitor.
|
|
72
|
-
*/
|
|
73
|
-
export type SelectionContext<T extends Entity, R> = [entities: T[], result?: R]
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Visitor callback.
|
|
77
|
-
* The visitor is passed the current entities and result (accumulator),
|
|
78
|
-
* which may be modified and returned.
|
|
79
|
-
*/
|
|
80
|
-
export type Callable<T extends Entity, R> = (entities: T[], result: R) => R
|
|
7
|
+
import { Entity } from '../entity';
|
|
8
|
+
import { Item } from '../item';
|
|
9
|
+
import { Link } from '../link';
|
|
10
|
+
import {
|
|
11
|
+
createQueryOptionsFilter,
|
|
12
|
+
filterToPredicate,
|
|
13
|
+
linkFilterToPredicate,
|
|
14
|
+
Callable,
|
|
15
|
+
ItemFilter,
|
|
16
|
+
LinkFilter,
|
|
17
|
+
Predicate,
|
|
18
|
+
QueryOptions,
|
|
19
|
+
RootFilter
|
|
20
|
+
} from './queries';
|
|
21
|
+
import { SelectionContext, SelectionResult, SelectionRoot } from './result';
|
|
81
22
|
|
|
82
23
|
/**
|
|
83
24
|
* Factory for selector that provides a root set of items.
|
|
@@ -87,7 +28,7 @@ export type Callable<T extends Entity, R> = (entities: T[], result: R) => R
|
|
|
87
28
|
* @param filter
|
|
88
29
|
* @param value Initial reducer value.
|
|
89
30
|
*/
|
|
90
|
-
export const
|
|
31
|
+
export const createSelection = <R>(
|
|
91
32
|
// Provider is called each time the query is executed.
|
|
92
33
|
itemsProvider: () => Item[],
|
|
93
34
|
// TODO(burdon): Replace with direct event handler.
|
|
@@ -97,6 +38,8 @@ export const createSelector = <R>(
|
|
|
97
38
|
value: R
|
|
98
39
|
): Selection<Item<any>, R> => {
|
|
99
40
|
const predicate = filter ? filterToPredicate(filter) : () => true;
|
|
41
|
+
|
|
42
|
+
// TODO(burdon): Option to filter out system types.
|
|
100
43
|
const visitor = (options: QueryOptions): SelectionContext<any, any> => {
|
|
101
44
|
const items = itemsProvider()
|
|
102
45
|
.filter(createQueryOptionsFilter(options))
|
|
@@ -114,11 +57,13 @@ export const createSelector = <R>(
|
|
|
114
57
|
* @param update
|
|
115
58
|
* @param value Initial reducer value.
|
|
116
59
|
*/
|
|
117
|
-
export const
|
|
60
|
+
export const createItemSelection = <R>(
|
|
118
61
|
root: Item<any>,
|
|
119
62
|
update: Event<Entity[]>,
|
|
120
63
|
value: R
|
|
121
|
-
): Selection<Item<any>, R> =>
|
|
64
|
+
): Selection<Item<any>, R> => {
|
|
65
|
+
return new Selection(() => [[root], value], update, root, value !== undefined);
|
|
66
|
+
};
|
|
122
67
|
|
|
123
68
|
/**
|
|
124
69
|
* Selections are used to construct database subscriptions.
|
|
@@ -155,7 +100,14 @@ export class Selection<T extends Entity<any>, R = void> {
|
|
|
155
100
|
/**
|
|
156
101
|
* Finish the selection and return the result.
|
|
157
102
|
*/
|
|
158
|
-
|
|
103
|
+
exec (options: QueryOptions = {}): SelectionResult<T, R> {
|
|
104
|
+
return this.query(options);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @deprecated
|
|
109
|
+
*/
|
|
110
|
+
// TODO(burdon): Remove.
|
|
159
111
|
query (options: QueryOptions = {}): SelectionResult<T, R> {
|
|
160
112
|
return new SelectionResult<T, R>(() => this._visitor(options), this._update, this._root, this._reducer);
|
|
161
113
|
}
|
|
@@ -254,156 +206,3 @@ export class Selection<T extends Entity<any>, R = void> {
|
|
|
254
206
|
]);
|
|
255
207
|
}
|
|
256
208
|
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Query subscription.
|
|
260
|
-
* Represents a live-query (subscription) that can notify about future updates to the relevant subset of items.
|
|
261
|
-
*/
|
|
262
|
-
export class SelectionResult<T extends Entity, R = any> {
|
|
263
|
-
/**
|
|
264
|
-
* Fired when there are updates in the selection.
|
|
265
|
-
* Only update that are relevant to the selection cause the update.
|
|
266
|
-
*/
|
|
267
|
-
readonly update = new Event<SelectionResult<T>>(); // TODO(burdon): Result result object.
|
|
268
|
-
|
|
269
|
-
private _lastResult: SelectionContext<T, R> = [[]];
|
|
270
|
-
|
|
271
|
-
constructor (
|
|
272
|
-
private readonly _execute: () => SelectionContext<T, R>,
|
|
273
|
-
private readonly _update: Event<Entity[]>,
|
|
274
|
-
private readonly _root: SelectionRoot,
|
|
275
|
-
private readonly _reducer: boolean
|
|
276
|
-
) {
|
|
277
|
-
this.refresh();
|
|
278
|
-
|
|
279
|
-
// Re-run if deps change.
|
|
280
|
-
this.update.addEffect(() => _update.on(currentEntities => {
|
|
281
|
-
const [previousEntities] = this._lastResult;
|
|
282
|
-
|
|
283
|
-
this.refresh();
|
|
284
|
-
|
|
285
|
-
// Filters mutation events only if selection (since we can't reason about deps of call methods).
|
|
286
|
-
const set = new Set([...previousEntities, ...this._lastResult![0]]);
|
|
287
|
-
if (this._reducer || currentEntities.some(entity => set.has(entity as any))) {
|
|
288
|
-
this.update.emit(this);
|
|
289
|
-
}
|
|
290
|
-
}));
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Re-run query.
|
|
295
|
-
*/
|
|
296
|
-
refresh () {
|
|
297
|
-
const [entities, result] = this._execute();
|
|
298
|
-
this._lastResult = [dedupe(entities), result];
|
|
299
|
-
return this;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* The root of the selection. Either a database or an item. Must be a stable reference.
|
|
304
|
-
*/
|
|
305
|
-
get root (): SelectionRoot {
|
|
306
|
-
return this._root;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* @deprecated
|
|
311
|
-
*/
|
|
312
|
-
// TODO(burdon): Remove.
|
|
313
|
-
// get result () {
|
|
314
|
-
// return this.entities;
|
|
315
|
-
// }
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Get the result of this selection.
|
|
319
|
-
*/
|
|
320
|
-
get entities (): T[] {
|
|
321
|
-
if (!this._lastResult) {
|
|
322
|
-
this.refresh();
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
const [entities] = this._lastResult!;
|
|
326
|
-
return entities;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Returns the selection or reducer result.
|
|
331
|
-
*/
|
|
332
|
-
get value (): R extends void ? T[] : R {
|
|
333
|
-
if (!this._lastResult) {
|
|
334
|
-
this.refresh();
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const [entities, value] = this._lastResult!;
|
|
338
|
-
return (this._reducer ? value : entities) as any;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* Return the first element if the set has exactly one element.
|
|
343
|
-
*/
|
|
344
|
-
expectOne (): T {
|
|
345
|
-
const entities = this.entities;
|
|
346
|
-
assert(entities.length === 1, `Expected one result; got ${entities.length}`);
|
|
347
|
-
return entities[0];
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
//
|
|
352
|
-
// Utils
|
|
353
|
-
//
|
|
354
|
-
|
|
355
|
-
const dedupe = <T>(values: T[]) => Array.from(new Set(values));
|
|
356
|
-
|
|
357
|
-
const coerceToId = (item: Item | ItemID): ItemID => {
|
|
358
|
-
if (typeof item === 'string') {
|
|
359
|
-
return item;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
return item.id;
|
|
363
|
-
};
|
|
364
|
-
|
|
365
|
-
const testOneOrMultiple = <T>(expected: OneOrMultiple<T>, value: T) => {
|
|
366
|
-
if (Array.isArray(expected)) {
|
|
367
|
-
return expected.includes(value);
|
|
368
|
-
} else {
|
|
369
|
-
return expected === value;
|
|
370
|
-
}
|
|
371
|
-
};
|
|
372
|
-
|
|
373
|
-
const filterToPredicate = (filter: ItemFilter | ItemIdFilter | Predicate<any>): Predicate<any> => {
|
|
374
|
-
if (typeof filter === 'function') {
|
|
375
|
-
return filter;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
return itemFilterToPredicate(filter);
|
|
379
|
-
};
|
|
380
|
-
|
|
381
|
-
const itemFilterToPredicate = (filter: ItemFilter | ItemIdFilter): Predicate<Item> => {
|
|
382
|
-
if ('id' in filter) {
|
|
383
|
-
return item => item.id === filter.id;
|
|
384
|
-
} else {
|
|
385
|
-
return item =>
|
|
386
|
-
(!filter.type || testOneOrMultiple(filter.type, item.type)) &&
|
|
387
|
-
(!filter.parent || item.parent?.id === coerceToId(filter.parent));
|
|
388
|
-
}
|
|
389
|
-
};
|
|
390
|
-
|
|
391
|
-
const linkFilterToPredicate = (filter: LinkFilter): Predicate<Link> =>
|
|
392
|
-
link => (!filter.type || testOneOrMultiple(filter.type, link.type));
|
|
393
|
-
|
|
394
|
-
const createQueryOptionsFilter = ({ deleted = ItemFilterDeleted.HIDE_DELETED }: QueryOptions): Predicate<Entity> => {
|
|
395
|
-
return entity => {
|
|
396
|
-
if (entity.model === null) {
|
|
397
|
-
return false;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
switch (deleted) {
|
|
401
|
-
case ItemFilterDeleted.HIDE_DELETED:
|
|
402
|
-
return !(entity instanceof Item) || !entity.deleted;
|
|
403
|
-
case ItemFilterDeleted.SHOW_DELETED:
|
|
404
|
-
return true;
|
|
405
|
-
case ItemFilterDeleted.SHOW_DELETED_ONLY:
|
|
406
|
-
return entity instanceof Item && entity.deleted;
|
|
407
|
-
}
|
|
408
|
-
};
|
|
409
|
-
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2020 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { ItemID } from '@dxos/echo-protocol';
|
|
6
|
+
|
|
7
|
+
import { Item } from '../item';
|
|
8
|
+
|
|
9
|
+
export type OneOrMultiple<T> = T | T[]
|
|
10
|
+
|
|
11
|
+
export const dedupe = <T>(values: T[]) => Array.from(new Set(values));
|
|
12
|
+
|
|
13
|
+
export const coerceToId = (item: Item | ItemID): ItemID => {
|
|
14
|
+
if (typeof item === 'string') {
|
|
15
|
+
return item;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return item.id;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const testOneOrMultiple = <T>(expected: OneOrMultiple<T>, value: T) => {
|
|
22
|
+
if (Array.isArray(expected)) {
|
|
23
|
+
return expected.includes(value);
|
|
24
|
+
} else {
|
|
25
|
+
return expected === value;
|
|
26
|
+
}
|
|
27
|
+
};
|
package/src/echo.test.ts
CHANGED
|
@@ -66,13 +66,13 @@ describe('ECHO', () => {
|
|
|
66
66
|
// TODO(burdon): Update currently called after all mutations below have completed?
|
|
67
67
|
expect(parties).toHaveLength(1);
|
|
68
68
|
parties.map(async party => {
|
|
69
|
-
const result1 = party.database.select().
|
|
69
|
+
const result1 = party.database.select().exec();
|
|
70
70
|
result1.entities.forEach(item => {
|
|
71
71
|
log('Item:', String(item));
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
// TODO(burdon): Check item mutations.
|
|
75
|
-
const result2 = party.database.select({ type: '
|
|
75
|
+
const result2 = party.database.select({ type: 'example:item/document' }).exec();
|
|
76
76
|
expect(result2.entities).toHaveLength(2);
|
|
77
77
|
onUpdate();
|
|
78
78
|
});
|
|
@@ -87,9 +87,9 @@ describe('ECHO', () => {
|
|
|
87
87
|
expect(members[0].displayName).toEqual(members[0].publicKey.humanize());
|
|
88
88
|
|
|
89
89
|
// TODO(burdon): Test item mutations.
|
|
90
|
-
await party.database.createItem({ model: ObjectModel, type: '
|
|
91
|
-
await party.database.createItem({ model: ObjectModel, type: '
|
|
92
|
-
await party.database.createItem({ model: ObjectModel, type: '
|
|
90
|
+
await party.database.createItem({ model: ObjectModel, type: 'example:item/document' });
|
|
91
|
+
await party.database.createItem({ model: ObjectModel, type: 'example:item/document' });
|
|
92
|
+
await party.database.createItem({ model: ObjectModel, type: 'example:item/kanban' });
|
|
93
93
|
|
|
94
94
|
await updated;
|
|
95
95
|
unsubscribe();
|
|
@@ -108,12 +108,12 @@ describe('ECHO', () => {
|
|
|
108
108
|
|
|
109
109
|
expect(parties).toHaveLength(1);
|
|
110
110
|
parties.map(async party => {
|
|
111
|
-
const items = party.database.select().
|
|
111
|
+
const items = party.database.select().exec().entities;
|
|
112
112
|
items.forEach(item => {
|
|
113
113
|
log('Item:', String(item));
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
-
const item = party.database.select({ type: '
|
|
116
|
+
const item = party.database.select({ type: 'example:item/document' }).exec().entities[0];
|
|
117
117
|
expect(item.children).toHaveLength(1);
|
|
118
118
|
expect(item.children[0].type).toBe(undefined);
|
|
119
119
|
// TODO(burdon): Test parent.
|
|
@@ -129,7 +129,7 @@ describe('ECHO', () => {
|
|
|
129
129
|
// Within this test, we use the humanized key as the name.
|
|
130
130
|
expect(members[0].displayName).toEqual(members[0].publicKey.humanize());
|
|
131
131
|
|
|
132
|
-
const parent = await party.database.createItem({ model: ObjectModel, type: '
|
|
132
|
+
const parent = await party.database.createItem({ model: ObjectModel, type: 'example:item/document' });
|
|
133
133
|
await party.database.createItem({ model: ObjectModel, parent: parent.id });
|
|
134
134
|
|
|
135
135
|
await updated;
|
|
@@ -151,12 +151,12 @@ describe('ECHO', () => {
|
|
|
151
151
|
// Within this test, we use the humanized key as the name.
|
|
152
152
|
expect(members[0].displayName).toEqual(members[0].publicKey.humanize());
|
|
153
153
|
|
|
154
|
-
const parentA = await party.database.createItem({ model: ObjectModel, type: '
|
|
154
|
+
const parentA = await party.database.createItem({ model: ObjectModel, type: 'example:item/document' });
|
|
155
155
|
const childA = await party.database.createItem({ model: ObjectModel, parent: parentA.id });
|
|
156
156
|
expect(parentA.children).toHaveLength(1);
|
|
157
157
|
expect(parentA.children[0].id).toEqual(childA.id);
|
|
158
158
|
|
|
159
|
-
const parentB = await party.database.createItem({ model: ObjectModel, type: '
|
|
159
|
+
const parentB = await party.database.createItem({ model: ObjectModel, type: 'example:item/document' });
|
|
160
160
|
const childB = await party.database.createItem({ model: ObjectModel, parent: parentB.id });
|
|
161
161
|
expect(parentB.children).toHaveLength(1);
|
|
162
162
|
expect(parentB.children[0].id).toEqual(childB.id);
|
|
@@ -199,7 +199,7 @@ describe('ECHO', () => {
|
|
|
199
199
|
assert(party);
|
|
200
200
|
log('Initialized party');
|
|
201
201
|
|
|
202
|
-
const items = party.database.select().
|
|
202
|
+
const items = party.database.select().exec().entities;
|
|
203
203
|
await waitForCondition(() => items.length > 0);
|
|
204
204
|
expect(items.length).toBeGreaterThan(0);
|
|
205
205
|
});
|
|
@@ -301,7 +301,7 @@ describe('ECHO', () => {
|
|
|
301
301
|
let itemA: Item<any> | null = null;
|
|
302
302
|
|
|
303
303
|
// Subscribe to Item updates on B.
|
|
304
|
-
const updated = partyB.database.select({ type: 'example:item/test' }).
|
|
304
|
+
const updated = partyB.database.select({ type: 'example:item/test' }).exec()
|
|
305
305
|
.update.waitFor(result => !!itemA && !!result.entities.find(item => item.id === itemA?.id));
|
|
306
306
|
|
|
307
307
|
// Create a new Item on A.
|
|
@@ -398,7 +398,7 @@ describe('ECHO', () => {
|
|
|
398
398
|
// Empty across the board.
|
|
399
399
|
for (const node of [a1, a2, b1, b2]) {
|
|
400
400
|
const [party] = node.queryParties().value;
|
|
401
|
-
expect(party.database.select({ type: 'example:item/test' }).
|
|
401
|
+
expect(party.database.select({ type: 'example:item/test' }).exec().entities.length).toBe(0);
|
|
402
402
|
}
|
|
403
403
|
|
|
404
404
|
for await (const node of [a1, a2, b1, b2]) {
|
|
@@ -409,7 +409,7 @@ describe('ECHO', () => {
|
|
|
409
409
|
for (const otherNode of [a1, a2, b1, b2].filter(x => x !== node)) {
|
|
410
410
|
const [otherParty] = otherNode.queryParties().value;
|
|
411
411
|
const [updated, onUpdate] = latch();
|
|
412
|
-
otherParty.database.select({ type: 'example:item/test' }).
|
|
412
|
+
otherParty.database.select({ type: 'example:item/test' }).exec()
|
|
413
413
|
.update.on(result => {
|
|
414
414
|
if (result.entities.find(current => current.id === item?.id)) {
|
|
415
415
|
log(`other has ${item?.id}`);
|
|
@@ -460,8 +460,8 @@ describe('ECHO', () => {
|
|
|
460
460
|
let itemA: Item<any> | null = null;
|
|
461
461
|
|
|
462
462
|
// Subscribe to Item updates on B.
|
|
463
|
-
const
|
|
464
|
-
|
|
463
|
+
const result = b.queryParties().value[0].database.select({ type: 'example:item/test' }).exec();
|
|
464
|
+
const updated = result.update.waitFor(items => !!itemA && !!items.entities.find(item => item.id === itemA?.id));
|
|
465
465
|
|
|
466
466
|
// Create a new Item on A.
|
|
467
467
|
itemA = await a.queryParties().value[0].database
|
|
@@ -592,7 +592,7 @@ describe('ECHO', () => {
|
|
|
592
592
|
let itemA: Item<any> | null = null;
|
|
593
593
|
const [updated, onUpdate] = latch();
|
|
594
594
|
|
|
595
|
-
partyA.database.select({ type: 'example:item/test' }).
|
|
595
|
+
partyA.database.select({ type: 'example:item/test' }).exec()
|
|
596
596
|
.update.on(result => {
|
|
597
597
|
if (result.entities.length) {
|
|
598
598
|
const [receivedItem] = result.entities;
|
|
@@ -605,7 +605,7 @@ describe('ECHO', () => {
|
|
|
605
605
|
itemA = await partyA.database.createItem({ model: ObjectModel, type: 'example:item/test' }) as Item<any>;
|
|
606
606
|
await updated; // Wait for update.
|
|
607
607
|
|
|
608
|
-
expect(partyA.database.select({ type: 'example:item/test' }).
|
|
608
|
+
expect(partyA.database.select({ type: 'example:item/test' }).exec().entities.length).toEqual(1);
|
|
609
609
|
|
|
610
610
|
await partyA.deactivate({ global: true });
|
|
611
611
|
await partyA.activate({ global: true });
|
|
@@ -615,9 +615,9 @@ describe('ECHO', () => {
|
|
|
615
615
|
|
|
616
616
|
await partyA.database
|
|
617
617
|
.select({ type: 'example:item/test' })
|
|
618
|
-
.
|
|
618
|
+
.exec()
|
|
619
619
|
.update.waitFor(result => result.entities.length > 0);
|
|
620
|
-
expect(partyA.database.select({ type: 'example:item/test' }).
|
|
620
|
+
expect(partyA.database.select({ type: 'example:item/test' }).exec().entities.length).toEqual(1);
|
|
621
621
|
}).timeout(10_000);
|
|
622
622
|
|
|
623
623
|
test('Deactivate Party - multi device', async () => {
|
|
@@ -22,12 +22,12 @@ export class ContactManager {
|
|
|
22
22
|
) {}
|
|
23
23
|
|
|
24
24
|
getContactListItem (): Item<ObjectModel> | undefined {
|
|
25
|
-
return this._party.database.select({ type: HALO_PARTY_CONTACT_LIST_TYPE }).
|
|
25
|
+
return this._party.database.select({ type: HALO_PARTY_CONTACT_LIST_TYPE }).exec().entities[0];
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
queryContacts (): ResultSet<Contact> {
|
|
29
29
|
const event = new Event();
|
|
30
|
-
const result = this._party.database.select({ type: HALO_PARTY_CONTACT_LIST_TYPE }).
|
|
30
|
+
const result = this._party.database.select({ type: HALO_PARTY_CONTACT_LIST_TYPE }).exec();
|
|
31
31
|
result.update.on(() => {
|
|
32
32
|
event.emit();
|
|
33
33
|
});
|
package/src/halo/halo.ts
CHANGED
|
@@ -246,7 +246,6 @@ export class HALO {
|
|
|
246
246
|
|
|
247
247
|
const keyPair = publicKey ? { publicKey: Buffer.from(publicKey), secretKey: Buffer.from(secretKey!) } : createKeyPair();
|
|
248
248
|
await this.createIdentity(keyPair);
|
|
249
|
-
|
|
250
249
|
await this.create(username);
|
|
251
250
|
|
|
252
251
|
const profile = this.getProfile();
|
|
@@ -270,7 +269,7 @@ export class HALO {
|
|
|
270
269
|
}
|
|
271
270
|
|
|
272
271
|
// TODO(burdon): Should be part of profile object. Or use standard Result object.
|
|
273
|
-
subscribeToProfile (
|
|
274
|
-
return this.identityReady.on(
|
|
272
|
+
subscribeToProfile (callback: () => void): () => void {
|
|
273
|
+
return this.identityReady.on(callback);
|
|
275
274
|
}
|
|
276
275
|
}
|
package/src/halo/preferences.ts
CHANGED
|
@@ -42,8 +42,8 @@ export class Preferences {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
subscribeToPreferences (callback: (preferences: any) => void) {
|
|
45
|
-
const globalResults = this._party.database.select({ type: HALO_PARTY_PREFERENCES_TYPE }).
|
|
46
|
-
const deviceResults = this._party.database.select({ type: HALO_PARTY_DEVICE_PREFERENCES_TYPE }).
|
|
45
|
+
const globalResults = this._party.database.select({ type: HALO_PARTY_PREFERENCES_TYPE }).exec();
|
|
46
|
+
const deviceResults = this._party.database.select({ type: HALO_PARTY_DEVICE_PREFERENCES_TYPE }).exec();
|
|
47
47
|
|
|
48
48
|
const event = new Event<any>();
|
|
49
49
|
|
|
@@ -77,7 +77,7 @@ export class Preferences {
|
|
|
77
77
|
if (!this._party.isOpen) {
|
|
78
78
|
return null;
|
|
79
79
|
}
|
|
80
|
-
const [globalItem] = this._party.database.select({ type: HALO_PARTY_PREFERENCES_TYPE }).
|
|
80
|
+
const [globalItem] = this._party.database.select({ type: HALO_PARTY_PREFERENCES_TYPE }).exec().entities;
|
|
81
81
|
return globalItem;
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -85,7 +85,7 @@ export class Preferences {
|
|
|
85
85
|
if (!this._party.isOpen) {
|
|
86
86
|
return null;
|
|
87
87
|
}
|
|
88
|
-
const deviceItems = this._party.database.select({ type: HALO_PARTY_DEVICE_PREFERENCES_TYPE }).
|
|
88
|
+
const deviceItems = this._party.database.select({ type: HALO_PARTY_DEVICE_PREFERENCES_TYPE }).exec().entities;
|
|
89
89
|
return deviceItems.find(item => this._deviceKey.equals(item.model.get('publicKey')));
|
|
90
90
|
}
|
|
91
91
|
|
|
@@ -133,7 +133,7 @@ export class Preferences {
|
|
|
133
133
|
const [partyDesc] = this._party.database
|
|
134
134
|
.select({ type: HALO_PARTY_DESCRIPTOR_TYPE })
|
|
135
135
|
.filter(partyMarker => joinedParty.partyKey.equals(partyMarker.model.get('publicKey')))
|
|
136
|
-
.
|
|
136
|
+
.exec().entities;
|
|
137
137
|
assert(!partyDesc, `Descriptor already exists for Party: ${joinedParty.partyKey.toHex()}`);
|
|
138
138
|
|
|
139
139
|
await this._party.database.createItem({
|
|
@@ -159,7 +159,7 @@ export class Preferences {
|
|
|
159
159
|
};
|
|
160
160
|
};
|
|
161
161
|
|
|
162
|
-
const result = this._party.database.select({ type: HALO_PARTY_DESCRIPTOR_TYPE }).
|
|
162
|
+
const result = this._party.database.select({ type: HALO_PARTY_DESCRIPTOR_TYPE }).exec();
|
|
163
163
|
|
|
164
164
|
// Wrap the query event so we can have manual control.
|
|
165
165
|
const event = new Event();
|
|
@@ -107,9 +107,9 @@ describe('PartyCore', () => {
|
|
|
107
107
|
await party.open();
|
|
108
108
|
|
|
109
109
|
{
|
|
110
|
-
await party.database.select().
|
|
111
|
-
const parent = party.database.select({ type: 'parent' }).
|
|
112
|
-
const child = party.database.select({ type: 'child' }).
|
|
110
|
+
await party.database.select().exec().update.waitFor(result => result.entities.length === 2);
|
|
111
|
+
const parent = party.database.select({ type: 'parent' }).exec().entities[0];
|
|
112
|
+
const child = party.database.select({ type: 'child' }).exec().entities[0];
|
|
113
113
|
|
|
114
114
|
expect(child.parent).toEqual(parent);
|
|
115
115
|
expect(parent.children).toContain(child);
|
|
@@ -51,10 +51,9 @@ export class PartyFactory {
|
|
|
51
51
|
@timed(5_000)
|
|
52
52
|
async createParty (): Promise<PartyInternal> {
|
|
53
53
|
const identity = this._identityProvider();
|
|
54
|
-
|
|
55
|
-
assert(
|
|
56
|
-
assert(
|
|
57
|
-
assert(identity.deviceKeyChain, 'Device KeyChain must exist');
|
|
54
|
+
assert(identity.identityGenesis, 'HALO not initialized.');
|
|
55
|
+
assert(identity.deviceKeyChain, 'Device KeyChain not initialized.');
|
|
56
|
+
assert(!this._options.readOnly, 'PartyFactory is read-only.');
|
|
58
57
|
|
|
59
58
|
const partyKey = await identity.keyring.createKeyRecord({ type: KeyType.PARTY });
|
|
60
59
|
const party = await this.constructParty(partyKey.publicKey);
|
|
@@ -186,7 +186,7 @@ export class PartyInternal {
|
|
|
186
186
|
await this._protocol.start();
|
|
187
187
|
|
|
188
188
|
// Issue an 'update' whenever the properties change.
|
|
189
|
-
this.database.select({ type: PARTY_ITEM_TYPE }).
|
|
189
|
+
this.database.select({ type: PARTY_ITEM_TYPE }).exec().update.on(() => this.update.emit());
|
|
190
190
|
|
|
191
191
|
this.update.emit();
|
|
192
192
|
return this;
|
|
@@ -246,7 +246,7 @@ export class PartyInternal {
|
|
|
246
246
|
assert(this.isOpen, 'Party not open.');
|
|
247
247
|
|
|
248
248
|
await this.database.waitForItem({ type: PARTY_ITEM_TYPE });
|
|
249
|
-
const items = this.database.select({ type: PARTY_ITEM_TYPE }).
|
|
249
|
+
const items = this.database.select({ type: PARTY_ITEM_TYPE }).exec().entities;
|
|
250
250
|
assert(items.length === 1, 'Party properties missing.');
|
|
251
251
|
return items[0] as Item<ObjectModel>;
|
|
252
252
|
}
|
|
@@ -256,7 +256,7 @@ export class PartyInternal {
|
|
|
256
256
|
*/
|
|
257
257
|
getPropertiesSet () {
|
|
258
258
|
assert(this.isOpen, 'Party not open.');
|
|
259
|
-
return this.database.select({ type: PARTY_ITEM_TYPE }).
|
|
259
|
+
return this.database.select({ type: PARTY_ITEM_TYPE }).exec();
|
|
260
260
|
}
|
|
261
261
|
|
|
262
262
|
/**
|
|
@@ -263,14 +263,12 @@ describe('Party manager', () => {
|
|
|
263
263
|
expect(partyB).toBeDefined();
|
|
264
264
|
|
|
265
265
|
const [updated, onUpdate] = latch();
|
|
266
|
-
partyB.database.select({ type: 'example:item/test' }).
|
|
266
|
+
partyB.database.select({ type: 'example:item/test' }).exec()
|
|
267
267
|
.update.on(result => {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
onUpdate();
|
|
273
|
-
}
|
|
268
|
+
const [itemB] = result.entities;
|
|
269
|
+
if (itemA && itemA.id === itemB.id) {
|
|
270
|
+
log(`B has ${itemB.id}`);
|
|
271
|
+
onUpdate();
|
|
274
272
|
}
|
|
275
273
|
});
|
|
276
274
|
|
|
@@ -318,7 +316,7 @@ describe('Party manager', () => {
|
|
|
318
316
|
|
|
319
317
|
// Subscribe to Item updates on B.
|
|
320
318
|
const [updated, onUpdate] = latch();
|
|
321
|
-
partyB.database.select({ type: 'example:item/test' }).
|
|
319
|
+
partyB.database.select({ type: 'example:item/test' }).exec()
|
|
322
320
|
.update.on(result => {
|
|
323
321
|
if (result.entities.length) {
|
|
324
322
|
const [itemB] = result.entities;
|
|
@@ -399,7 +397,7 @@ describe('Party manager', () => {
|
|
|
399
397
|
const [updated, onUpdate] = latch();
|
|
400
398
|
|
|
401
399
|
// Subscribe to Item updates on B.
|
|
402
|
-
partyB.database.select({ type: 'example:item/test' }).
|
|
400
|
+
partyB.database.select({ type: 'example:item/test' }).exec()
|
|
403
401
|
.update.on(result => {
|
|
404
402
|
if (result.entities.length) {
|
|
405
403
|
const [itemB] = result.entities;
|
|
@@ -458,7 +456,7 @@ describe('Party manager', () => {
|
|
|
458
456
|
const [updated, onUpdate] = latch();
|
|
459
457
|
|
|
460
458
|
// Subscribe to Item updates on B.
|
|
461
|
-
partyB.database.select({ type: 'example:item/test' }).
|
|
459
|
+
partyB.database.select({ type: 'example:item/test' }).exec()
|
|
462
460
|
.update.on(result => {
|
|
463
461
|
if (result.entities.length) {
|
|
464
462
|
const [itemB] = result.entities;
|
|
@@ -244,7 +244,7 @@ export class PartyManager {
|
|
|
244
244
|
const attachUpdateListeners = () => {
|
|
245
245
|
const debouncedContacts = party.processor.keyOrInfoAdded.debounce(CONTACT_DEBOUNCE_INTERVAL).discardParameter();
|
|
246
246
|
debouncedContacts.on(updateContact);
|
|
247
|
-
party.database.select({ type: PARTY_ITEM_TYPE }).
|
|
247
|
+
party.database.select({ type: PARTY_ITEM_TYPE }).exec().update.on(updateTitle);
|
|
248
248
|
};
|
|
249
249
|
|
|
250
250
|
if (party.isOpen) {
|