@dxos/echo-pipeline 0.4.8-main.c67d72c → 0.4.8-main.cd60bd2

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 (36) hide show
  1. package/dist/lib/browser/{chunk-XR2636AC.mjs → chunk-WAN2XUWE.mjs} +38 -700
  2. package/dist/lib/browser/chunk-WAN2XUWE.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +633 -6
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/testing/index.mjs +2 -274
  7. package/dist/lib/browser/testing/index.mjs.map +4 -4
  8. package/dist/lib/node/{chunk-LD4R726W.cjs → chunk-U6J2HC4T.cjs} +39 -691
  9. package/dist/lib/node/chunk-U6J2HC4T.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +647 -30
  11. package/dist/lib/node/index.cjs.map +4 -4
  12. package/dist/lib/node/meta.json +1 -1
  13. package/dist/lib/node/testing/index.cjs +12 -282
  14. package/dist/lib/node/testing/index.cjs.map +4 -4
  15. package/dist/types/src/automerge/automerge-host.d.ts.map +1 -1
  16. package/dist/types/src/space/control-pipeline.d.ts.map +1 -1
  17. package/dist/types/src/space/data-pipeline.d.ts +0 -1
  18. package/dist/types/src/space/data-pipeline.d.ts.map +1 -1
  19. package/dist/types/src/testing/index.d.ts +0 -1
  20. package/dist/types/src/testing/index.d.ts.map +1 -1
  21. package/dist/types/src/testing/util.d.ts +2 -6
  22. package/dist/types/src/testing/util.d.ts.map +1 -1
  23. package/package.json +33 -33
  24. package/src/automerge/automerge-host.ts +2 -5
  25. package/src/space/control-pipeline.ts +3 -1
  26. package/src/space/data-pipeline.ts +1 -44
  27. package/src/testing/index.ts +0 -1
  28. package/src/testing/util.ts +2 -26
  29. package/dist/lib/browser/chunk-XR2636AC.mjs.map +0 -7
  30. package/dist/lib/node/chunk-LD4R726W.cjs.map +0 -7
  31. package/dist/types/src/testing/database-test-rig.d.ts +0 -67
  32. package/dist/types/src/testing/database-test-rig.d.ts.map +0 -1
  33. package/dist/types/src/tests/database.test.d.ts +0 -2
  34. package/dist/types/src/tests/database.test.d.ts.map +0 -1
  35. package/src/testing/database-test-rig.ts +0 -289
  36. package/src/tests/database.test.ts +0 -100
