@dxos/echo-db 2.29.1 → 2.29.2-dev.f64f2a6f

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.
Files changed (84) hide show
  1. package/dist/src/api/database.d.ts +11 -7
  2. package/dist/src/api/database.d.ts.map +1 -1
  3. package/dist/src/api/database.js +20 -23
  4. package/dist/src/api/database.js.map +1 -1
  5. package/dist/src/api/database.test.js +13 -13
  6. package/dist/src/api/database.test.js.map +1 -1
  7. package/dist/src/api/item.d.ts.map +1 -1
  8. package/dist/src/api/item.js +1 -1
  9. package/dist/src/api/item.js.map +1 -1
  10. package/dist/src/api/result-set.d.ts.map +1 -1
  11. package/dist/src/api/result-set.js +1 -0
  12. package/dist/src/api/result-set.js.map +1 -1
  13. package/dist/src/api/selection/index.d.ts +5 -0
  14. package/dist/src/api/selection/index.d.ts.map +1 -0
  15. package/dist/src/api/selection/index.js +20 -0
  16. package/dist/src/api/selection/index.js.map +1 -0
  17. package/dist/src/api/selection/queries.d.ts +51 -0
  18. package/dist/src/api/selection/queries.d.ts.map +1 -0
  19. package/dist/src/api/selection/queries.js +70 -0
  20. package/dist/src/api/selection/queries.js.map +1 -0
  21. package/dist/src/api/selection/result.d.ts +50 -0
  22. package/dist/src/api/selection/result.d.ts.map +1 -0
  23. package/dist/src/api/selection/result.js +91 -0
  24. package/dist/src/api/selection/result.js.map +1 -0
  25. package/dist/src/api/selection/selection.d.ts +96 -0
  26. package/dist/src/api/selection/selection.d.ts.map +1 -0
  27. package/dist/src/api/selection/selection.js +164 -0
  28. package/dist/src/api/selection/selection.js.map +1 -0
  29. package/dist/src/api/{selection.test.d.ts → selection/selection.test.d.ts} +0 -0
  30. package/dist/src/api/selection/selection.test.d.ts.map +1 -0
  31. package/dist/src/api/{selection.test.js → selection/selection.test.js} +46 -44
  32. package/dist/src/api/selection/selection.test.js.map +1 -0
  33. package/dist/src/api/selection/util.d.ts +7 -0
  34. package/dist/src/api/selection/util.d.ts.map +1 -0
  35. package/dist/src/api/selection/util.js +25 -0
  36. package/dist/src/api/selection/util.js.map +1 -0
  37. package/dist/src/echo.test.js +20 -20
  38. package/dist/src/echo.test.js.map +1 -1
  39. package/dist/src/halo/contact-manager.js +2 -2
  40. package/dist/src/halo/contact-manager.js.map +1 -1
  41. package/dist/src/halo/preferences.js +6 -6
  42. package/dist/src/halo/preferences.js.map +1 -1
  43. package/dist/src/parties/party-core.test.js +3 -3
  44. package/dist/src/parties/party-core.test.js.map +1 -1
  45. package/dist/src/parties/party-factory.d.ts.map +1 -1
  46. package/dist/src/parties/party-factory.js +3 -3
  47. package/dist/src/parties/party-factory.js.map +1 -1
  48. package/dist/src/parties/party-internal.js +3 -3
  49. package/dist/src/parties/party-internal.js.map +1 -1
  50. package/dist/src/parties/party-manager.js +1 -1
  51. package/dist/src/parties/party-manager.js.map +1 -1
  52. package/dist/src/parties/party-manager.test.js +8 -10
  53. package/dist/src/parties/party-manager.test.js.map +1 -1
  54. package/dist/src/testing/testing-factories.d.ts +2 -2
  55. package/dist/src/testing/testing-factories.d.ts.map +1 -1
  56. package/dist/src/testing/testing-factories.js +1 -1
  57. package/dist/src/testing/testing-factories.js.map +1 -1
  58. package/dist/tsconfig.tsbuildinfo +1 -1
  59. package/package.json +27 -27
  60. package/src/api/database.test.ts +13 -13
  61. package/src/api/database.ts +28 -29
  62. package/src/api/item.ts +2 -2
  63. package/src/api/result-set.ts +1 -0
  64. package/src/api/selection/index.ts +8 -0
  65. package/src/api/selection/queries.ts +108 -0
  66. package/src/api/selection/result.ts +112 -0
  67. package/src/api/{selection.test.ts → selection/selection.test.ts} +50 -48
  68. package/src/api/{selection.ts → selection/selection.ts} +30 -231
  69. package/src/api/selection/util.ts +27 -0
  70. package/src/echo.test.ts +20 -20
  71. package/src/halo/contact-manager.ts +2 -2
  72. package/src/halo/preferences.ts +6 -6
  73. package/src/parties/party-core.test.ts +3 -3
  74. package/src/parties/party-factory.ts +3 -4
  75. package/src/parties/party-internal.ts +3 -3
  76. package/src/parties/party-manager.test.ts +8 -10
  77. package/src/parties/party-manager.ts +1 -1
  78. package/src/testing/testing-factories.ts +3 -3
  79. package/dist/src/api/selection.d.ts +0 -183
  80. package/dist/src/api/selection.d.ts.map +0 -1
  81. package/dist/src/api/selection.js +0 -308
  82. package/dist/src/api/selection.js.map +0 -1
  83. package/dist/src/api/selection.test.d.ts.map +0 -1
  84. package/dist/src/api/selection.test.js.map +0 -1
