@dxos/echo-db 2.28.3-dev.e4579238 → 2.28.4-dev.424a2abb

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 (167) hide show
  1. package/dist/src/{database → api}/database.d.ts +2 -4
  2. package/dist/src/api/database.d.ts.map +1 -0
  3. package/dist/src/{database → api}/database.js +6 -9
  4. package/dist/src/api/database.js.map +1 -0
  5. package/dist/src/{database → api}/database.test.d.ts +0 -0
  6. package/dist/src/api/database.test.d.ts.map +1 -0
  7. package/dist/src/{database → api}/database.test.js +17 -19
  8. package/dist/src/api/database.test.js.map +1 -0
  9. package/dist/src/{database → api}/entity.d.ts +1 -1
  10. package/dist/src/api/entity.d.ts.map +1 -0
  11. package/dist/src/{database → api}/entity.js +0 -0
  12. package/dist/src/api/entity.js.map +1 -0
  13. package/dist/src/api/index.d.ts +7 -0
  14. package/dist/src/api/index.d.ts.map +1 -0
  15. package/dist/src/api/index.js +22 -0
  16. package/dist/src/api/index.js.map +1 -0
  17. package/dist/src/{database → api}/item.d.ts +1 -1
  18. package/dist/src/api/item.d.ts.map +1 -0
  19. package/dist/src/{database → api}/item.js +0 -0
  20. package/dist/src/api/item.js.map +1 -0
  21. package/dist/src/{database → api}/link.d.ts +1 -1
  22. package/dist/src/api/link.d.ts.map +1 -0
  23. package/dist/src/{database → api}/link.js +0 -0
  24. package/dist/src/api/link.js.map +1 -0
  25. package/dist/src/{result.d.ts → api/result-set.d.ts} +1 -1
  26. package/dist/src/api/result-set.d.ts.map +1 -0
  27. package/dist/src/{result.js → api/result-set.js} +1 -1
  28. package/dist/src/api/result-set.js.map +1 -0
  29. package/dist/src/{database → api}/selection.d.ts +25 -11
  30. package/dist/src/api/selection.d.ts.map +1 -0
  31. package/dist/src/{database → api}/selection.js +64 -38
  32. package/dist/src/api/selection.js.map +1 -0
  33. package/dist/src/{database → api}/selection.test.d.ts +0 -0
  34. package/dist/src/api/selection.test.d.ts.map +1 -0
  35. package/dist/src/{database → api}/selection.test.js +18 -19
  36. package/dist/src/api/selection.test.js.map +1 -0
  37. package/dist/src/database/data-mirror.test.js.map +1 -1
  38. package/dist/src/database/data-service-host.d.ts.map +1 -1
  39. package/dist/src/database/data-service-host.js +3 -4
  40. package/dist/src/database/data-service-host.js.map +1 -1
  41. package/dist/src/database/index.d.ts +4 -6
  42. package/dist/src/database/index.d.ts.map +1 -1
  43. package/dist/src/database/index.js +4 -6
  44. package/dist/src/database/index.js.map +1 -1
  45. package/dist/src/database/item-demuxer.d.ts +1 -1
  46. package/dist/src/database/item-demuxer.d.ts.map +1 -1
  47. package/dist/src/database/item-demuxer.js +3 -3
  48. package/dist/src/database/item-demuxer.js.map +1 -1
  49. package/dist/src/database/item-demuxer.test.js +2 -2
  50. package/dist/src/database/item-demuxer.test.js.map +1 -1
  51. package/dist/src/database/item-manager.d.ts +1 -3
  52. package/dist/src/database/item-manager.d.ts.map +1 -1
  53. package/dist/src/database/item-manager.js +10 -11
  54. package/dist/src/database/item-manager.js.map +1 -1
  55. package/dist/src/database/testing.d.ts +1 -1
  56. package/dist/src/database/testing.d.ts.map +1 -1
  57. package/dist/src/database/testing.js +3 -3
  58. package/dist/src/database/testing.js.map +1 -1
  59. package/dist/src/echo.d.ts +1 -1
  60. package/dist/src/echo.d.ts.map +1 -1
  61. package/dist/src/echo.js +4 -4
  62. package/dist/src/echo.js.map +1 -1
  63. package/dist/src/echo.test.js +25 -28
  64. package/dist/src/echo.test.js.map +1 -1
  65. package/dist/src/halo/contact-manager.d.ts +1 -2
  66. package/dist/src/halo/contact-manager.d.ts.map +1 -1
  67. package/dist/src/halo/contact-manager.js +6 -6
  68. package/dist/src/halo/contact-manager.js.map +1 -1
  69. package/dist/src/halo/halo.d.ts +1 -1
  70. package/dist/src/halo/halo.d.ts.map +1 -1
  71. package/dist/src/halo/preferences.d.ts +1 -1
  72. package/dist/src/halo/preferences.d.ts.map +1 -1
  73. package/dist/src/halo/preferences.js +9 -9
  74. package/dist/src/halo/preferences.js.map +1 -1
  75. package/dist/src/index.d.ts +2 -1
  76. package/dist/src/index.d.ts.map +1 -1
  77. package/dist/src/index.js +2 -1
  78. package/dist/src/index.js.map +1 -1
  79. package/dist/src/parties/party-core.d.ts +1 -1
  80. package/dist/src/parties/party-core.d.ts.map +1 -1
  81. package/dist/src/parties/party-core.js +2 -1
  82. package/dist/src/parties/party-core.js.map +1 -1
  83. package/dist/src/parties/party-core.test.js +3 -3
  84. package/dist/src/parties/party-core.test.js.map +1 -1
  85. package/dist/src/parties/party-internal.d.ts +2 -3
  86. package/dist/src/parties/party-internal.d.ts.map +1 -1
  87. package/dist/src/parties/party-internal.js +4 -5
  88. package/dist/src/parties/party-internal.js.map +1 -1
  89. package/dist/src/parties/party-manager.test.js +19 -18
  90. package/dist/src/parties/party-manager.test.js.map +1 -1
  91. package/dist/src/snapshots/snapshot.test.js +3 -3
  92. package/dist/src/snapshots/snapshot.test.js.map +1 -1
  93. package/dist/src/testing/index.d.ts +3 -0
  94. package/dist/src/testing/index.d.ts.map +1 -0
  95. package/dist/src/testing/index.js +18 -0
  96. package/dist/src/testing/index.js.map +1 -0
  97. package/dist/src/{util → testing}/testing-factories.d.ts +1 -1
  98. package/dist/src/testing/testing-factories.d.ts.map +1 -0
  99. package/dist/src/{util → testing}/testing-factories.js +0 -0
  100. package/dist/src/testing/testing-factories.js.map +1 -0
  101. package/dist/src/{util → testing}/testing.d.ts +1 -1
  102. package/dist/src/testing/testing.d.ts.map +1 -0
  103. package/dist/src/{util → testing}/testing.js +2 -2
  104. package/dist/src/testing/testing.js.map +1 -0
  105. package/dist/src/util/index.d.ts +0 -2
  106. package/dist/src/util/index.d.ts.map +1 -1
  107. package/dist/src/util/index.js +0 -2
  108. package/dist/src/util/index.js.map +1 -1
  109. package/dist/tsconfig.tsbuildinfo +1 -1
  110. package/package.json +25 -22
  111. package/src/{database → api}/database.test.ts +15 -18
  112. package/src/{database → api}/database.ts +7 -14
  113. package/src/{database → api}/entity.ts +1 -1
  114. package/src/api/index.ts +10 -0
  115. package/src/{database → api}/item.ts +1 -1
  116. package/src/{database → api}/link.ts +1 -1
  117. package/src/{result.ts → api/result-set.ts} +0 -0
  118. package/src/{database → api}/selection.test.ts +24 -23
  119. package/src/{database → api}/selection.ts +72 -43
  120. package/src/database/data-mirror.test.ts +1 -1
  121. package/src/database/data-mirror.ts +1 -1
  122. package/src/database/data-service-host.ts +1 -2
  123. package/src/database/index.ts +4 -6
  124. package/src/database/item-demuxer.test.ts +1 -1
  125. package/src/database/item-demuxer.ts +3 -6
  126. package/src/database/item-manager.ts +1 -3
  127. package/src/database/testing.ts +1 -1
  128. package/src/echo.test.ts +28 -29
  129. package/src/echo.ts +2 -2
  130. package/src/halo/contact-manager.ts +5 -6
  131. package/src/halo/halo.ts +1 -1
  132. package/src/halo/preferences.ts +12 -11
  133. package/src/index.ts +2 -1
  134. package/src/parties/party-core.test.ts +3 -3
  135. package/src/parties/party-core.ts +2 -1
  136. package/src/parties/party-internal.ts +3 -4
  137. package/src/parties/party-manager.test.ts +19 -18
  138. package/src/snapshots/snapshot.test.ts +1 -1
  139. package/src/testing/index.ts +6 -0
  140. package/src/{util → testing}/testing-factories.ts +6 -2
  141. package/src/{util → testing}/testing.ts +2 -2
  142. package/src/util/index.ts +0 -2
  143. package/dist/src/conflict.test.d.ts +0 -2
  144. package/dist/src/conflict.test.d.ts.map +0 -1
  145. package/dist/src/conflict.test.js +0 -33
  146. package/dist/src/conflict.test.js.map +0 -1
  147. package/dist/src/database/database.d.ts.map +0 -1
  148. package/dist/src/database/database.js.map +0 -1
  149. package/dist/src/database/database.test.d.ts.map +0 -1
  150. package/dist/src/database/database.test.js.map +0 -1
  151. package/dist/src/database/entity.d.ts.map +0 -1
  152. package/dist/src/database/entity.js.map +0 -1
  153. package/dist/src/database/item.d.ts.map +0 -1
  154. package/dist/src/database/item.js.map +0 -1
  155. package/dist/src/database/link.d.ts.map +0 -1
  156. package/dist/src/database/link.js.map +0 -1
  157. package/dist/src/database/selection.d.ts.map +0 -1
  158. package/dist/src/database/selection.js.map +0 -1
  159. package/dist/src/database/selection.test.d.ts.map +0 -1
  160. package/dist/src/database/selection.test.js.map +0 -1
  161. package/dist/src/result.d.ts.map +0 -1
  162. package/dist/src/result.js.map +0 -1
  163. package/dist/src/util/testing-factories.d.ts.map +0 -1
  164. package/dist/src/util/testing-factories.js.map +0 -1
  165. package/dist/src/util/testing.d.ts.map +0 -1
  166. package/dist/src/util/testing.js.map +0 -1
  167. package/src/conflict.test.ts +0 -37