@@ -1,289 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import { Event, Trigger } from '@dxos/async';
6
- import { DocumentModel } from '@dxos/document-model';
7
- import { DatabaseProxy, ItemManager, createModelMutation, encodeModelMutation } from '@dxos/echo-db';
8
- import { type WriteOptions, type WriteReceipt } from '@dxos/feed-store';
9
- import { invariant } from '@dxos/invariant';
10
- import { PublicKey } from '@dxos/keys';
11
- import { ModelFactory } from '@dxos/model-factory';
12
- import { type FeedMessageBlock, schema } from '@dxos/protocols';
13
- import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
14
- import { type EchoSnapshot, type SpaceSnapshot } from '@dxos/protocols/proto/dxos/echo/snapshot';
15
- import { type Epoch } from '@dxos/protocols/proto/dxos/halo/credentials';
16
- import { TextModel } from '@dxos/text-model';
17
- import { Timeframe } from '@dxos/timeframe';
18
- import { ComplexMap, isNotNullOrUndefined } from '@dxos/util';
19
-
20
- import { DatabaseHost } from '../db-host';
21
-
22
- const SPACE_KEY = PublicKey.random();
23
-
24
- export class DatabaseTestBuilder {
25
- public readonly peers = new ComplexMap<PublicKey, DatabaseTestPeer>(PublicKey.hash);
26
-
27
- async createPeer(spaceKey = SPACE_KEY): Promise<DatabaseTestPeer> {
28
- const peer = new DatabaseTestPeer(this, spaceKey);
29
- this.peers.set(peer.key, peer);
30
- await peer.open();
31
- return peer;
32
- }
33
- }
34
-
35
- type WriteRequest = {
36
- receipt: WriteReceipt;
37
- options: WriteOptions;
38
- trigger: Trigger;
39
- };
40
-
41
- // TODO(dmaretskyi): Still used in echo-schema test builder.
42
-
43
- export class DatabaseTestPeer {
44
- public readonly modelFactory = new ModelFactory().registerModel(DocumentModel).registerModel(TextModel);
45
-
46
- public items!: ItemManager;
47
- public proxy!: DatabaseProxy;
48
-
49
- public host!: DatabaseHost;
50
- public hostItems!: ItemManager;
51
-
52
- //
53
- // Test state.
54
- //
55
-
56
- public readonly key = PublicKey.random();
57
-
58
- public feedMessages: FeedMessage[] = [];
59
-
60
- public readonly snapshots = new Map<string, SpaceSnapshot>();
61
- private currentEpoch?: Epoch;
62
-
63
- /**
64
- * Sequence number of the last mutation confirmed to be written to the feed store.
65
- */
66
- public confirmed = -1;
67
-
68
- /**
69
- * Current position of the peer's pipeline.
70
- */
71
- public timeframe = new Timeframe();
72
-
73
- public snapshot: SpaceSnapshot | undefined;
74
-
75
- private readonly _onConfirm = new Event();
76
-
77
- private readonly _writes = new Set<WriteRequest>();
78
-
79
- constructor(
80
- public readonly rig: DatabaseTestBuilder,
81
- public readonly spaceKey: PublicKey,
82
- ) {}
83
-
84
- async open() {
85
- this.hostItems = new ItemManager(this.modelFactory);
86
- this.host = new DatabaseHost(
87
- {
88
- write: async (message, { afterWrite }: WriteOptions) => {
89
- const seq =
90
- this.feedMessages.push({
91
- timeframe: this.timeframe,
92
- payload: {
93
- data: message,
94
- },
95
- }) - 1;
96
-
97
- const request: WriteRequest = {
98
- receipt: {
99
- seq,
100
- feedKey: this.key,
101
- },
102
- options: { afterWrite },
103
- trigger: new Trigger(),
104
- };
105
- this._writes.add(request);
106
- await request.trigger.wait();
107
- return request.receipt;
108
- },
109
- },
110
- async () => {
111
- // No-op.
112
- },
113
- );
114
-
115
- await this.host.open(this.hostItems, this.modelFactory);
116
- if (this.snapshot) {
117
- this.host._itemDemuxer.restoreFromSnapshot(this.snapshot.database);
118
- }
119
-
120
- this.items = new ItemManager(this.modelFactory);
121
- this.proxy = new DatabaseProxy({
122
- service: this.host.createDataServiceHost({ deferEvents: false }),
123
- itemManager: this.items,
124
- spaceKey: this.spaceKey,
125
- });
126
- await this.proxy.open(this.modelFactory);
127
- }
128
-
129
- /**
130
- * Confirm mutations written to the local feed.
131
- * @param seq Sequence number of the mutation to confirm. If not specified, all mutations will be confirmed.
132
- */
133
- async confirm(seq?: number) {
134
- this.confirmed = seq ?? this.feedMessages.length - 1;
135
- this._onConfirm.emit();
136
-
137
- for (const request of [...this._writes]) {
138
- if (this.confirmed >= request.receipt.seq) {
139
- this._writes.delete(request);
140
- await request.options.afterWrite?.(request.receipt);
141
- request.trigger.wake();
142
- }
143
- }
144
-
145
- this._processMessages(Timeframe.merge(this.timeframe, new Timeframe([[this.key, this.confirmed]])));
146
- }
147
-
148
- /**
149
- * Replicate the database to the specified timeframe.
150
- * @param to Timeframe to replicate to. If not specified, the database will be replicated to the latest timeframe (based on all other peers).
151
- */
152
- replicate(to?: Timeframe) {
153
- const toTimeframe = Timeframe.merge(
154
- to ?? new Timeframe(Array.from(this.rig.peers.values()).map((peer) => [peer.key, peer.confirmed])),
155
- this.timeframe,
156
- );
157
- toTimeframe.set(this.key, this.confirmed);
158
-
159
- this._processMessages(toTimeframe);
160
- }
161
-
162
- /**
163
- * Reload data from the feed. Wipes unconfirmed mutations.
164
- */
165
- async reload() {
166
- await this.open();
167
- const timeframe = this.timeframe;
168
- this.timeframe = this.snapshot?.timeframe ?? new Timeframe();
169
- this._processMessages(timeframe);
170
- }
171
-
172
- /**
173
- * Create snapshot and use it for the next reload.
174
- */
175
- makeSnapshot(): SpaceSnapshot {
176
- this.snapshot = {
177
- spaceKey: SPACE_KEY.asUint8Array(),
178
- database: this.host.createSnapshot(),
179
- timeframe: this.timeframe,
180
- };
181
- return this.snapshot;
182
- }
183
-
184
- createEpoch(mockSnapshot?: EchoSnapshot) {
185
- const snapshot = this.makeSnapshot();
186
- // Substitute snapshot with the mock one for test purposes (e.g. to test with empty snapshot).
187
- mockSnapshot && (snapshot.database = mockSnapshot);
188
- const snapshotCid = PublicKey.from(
189
- schema.getCodecForType('dxos.echo.snapshot.SpaceSnapshot').encode(snapshot),
190
- ).toHex();
191
- this.snapshots.set(snapshotCid, snapshot);
192
-
193
- const epoch: Epoch = {
194
- previousId: PublicKey.random(),
195
- timeframe: this.timeframe,
196
- number: this.currentEpoch ? this.currentEpoch.number + 1 : 0,
197
- snapshotCid,
198
- };
199
-
200
- this.currentEpoch = epoch;
201
-
202
- this.host._itemDemuxer.restoreFromSnapshot(snapshot.database);
203
- }
204
-
205
- /**
206
- * Gets all candidate messages according to the current timeframe.
207
- * Does not take into account the current snapshot, timeframe dependencies, or the confirmed, or replicated state.
208
- */
209
- private _getHeads(): FeedMessageBlock[] {
210
- return Array.from(this.rig.peers.values())
211
- .map((peer): FeedMessageBlock => {
212
- const seq = this.timeframe.get(peer.key) ?? -1;
213
- const message = peer.feedMessages[seq + 1];
214
- return (
215
- message && {
216
- feedKey: peer.key,
217
- seq: seq + 1,
218
- data: message,
219
- }
220
- );
221
- })
222
- .filter(isNotNullOrUndefined);
223
- }
224
-
225
- private _processMessages(to: Timeframe) {
226
- let run = true;
227
- while (run) {
228
- run = false;
229
-
230
- const heads = this._getHeads();
231
- for (const candidate of heads) {
232
- const toSeq = to.get(candidate.feedKey) ?? -1;
233
- if (toSeq < candidate.seq) {
234
- continue;
235
- }
236
-
237
- const snapshotSeq = this.snapshot?.timeframe?.get(candidate.feedKey) ?? -1;
238
- if (candidate.seq <= snapshotSeq) {
239
- continue;
240
- }
241
-
242
- if (!Timeframe.dependencies(candidate.data.timeframe, this.timeframe).isEmpty()) {
243
- continue;
244
- }
245
-
246
- run = true;
247
- this.host.echoProcessor({
248
- batch: candidate.data.payload.data!.batch,
249
- meta: {
250
- feedKey: candidate.feedKey,
251
- seq: candidate.seq,
252
- memberKey: candidate.feedKey,
253
- timeframe: candidate.data.timeframe,
254
- },
255
- });
256
- this.timeframe = Timeframe.merge(this.timeframe, new Timeframe([[candidate.feedKey, candidate.seq]]));
257
- }
258
- }
259
- }
260
-
261
- getModel(id: string): DocumentModel | TextModel | undefined {
262
- const item = this.items.getItem(id);
263
- if (!item) {
264
- return;
265
- }
266
-
267
- invariant(item.modelMeta);
268
- const ModelConstructor = this.modelFactory.getModel(item.modelMeta.type)?.constructor;
269
- invariant(ModelConstructor);
270
-
271
- const model = new ModelConstructor(
272
- item.modelMeta,
273
- item.id,
274
- () => item.state,
275
- async (mutation) => {
276
- invariant(item.modelMeta);
277
- this.proxy.mutate(createModelMutation(id, encodeModelMutation(item.modelMeta, mutation)));
278
- return {
279
- feedKey: PublicKey.from('00'),
280
- seq: 0,
281
- waitToBeProcessed: () => Promise.resolve(),
282
- };
283
- },
284
- );
285
- model.initialize();
286
-
287
- return model;
288
- }
289
- }
@@ -1,100 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import expect from 'expect';
6
-
7
- import { DocumentModel, MutationBuilder } from '@dxos/document-model';
8
- import { createModelMutation, DatabaseProxy, encodeModelMutation, genesisMutation, ItemManager } from '@dxos/echo-db';
9
- import { TestBuilder as FeedTestBuilder } from '@dxos/feed-store/testing';
10
- import { PublicKey } from '@dxos/keys';
11
- import { ModelFactory } from '@dxos/model-factory';
12
- import { type DataMessage } from '@dxos/protocols/proto/dxos/echo/feed';
13
- import { createStorage, StorageType } from '@dxos/random-access-storage';
14
- import { test } from '@dxos/test';
15
-
16
- import { AutomergeHost } from '../automerge';
17
- import { createMappedFeedWriter } from '../common';
18
- import { DatabaseHost, DataServiceImpl, DataServiceSubscriptions } from '../db-host';
19
- import { createMemoryDatabase, createRemoteDatabaseFromDataServiceHost } from '../testing';
20
-
21
- const createDatabase = async () => {
22
- // prettier-ignore
23
- const modelFactory = new ModelFactory()
24
- .registerModel(DocumentModel);
25
-
26
- // TODO(dmaretskyi): Fix.
27
- const host = await createMemoryDatabase(modelFactory);
28
- const proxy = await createRemoteDatabaseFromDataServiceHost(modelFactory, host.backend.createDataServiceHost());
29
- return proxy;
30
- };
31
-
32
- const createDatabaseWithFeeds = async () => {
33
- const modelFactory = new ModelFactory().registerModel(DocumentModel);
34
-
35
- const feedTestBuilder = new FeedTestBuilder();
36
- const feedStore = feedTestBuilder.createFeedStore();
37
- const feed = await feedStore.openFeed(await feedTestBuilder.keyring.createKey(), { writable: true });
38
-
39
- const writer = createMappedFeedWriter((data: DataMessage) => ({ data }), feed.createFeedWriter());
40
- const host = new DatabaseHost(writer, async () => {
41
- /* No-op. */
42
- });
43
- await host.open(new ItemManager(modelFactory), new ModelFactory().registerModel(DocumentModel));
44
-
45
- const dataServiceSubscriptions = new DataServiceSubscriptions();
46
- const automergeHost = new AutomergeHost({
47
- directory: createStorage({ type: StorageType.RAM }).createDirectory(),
48
- });
49
- const dataService = new DataServiceImpl(dataServiceSubscriptions, automergeHost);
50
-
51
- const spaceKey = PublicKey.random();
52
- await dataServiceSubscriptions.registerSpace(spaceKey, host.createDataServiceHost());
53
-
54
- const proxy = new DatabaseProxy({ service: dataService, itemManager: new ItemManager(modelFactory), spaceKey });
55
- await proxy.open(new ModelFactory().registerModel(DocumentModel));
56
-
57
- return { proxy, host };
58
- };
59
-
60
- describe('database', () => {
61
- describe('proxy-service mode', () => {
62
- test('create object', async () => {
63
- const database = await createDatabase();
64
-
65
- const result = database.backend.mutate(genesisMutation(PublicKey.random().toHex(), DocumentModel.meta.type));
66
- expect(result.updateEvent.itemsUpdated.length).toEqual(1);
67
- expect(database.itemManager.entities.has(result.updateEvent.itemsUpdated[0].id));
68
- database.backend.commitBatch();
69
-
70
- await result.batch.waitToBeProcessed();
71
- expect(database.itemManager.entities.has(result.updateEvent.itemsUpdated[0].id));
72
- });
73
-
74
- test('mutate document', async () => {
75
- const database = await createDatabase();
76
-
77
- const id = PublicKey.random().toHex();
78
- database.backend.mutate(genesisMutation(id, DocumentModel.meta.type));
79
- const result = database.backend.mutate(
80
- createModelMutation(id, encodeModelMutation(DocumentModel.meta, new MutationBuilder().set('test', 42).build())),
81
- );
82
- expect(database.itemManager.getItem(id)!.state.data.test).toEqual(42);
83
- database.backend.commitBatch();
84
-
85
- await result.batch.waitToBeProcessed();
86
- expect(database.itemManager.getItem(id)!.state.data.test).toEqual(42);
87
- });
88
-
89
- // TODO(dmaretskyi): Flush is broken in this mock database.
90
- test
91
- .skip('flush', async () => {
92
- const { proxy } = await createDatabaseWithFeeds();
93
-
94
- const id = PublicKey.random().toHex();
95
- proxy.mutate(genesisMutation(id, DocumentModel.meta.type));
96
- await proxy.flush();
97
- })
98
- .timeout(100);
99
- });
100
- });