@@ -11,10 +11,11 @@ import { ItemID, ItemType } from '@dxos/echo-protocol';
11
11
  import { ModelFactory } from '@dxos/model-factory';
12
12
  import { ObjectModel } from '@dxos/object-model';
13
13
 
14
- import { Entity } from './entity';
15
- import { Item } from './item';
16
- import { Link } from './link';
17
- import { RootFilter, createSelector } from './selection';
14
+ import { Entity } from '../entity';
15
+ import { Item } from '../item';
16
+ import { Link } from '../link';
17
+ import { RootFilter } from './queries';
18
+ import { createSelection } from './selection';
18
19
 
19
20
  // Use to prevent ultra-long diffs.
20
21
  const ids = (entities: Entity[]) => entities.map(entity => entity.id);
@@ -23,8 +24,9 @@ const modelFactory = new ModelFactory().registerModel(ObjectModel);
23
24
 
24
25
  const createModel = (id: ItemID) => modelFactory.createModel(ObjectModel.meta.type, id, {}, PublicKey.random());
25
26
 
26
- const createItem = (id: ItemID, type: ItemType, parent?: Item<any>) =>
27
- new Item(null as any, id, type, createModel(id), undefined, parent);
27
+ const createItem = (id: ItemID, type: ItemType, parent?: Item<any>) => {
28
+ return new Item(null as any, id, type, createModel(id), undefined, parent);
29
+ };
28
30
 