@@ -10,14 +10,11 @@ import { ItemID, ItemType } from '@dxos/echo-protocol';
10
10
  import { Model, ModelConstructor, ModelFactory, validateModelClass } from '@dxos/model-factory';
11
11
  import { ObjectModel } from '@dxos/object-model';
12
12
 
13
- import { Selection } from '.';
14
- import { DataServiceHost } from './data-service-host';
15
- import { DatabaseBackend } from './database-backend';
13
+ import { DatabaseBackend, DataServiceHost, ItemManager } from '../database';
16
14
  import { Entity } from './entity';
17
15
  import { Item } from './item';
18
- import { ItemManager } from './item-manager';
19
16
  import { Link } from './link';
20
- import { createSelector, RootFilter } from './selection';
17
+ import { Selection, createSelector, RootFilter } from './selection';
21
18
 
22
19
  export interface ItemCreationOptions<M extends Model> {
23
20
  model: ModelConstructor<M>
@@ -161,14 +158,10 @@ export class Database {
161
158
  * Waits for item matching the filter to be present and returns it.
162
159
  */
163
160
  async waitForItem<T extends Model<any>> (filter: RootFilter): Promise<Item<T>> {
164
- const query = this.select(filter).query();
165
- await query.update.waitForCondition(() => {
166
- const { result } = query;
167
- return Array.isArray(result) ? result.length > 0 : result !== undefined;
168
- });
169
-
170
- const item = Array.isArray(query.result) ? query.result[0] : query.result;
171
- assert(item, 'Race condition detected');
161
+ const result = this.select(filter).query();
162
+ await result.update.waitForCondition(() => result.entities.length > 0);
163
+ const item = result.expectOne();
164
+ assert(item, 'Possible condition detected.');
172
165
  return item as Item<T>;
173
166
  }
174
167
 
@@ -192,7 +185,7 @@ export class Database {
192
185
  * @param filter
193
186
  */
194
187
  reduce<R> (result: R, filter?: RootFilter): Selection<Item<any>, R> {
195
- return createSelector(
188
+ return createSelector<R>(
196
189
  () => this._itemManager.items,
197
190
  () => this._itemManager.debouncedUpdate,
198
191
  this,
@@ -7,7 +7,7 @@ import { ItemID, ItemType } from '@dxos/echo-protocol';
7
7
  import { Model, ModelMeta, StateManager } from '@dxos/model-factory';
8
8
  import { SubscriptionGroup } from '@dxos/util';
9
9
 
10
- import { ItemManager } from './item-manager';
10
+ import { ItemManager } from '../database';
11
11
 
12
12
  /**
13
13
  * Base class for all ECHO entitities.
@@ -0,0 +1,10 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ export * from './database';
6
+ export * from './entity';
7
+ export * from './item';
8
+ export * from './link';
9
+ export * from './result-set';
10
+ export * from './selection';
@@ -7,8 +7,8 @@ import debug from 'debug';
7
7
  import { EchoEnvelope, ItemID, ItemMutation, ItemType, FeedWriter } from '@dxos/echo-protocol';
8
8
  import { Model, StateManager } from '@dxos/model-factory';
9
9
 
10
+ import { ItemManager } from '../database';
10
11
  import { Entity } from './entity';
11
- import { ItemManager } from './item-manager';
12
12
  import type { Link } from './link';
13
13
  import { Selection, createItemSelector } from './selection';
14
14
 
@@ -7,9 +7,9 @@ import assert from 'assert';
7
7
  import { ItemID, ItemType } from '@dxos/echo-protocol';
8
8
  import { Model, StateManager } from '@dxos/model-factory';
9
9
 
10
+ import { ItemManager } from '../database';
10
11
  import { Entity } from './entity';
11
12
  import { Item } from './item';
12
- import { ItemManager } from './item-manager';
13
13
 
14
14
  export interface LinkData {
15
15
  sourceId: ItemID
File without changes
@@ -11,10 +11,10 @@ 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, RootFilter } from '.';
14
+ import { Entity } from './entity';
15
15
  import { Item } from './item';
16
16
  import { Link } from './link';
17
- import { createSelector } from './selection';
17
+ import { RootFilter, createSelector } from './selection';
18
18
 
19
19
  // Use to prevent ultra-long diffs.
20
20
  const ids = (entities: Entity[]) => entities.map(entity => entity.id);
@@ -40,9 +40,11 @@ const createLink = (id: ItemID, type: ItemType, source: Item<any>, target: Item<
40
40
  return link;
41
41
  };
42
42
 
43
- const createRootSelector = (filter?: RootFilter) => createSelector<void>(() => items, () => new Event(), null as any, filter, undefined);
43
+ const createRootSelector = (filter?: RootFilter) =>
44
+ createSelector<void>(() => items, () => new Event(), null as any, filter, undefined);
44
45
 
45
- const createReducer = <R>(result: R) => createSelector<R>(() => items, () => new Event(), null as any, undefined, result);
46
+ const createReducer = <R>(result: R) =>
47
+ createSelector<R>(() => items, () => new Event(), null as any, undefined, result);
46
48
 
47
49
  // TODO(burdon): Use more complex data set (org, person, project, task).
48
50
 
@@ -89,33 +91,33 @@ describe('Selection', () => {
89
91
  test('all', () => {
90
92
  expect(
91
93
  createRootSelector()
92
- .query().result
94
+ .query().entities
93
95
  ).toHaveLength(items.length);
94
96
  });
95
97
 
96
98
  test('by id', () => {
97
99
  expect(
98
100
  createRootSelector({ id: org1.id })
99
- .query().result
101
+ .query().entities
100
102
  ).toEqual([org1]);
101
103
 
102
104
  expect(
103
105
  createRootSelector({ id: org2.id })
104
- .query().result
106
+ .query().entities
105
107
  ).toEqual([org2]);
106
108
  });
107
109
 
108
110
  test('single type', () => {
109
111
  expect(
110
112
  createRootSelector({ type: ITEM_PROJECT })
111
- .query().result
113
+ .query().entities
112
114
  ).toHaveLength(3);
113
115
  });
114
116
 
115
117
  test('multiple types', () => {
116
118
  expect(
117
119
  createRootSelector({ type: [ITEM_ORG, ITEM_PROJECT] })
118
- .query().result
120
+ .query().entities
119
121
  ).toHaveLength(5);
120
122
  });
121
123
  });
@@ -125,7 +127,7 @@ describe('Selection', () => {
125
127
  expect(
126
128
  createRootSelector()
127
129
  .filter({ type: 'dxos:type.invalid' })
128
- .query().result
130
+ .query().entities
129
131
  ).toHaveLength(0);
130
132
  });
131
133
 
@@ -133,7 +135,7 @@ describe('Selection', () => {
133
135
  expect(
134
136
  createRootSelector()
135
137
  .filter({ type: ITEM_PROJECT })
136
- .query().result
138
+ .query().entities
137
139
  ).toHaveLength(3);
138
140
  });
139
141
 
@@ -141,7 +143,7 @@ describe('Selection', () => {
141
143
  expect(
142
144
  createRootSelector()
143
145
  .filter({ type: [ITEM_ORG, ITEM_PROJECT] })
144
- .query().result
146
+ .query().entities
145
147
  ).toHaveLength(5);
146
148
  });
147
149
 
@@ -149,7 +151,7 @@ describe('Selection', () => {
149
151
  expect(
150
152
  createRootSelector()
151
153
  .filter(item => item.type === ITEM_ORG)
152
- .query().result
154
+ .query().entities
153
155
  ).toHaveLength(2);
154
156
  });
155
157
  });
@@ -160,7 +162,7 @@ describe('Selection', () => {
160
162
  createRootSelector()
161
163
  .filter({ type: ITEM_ORG })
162
164
  .children({ type: ITEM_PROJECT })
163
- .query().result
165
+ .query().entities
164
166
  )).toStrictEqual(ids([
165
167
  project1,
166
168
  project2,
@@ -172,7 +174,7 @@ describe('Selection', () => {
172
174
  expect(ids(
173
175
  createRootSelector({ id: org1.id })
174
176
  .children()
175
- .query().result
177
+ .query().entities
176
178
  )).toStrictEqual(ids([
177
179
  project1,
178
180
  project2,
@@ -188,7 +190,7 @@ describe('Selection', () => {
188
190
  createRootSelector()
189
191
  .filter({ type: ITEM_PROJECT })
190
192
  .parent()
191
- .query().result
193
+ .query().entities
192
194
  )).toStrictEqual(ids([
193
195
  org1,
194
196
  org2
@@ -199,7 +201,7 @@ describe('Selection', () => {
199
201
  expect(ids(
200
202
  createRootSelector({ id: project1.id })
201
203
  .parent()
202
- .query().result
204
+ .query().entities
203
205
  )).toStrictEqual(ids([
204
206
  org1
205
207
  ]));
@@ -209,7 +211,7 @@ describe('Selection', () => {
209
211
  expect(
210
212
  createRootSelector({ id: org1.id })
211
213
  .parent()
212
- .query().result
214
+ .query().entities
213
215
  ).toEqual([]);
214
216
  });
215
217
  });
@@ -220,7 +222,7 @@ describe('Selection', () => {
220
222
  createRootSelector({ id: project1.id })
221
223
  .links()
222
224
  .target()
223
- .query().result
225
+ .query().entities
224
226
  )).toStrictEqual(ids([
225
227
  person1,
226
228
  person2
@@ -231,7 +233,7 @@ describe('Selection', () => {
231
233
  expect(
232
234
  createRootSelector({ type: ITEM_PROJECT })
233
235
  .links()
234
- .query().result
236
+ .query().entities
235
237
  ).toHaveLength(links.length);
236
238
  });
237
239
 
@@ -240,7 +242,7 @@ describe('Selection', () => {
240
242
  createRootSelector({ type: ITEM_PERSON })
241
243
  .refs()
242
244
  .source()
243
- .query().result
245
+ .query().entities
244
246
  )).toStrictEqual(ids([
245
247
  project1,
246
248
  project2
@@ -270,9 +272,8 @@ describe('Selection', () => {
270
272
  return { ...rest, numLinks: numLinks + links.length, stage: 'c' };
271
273
  })
272
274
  .target()
273
- .query(); // TODO(burdon): Different verb? (exec?)
275
+ .query();
274
276
 
275
- expect(ids(query.result)).toStrictEqual(ids([person1, person2, person3]));
276
277
  expect(query.value).toEqual({ numItems: 5, numLinks: 4, stage: 'c' });
277
278
  });
278
279
  });
@@ -70,7 +70,7 @@ export type SelectionRoot = Database | Entity;
70
70
  /**
71
71
  * Returned from each stage of the visitor.
72
72
  */
73
- export type SelectionContext<T extends Entity, R> = [entities: T[], result: R]
73
+ export type SelectionContext<T extends Entity, R> = [entities: T[], result?: R]
74
74
 
75
75
  /**
76
76
  * Visitor callback.
@@ -79,19 +79,18 @@ export type SelectionContext<T extends Entity, R> = [entities: T[], result: R]
79
79
  */
80
80
  export type Callable<T extends Entity, R> = (entities: T[], result: R) => R
81
81
 
82
- const dedupe = <T>(values: T[]) => Array.from(new Set(values));
83
-
84
82
  /**
85
83
  * Factory for selector that provides a root set of items.
86
84
  * @param itemsProvider
87
85
  * @param updateEventProvider
88
86
  * @param root
87
+ * @param filter
89
88
  * @param value Initial reducer value.
90
89
  */
91
90
  export const createSelector = <R>(
92
91
  // Provider is called each time the query is executed.
93
92
  itemsProvider: () => Item[],
94
- // TODO(burdon): Why is this a provider?
93
+ // TODO(burdon): Replace with direct event handler.
95
94
  updateEventProvider: () => Event<Entity[]>,
96
95
  root: SelectionRoot,
97
96
  filter: RootFilter | undefined,
@@ -106,7 +105,7 @@ export const createSelector = <R>(
106
105
  return [items, value];
107
106
  };
108
107
 
109
- return new Selection(visitor, updateEventProvider(), root);
108
+ return new Selection(visitor, updateEventProvider(), root, value !== undefined);
110
109
  };
111
110
 
112
111
  /**
@@ -119,7 +118,7 @@ export const createItemSelector = <R>(
119
118
  root: Item<any>,
120
119
  update: Event<Entity[]>,
121
120
  value: R
122
- ): Selection<Item<any>, R> => new Selection(() => [[root], value], update, root);
121
+ ): Selection<Item<any>, R> => new Selection(() => [[root], value], update, root, value !== undefined);
123
122
 
124
123
  /**
125
124
  * Selections are used to construct database subscriptions.
@@ -135,11 +134,13 @@ export class Selection<T extends Entity<any>, R = void> {
135
134
  * @param _visitor Executes the query.
136
135
  * @param _update The unfiltered update event.
137
136
  * @param _root The root of the selection. Must be a stable reference.
137
+ * @param _reducer
138
138
  */
139
139
  constructor (
140
140
  private readonly _visitor: (options: QueryOptions) => SelectionContext<T, R>,
141
141
  private readonly _update: Event<Entity[]>,
142
- private readonly _root: SelectionRoot
142
+ private readonly _root: SelectionRoot,
143
+ private readonly _reducer = false
143
144
  ) {}
144
145
 
145
146
  /**
@@ -148,14 +149,15 @@ export class Selection<T extends Entity<any>, R = void> {
148
149
  private _createSubSelection<U extends Entity> (
149
150
  map: (context: SelectionContext<T, R>, options: QueryOptions) => SelectionContext<U, R>
150
151
  ): Selection<U, R> {
151
- return new Selection(options => map(this._visitor(options), options), this._update, this._root);
152
+ return new Selection(options => map(this._visitor(options), options), this._update, this._root, this._reducer);
152
153
  }
153
154
 
154
155
  /**
155
156
  * Finish the selection and return the result.
156
157
  */
158
+ // TODO(burdon): Rename exec.
157
159
  query (options: QueryOptions = {}): SelectionResult<T, R> {
158
- return new SelectionResult<T, R>(() => this._visitor(options), this._update, this._root);
160
+ return new SelectionResult<T, R>(() => this._visitor(options), this._update, this._root, this._reducer);
159
161
  }
160
162
 
161
163
  /**
@@ -262,69 +264,96 @@ export class SelectionResult<T extends Entity, R = any> {
262
264
  * Fired when there are updates in the selection.
263
265
  * Only update that are relevant to the selection cause the update.
264
266
  */
265
- readonly update = new Event<T[]>();
267
+ readonly update = new Event<SelectionResult<T>>(); // TODO(burdon): Result result object.
266
268
 
267
- private _lastResult: SelectionContext<T, R>;
269
+ private _lastResult: SelectionContext<T, R> = [[]];
268
270
 
269
271
  constructor (
270
272
  private readonly _execute: () => SelectionContext<T, R>,
271
273
  private readonly _update: Event<Entity[]>,
272
- private readonly _root: SelectionRoot
274
+ private readonly _root: SelectionRoot,
275
+ private readonly _reducer: boolean
273
276
  ) {
274
- this._lastResult = this._execute();
275
-
276
- // TODO(burdon): Query updates are based on
277
-
278
- // TODO(burdon): Every update should update reducer.
277
+ this.refresh();
279
278
 
280
279
  // Re-run if deps change.
281
- // TODO(burdon): Explain this.
282
- // TODO(burdon): Should also fire if entities have been REMOVED from the set?
283
280
  this.update.addEffect(() => _update.on(currentEntities => {
284
- const result = this._execute();
285
- const [entities] = result;
286
- const set = new Set([...entities, ...this._lastResult[0]]);
287
- this._lastResult = result;
281
+ const [previousEntities] = this._lastResult;
288
282
 
289
- if (currentEntities.some(entity => set.has(entity as any))) {
290
- this.update.emit(entities);
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);
291
289
  }
292
290
  }));
293
291
  }
294
292
 
295
293
  /**
296
- * Get the result of this select.
294
+ * Re-run query.
297
295
  */
298
- // TODO(burdon): Rename entities.
299
- // TODO(burdon): Don't trigger execute in getter (provide refresh method).
300
- get result (): T[] {
301
- const [entities] = this._execute();
302
- return dedupe(entities);
296
+ refresh () {
297
+ const [entities, result] = this._execute();
298
+ this._lastResult = [dedupe(entities), result];
299
+ return this;
303
300
  }
304
301
 
305
- // TODO(burdon): Better name for reducer result? Just return value directly?
306
- get value (): R {
307
- const [, value] = this._execute();
308
- return value!;
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;
309
307
  }
310
308
 
311
309
  /**
312
- * If the result contains exatly one entity, returns it, errors otherwise.
310
+ * @deprecated
313
311
  */
314
- expectOne (): T {
315
- const res = this.result;
316
- assert(res.length === 1, 'Expected one result, got ' + res.length);
317
- return res[0];
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;
318
327
  }
319
328
 
320
329
  /**
321
- * The root of the selection. Either a database or an item. Must be a stable reference.
330
+ * Returns the selection or reducer result.
322
331
  */
323
- get root (): SelectionRoot {
324
- return this._root;
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];
325
348
  }
326
349
  }
327
350
 
351
+ //
352
+ // Utils
353
+ //
354
+
355
+ const dedupe = <T>(values: T[]) => Array.from(new Set(values));
356
+
328
357
  const coerceToId = (item: Item | ItemID): ItemID => {
329
358
  if (typeof item === 'string') {
330
359
  return item;
@@ -11,10 +11,10 @@ import { EchoEnvelope, MockFeedWriter, Timeframe } from '@dxos/echo-protocol';
11
11
  import { ModelFactory } from '@dxos/model-factory';
12
12
  import { ObjectModel } from '@dxos/object-model';
13
13
 
14
+ import { Item } from '../api';
14
15
  import { DataMirror } from './data-mirror';
15
16
  import { DataServiceHost } from './data-service-host';
16
17
  import { DataServiceRouter } from './data-service-router';
17
- import { Item } from './item';
18
18
  import { ItemDemuxer } from './item-demuxer';
19
19
  import { ItemManager } from './item-manager';
20
20
 
@@ -10,7 +10,7 @@ import { failUndefined } from '@dxos/debug';
10
10
  import { DataService } from '@dxos/echo-protocol';
11
11
  import { Model } from '@dxos/model-factory';
12
12
 
13
- import { Entity } from './entity';
13
+ import { Entity } from '../api';
14
14
  import { ItemManager } from './item-manager';
15
15
 
16
16
  const log = debug('dxos:echo-db:data-mirror');
@@ -18,11 +18,10 @@ import {
18
18
  SubscribeEntityStreamResponse
19
19
  } from '@dxos/echo-protocol';
20
20
 
21
+ import { Item, Link } from '../api';
21
22
  import { EntityNotFoundError } from '../errors';
22
- import { Item } from './item';
23
23
  import { ItemDemuxer } from './item-demuxer';
24
24
  import { ItemManager } from './item-manager';
25
- import { Link } from './link';
26
25
 
27
26
  const log = debug('dxos:echo-db:data-service-host');
28
27
 
@@ -2,13 +2,11 @@
2
2
  // Copyright 2020 DXOS.org
3
3
  //
4
4
 
5
- export * from './database';
5
+ export * from './data-mirror';
6
+ export * from './data-service-host';
7
+ export * from './data-service-router';
6
8
  export * from './database-backend';
7
- export * from './entity';
8
- export * from './item';
9
- export * from './link';
10
9
  export * from './item-demuxer';
11
10
  export * from './item-manager';
12
- export * from './timeframe-clock';
13
- export * from './selection';
14
11
  export * from './testing';
12
+ export * from './timeframe-clock';
@@ -14,7 +14,7 @@ import { createTransform } from '@dxos/feed-store';
14
14
  import { ModelFactory, TestModel } from '@dxos/model-factory';
15
15
  import { ObjectModel } from '@dxos/object-model';
16
16
 
17
- import { Item } from './item';
17
+ import { Item } from '../api';
18
18
  import { ItemDemuxer } from './item-demuxer';
19
19
  import { ItemManager } from './item-manager';
20
20
 
@@ -7,15 +7,12 @@ import debug from 'debug';
7
7
 
8
8
  import { Event } from '@dxos/async';
9
9
  import { failUndefined } from '@dxos/debug';
10
- import {
11
- DatabaseSnapshot, IEchoStream, ItemID, ItemSnapshot
12
- } from '@dxos/echo-protocol';
10
+ import { DatabaseSnapshot, IEchoStream, ItemID, ItemSnapshot } from '@dxos/echo-protocol';
13
11
  import { createWritable } from '@dxos/feed-store';
14
12
  import { Model, ModelFactory, ModelMessage } from '@dxos/model-factory';
15
13
  import { jsonReplacer } from '@dxos/util';
16
14
 
17
- import { Entity } from './entity';
18
- import { Item } from './item';
15
+ import { Entity, Item } from '../api';
19
16
  import { ItemManager, ModelConstructionOptions } from './item-manager';
20
17
 
21
18
  const log = debug('dxos:echo-db:item-demuxer');
@@ -93,7 +90,7 @@ export class ItemDemuxer {
93
90
  const item = this._itemManager.getItem(itemId);
94
91
  assert(item);
95
92
 
96
- item._processMutation(itemMutation, itemId => this._itemManager.getItem(itemId));
93
+ item._processMutation(itemMutation, (itemId: ItemID) => this._itemManager.getItem(itemId));
97
94
  }
98
95
 
99
96
  //
@@ -11,10 +11,8 @@ import { timed } from '@dxos/debug';
11
11
  import { EchoEnvelope, FeedWriter, ItemID, ItemType, mapFeedWriter, ModelSnapshot } from '@dxos/echo-protocol';
12
12
  import { Model, ModelFactory, ModelMessage, ModelType, StateManager } from '@dxos/model-factory';
13
13
 
14
+ import { Entity, Item, Link } from '../api';
14
15
  import { UnknownModelError } from '../errors';
15
- import { Entity } from './entity';
16
- import { Item } from './item';
17
- import { Link } from './link';
18
16
 
19
17
  const log = debug('dxos:echo-db:item-manager');
20
18
 
@@ -8,9 +8,9 @@ import { PublicKey } from '@dxos/crypto';
8
8
  import { EchoEnvelope, MockFeedWriter, Timeframe } from '@dxos/echo-protocol';
9
9
  import { ModelFactory } from '@dxos/model-factory';
10
10
 
11
+ import { Database } from '../api';
11
12
  import { DataServiceHost } from './data-service-host';
12
13
  import { DataServiceRouter } from './data-service-router';
13
- import { Database } from './database';
14
14
  import { FeedDatabaseBackend, RemoteDatabaseBackend } from './database-backend';
15
15
 
16
16
  export const createInMemoryDatabase = async (modelFactory: ModelFactory) => {