29
31
  const createLink = (id: ItemID, type: ItemType, source: Item<any>, target: Item<any>) => {
30
32
  const link = new Link(null as any, id, type, createModel(id), {
@@ -40,11 +42,11 @@ const createLink = (id: ItemID, type: ItemType, source: Item<any>, target: Item<
40
42
  return link;
41
43
  };
42
44
 
43
- const createRootSelector = (filter?: RootFilter) =>
44
- createSelector<void>(() => items, () => new Event(), null as any, filter, undefined);
45
+ const createRootSelection = (filter?: RootFilter) =>
46
+ createSelection<void>(() => items, () => new Event(), null as any, filter, undefined);
45
47
 
46
48
  const createReducer = <R>(result: R) =>
47
- createSelector<R>(() => items, () => new Event(), null as any, undefined, result);
49
+ createSelection<R>(() => items, () => new Event(), null as any, undefined, result);
48
50
 
49
51
  // TODO(burdon): Use more complex data set (org, person, project, task).
50
52
 
@@ -90,34 +92,34 @@ describe('Selection', () => {
90
92
  describe('root', () => {
91
93
  test('all', () => {
92
94
  expect(
93
- createRootSelector()
94
- .query().entities
95
+ createRootSelection()
96
+ .exec().entities
95
97
  ).toHaveLength(items.length);
96
98
  });
97
99
 
98
100
  test('by id', () => {
99
101
  expect(
100
- createRootSelector({ id: org1.id })
101
- .query().entities
102
+ createRootSelection({ id: org1.id })
103
+ .exec().entities
102
104
  ).toEqual([org1]);
103
105
 
104
106
  expect(
105
- createRootSelector({ id: org2.id })
106
- .query().entities
107
+ createRootSelection({ id: org2.id })
108
+ .exec().entities
107
109
  ).toEqual([org2]);
108
110
  });
109
111
 
110
112
  test('single type', () => {
111
113
  expect(
112
- createRootSelector({ type: ITEM_PROJECT })
113
- .query().entities
114
+ createRootSelection({ type: ITEM_PROJECT })
115
+ .exec().entities
114
116
  ).toHaveLength(3);
115
117
  });
116
118
 
117
119
  test('multiple types', () => {
118
120
  expect(
119
- createRootSelector({ type: [ITEM_ORG, ITEM_PROJECT] })
120
- .query().entities
121
+ createRootSelection({ type: [ITEM_ORG, ITEM_PROJECT] })
122
+ .exec().entities
121
123
  ).toHaveLength(5);
122
124
  });
123
125
  });
@@ -125,33 +127,33 @@ describe('Selection', () => {
125
127
  describe('filter', () => {
126
128
  test('invalid', () => {
127
129
  expect(
128
- createRootSelector()
129
- .filter({ type: 'dxos:type.invalid' })
130
- .query().entities
130
+ createRootSelection()
131
+ .filter({ type: 'dxos:type/invalid' })
132
+ .exec().entities
131
133
  ).toHaveLength(0);
132
134
  });
133
135
 
134
136
  test('single type', () => {
135
137
  expect(
136
- createRootSelector()
138
+ createRootSelection()
137
139
  .filter({ type: ITEM_PROJECT })
138
- .query().entities
140
+ .exec().entities
139
141
  ).toHaveLength(3);
140
142
  });
141
143
 
142
144
  test('multiple types', () => {
143
145
  expect(
144
- createRootSelector()
146
+ createRootSelection()
145
147
  .filter({ type: [ITEM_ORG, ITEM_PROJECT] })
146
- .query().entities
148
+ .exec().entities
147
149
  ).toHaveLength(5);
148
150
  });
149
151
 
150
152
  test('by function', () => {
151
153
  expect(
152
- createRootSelector()
154
+ createRootSelection()
153
155
  .filter(item => item.type === ITEM_ORG)
154
- .query().entities
156
+ .exec().entities
155
157
  ).toHaveLength(2);
156
158
  });
157
159
  });
@@ -159,10 +161,10 @@ describe('Selection', () => {
159
161
  describe('children', () => {
160
162
  test('from multiple items', () => {
161
163
  expect(ids(
162
- createRootSelector()
164
+ createRootSelection()
163
165
  .filter({ type: ITEM_ORG })
164
166
  .children({ type: ITEM_PROJECT })
165
- .query().entities
167
+ .exec().entities
166
168
  )).toStrictEqual(ids([
167
169
  project1,
168
170
  project2,
@@ -172,9 +174,9 @@ describe('Selection', () => {
172
174
 
173
175
  test('from single item', () => {
174
176
  expect(ids(
175
- createRootSelector({ id: org1.id })
177
+ createRootSelection({ id: org1.id })
176
178
  .children()
177
- .query().entities
179
+ .exec().entities
178
180
  )).toStrictEqual(ids([
179
181
  project1,
180
182
  project2,
@@ -187,10 +189,10 @@ describe('Selection', () => {
187
189
  describe('parent', () => {
188
190
  test('from multiple items', () => {
189
191
  expect(ids(
190
- createRootSelector()
192
+ createRootSelection()
191
193
  .filter({ type: ITEM_PROJECT })
192
194
  .parent()
193
- .query().entities
195
+ .exec().entities
194
196
  )).toStrictEqual(ids([
195
197
  org1,
196
198
  org2
@@ -199,9 +201,9 @@ describe('Selection', () => {
199
201
 
200
202
  test('from single item', () => {
201
203
  expect(ids(
202
- createRootSelector({ id: project1.id })
204
+ createRootSelection({ id: project1.id })
203
205
  .parent()
204
- .query().entities
206
+ .exec().entities
205
207
  )).toStrictEqual(ids([
206
208
  org1
207
209
  ]));
@@ -209,9 +211,9 @@ describe('Selection', () => {
209
211
 
210
212
  test('is empty', () => {
211
213
  expect(
212
- createRootSelector({ id: org1.id })
214
+ createRootSelection({ id: org1.id })
213
215
  .parent()
214
- .query().entities
216
+ .exec().entities
215
217
  ).toEqual([]);
216
218
  });
217
219
  });
@@ -219,10 +221,10 @@ describe('Selection', () => {
219
221
  describe('links', () => {
220
222
  test('links from single item', () => {
221
223
  expect(ids(
222
- createRootSelector({ id: project1.id })
224
+ createRootSelection({ id: project1.id })
223
225
  .links()
224
226
  .target()
225
- .query().entities
227
+ .exec().entities
226
228
  )).toStrictEqual(ids([
227
229
  person1,
228
230
  person2
@@ -231,18 +233,18 @@ describe('Selection', () => {
231
233
 
232
234
  test('links from multiple items', () => {
233
235
  expect(
234
- createRootSelector({ type: ITEM_PROJECT })
236
+ createRootSelection({ type: ITEM_PROJECT })
235
237
  .links()
236
- .query().entities
238
+ .exec().entities
237
239
  ).toHaveLength(links.length);
238
240
  });
239
241
 
240
242
  test('sources', () => {
241
243
  expect(ids(
242
- createRootSelector({ type: ITEM_PERSON })
244
+ createRootSelection({ type: ITEM_PERSON })
243
245
  .refs()
244
246
  .source()
245
- .query().entities
247
+ .exec().entities
246
248
  )).toStrictEqual(ids([
247
249
  project1,
248
250
  project2
@@ -252,7 +254,7 @@ describe('Selection', () => {
252
254
 
253
255
  describe('reducer', () => {
254
256
  test('simple reducer', () => {
255
- const query = createReducer(0).call((items, count) => count + items.length).query();
257
+ const query = createReducer(0).call((items, count) => count + items.length).exec();
256
258
  expect(query.value).toEqual(items.length);
257
259
  });
258
260
 
@@ -272,7 +274,7 @@ describe('Selection', () => {
272
274
  return { ...rest, numLinks: numLinks + links.length, stage: 'c' };
273
275
  })
274
276
  .target()
275
- .query();
277
+ .exec();
276
278
 
277
279
  expect(query.value).toEqual({ numItems: 5, numLinks: 4, stage: 'c' });
278
280
  });
@@ -282,9 +284,9 @@ describe('Selection', () => {
282
284
  test('events get filtered correctly', async () => {
283
285
  const update = new Event<Entity[]>();
284
286
 
285
- const query = createSelector<void>(() => items, () => update, null as any, { type: ITEM_ORG }, undefined)
287
+ const query = createSelection<void>(() => items, () => update, null as any, { type: ITEM_ORG }, undefined)
286
288
  .children()
287
- .query();
289
+ .exec();
288
290
 
289
291
  {
290
292
  const promise = query.update.waitForCount(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
- export type ItemIdFilter = {
22
- id: ItemID
23
- }
24
-
25
- export type ItemFilter = {
26
- type?: OneOrMultiple<string>
27
- parent?: ItemID | Item
28
- }
29
-
30
- export type LinkFilter = {
31
- type?: OneOrMultiple<string>;
32
- }
33
-
34
- export type Predicate<T extends Entity> = (entity: T) => boolean;
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 createSelector = <R>(
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 createItemSelector = <R>(
60
+ export const createItemSelection = <R>(
118
61
  root: Item<any>,
119
62
  update: Event<Entity[]>,
120
63
  value: R
121
- ): Selection<Item<any>, R> => new Selection(() => [[root], value], update, root, value !== undefined);
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
- // TODO(burdon): Rename exec.
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
+ };