@dxos/echo-db 2.28.6-dev.ca9d0e33 → 2.28.7-dev.1dc18bee
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/link.d.ts.map +1 -1
- package/dist/src/api/link.js +1 -0
- package/dist/src/api/link.js.map +1 -1
- package/dist/src/database/data-service-host.d.ts.map +1 -1
- package/dist/src/database/data-service-host.js +14 -3
- package/dist/src/database/data-service-host.js.map +1 -1
- package/dist/src/database/item-demuxer.d.ts +8 -3
- package/dist/src/database/item-demuxer.d.ts.map +1 -1
- package/dist/src/database/item-demuxer.js +39 -10
- package/dist/src/database/item-demuxer.js.map +1 -1
- package/dist/src/database/item-manager.d.ts +3 -11
- package/dist/src/database/item-manager.d.ts.map +1 -1
- package/dist/src/database/item-manager.js +5 -12
- package/dist/src/database/item-manager.js.map +1 -1
- package/dist/src/snapshots/snapshot-store.test.js +2 -1
- package/dist/src/snapshots/snapshot-store.test.js.map +1 -1
- package/dist/src/snapshots/snapshot.test.js +66 -10
- package/dist/src/snapshots/snapshot.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +20 -20
- package/src/api/link.ts +3 -1
- package/src/database/data-service-host.ts +12 -2
- package/src/database/item-demuxer.ts +46 -11
- package/src/database/item-manager.ts +7 -14
- package/src/snapshots/snapshot-store.test.ts +2 -1
- package/src/snapshots/snapshot.test.ts +78 -8
|
@@ -7,12 +7,12 @@ import debug from 'debug';
|
|
|
7
7
|
|
|
8
8
|
import { Event } from '@dxos/async';
|
|
9
9
|
import { failUndefined } from '@dxos/debug';
|
|
10
|
-
import { DatabaseSnapshot, IEchoStream, ItemID, ItemSnapshot } from '@dxos/echo-protocol';
|
|
10
|
+
import { DatabaseSnapshot, IEchoStream, ItemID, ItemSnapshot, LinkSnapshot } from '@dxos/echo-protocol';
|
|
11
11
|
import { createWritable } from '@dxos/feed-store';
|
|
12
12
|
import { Model, ModelFactory, ModelMessage } from '@dxos/model-factory';
|
|
13
13
|
import { jsonReplacer } from '@dxos/util';
|
|
14
14
|
|
|
15
|
-
import { Entity, Item } from '../api';
|
|
15
|
+
import { Entity, Item, Link } from '../api';
|
|
16
16
|
import { ItemManager, ModelConstructionOptions } from './item-manager';
|
|
17
17
|
|
|
18
18
|
const log = debug('dxos:echo-db:item-demuxer');
|
|
@@ -43,6 +43,7 @@ export class ItemDemuxer {
|
|
|
43
43
|
}
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
+
// TODO(burdon): Factor out.
|
|
46
47
|
// TODO(burdon): Should this implement some "back-pressure" (hints) to the PartyProcessor?
|
|
47
48
|
return createWritable<IEchoStream>(async (message: IEchoStream) => {
|
|
48
49
|
log('Reading:', JSON.stringify(message, jsonReplacer));
|
|
@@ -116,26 +117,40 @@ export class ItemDemuxer {
|
|
|
116
117
|
createSnapshot (): DatabaseSnapshot {
|
|
117
118
|
assert(this._options.snapshots, 'Snapshots are disabled');
|
|
118
119
|
return {
|
|
119
|
-
items:
|
|
120
|
+
items: this._itemManager.items.map(item => this.createItemSnapshot(item)),
|
|
121
|
+
links: this._itemManager.links.map(link => this.createLinkSnapshot(link))
|
|
120
122
|
};
|
|
121
123
|
}
|
|
122
124
|
|
|
123
|
-
|
|
124
|
-
const model =
|
|
125
|
+
createItemSnapshot (item: Item<Model<any>>): ItemSnapshot {
|
|
126
|
+
const model = item._stateManager.createSnapshot();
|
|
125
127
|
|
|
126
128
|
return {
|
|
127
|
-
itemId:
|
|
128
|
-
itemType:
|
|
129
|
-
modelType:
|
|
130
|
-
parentId:
|
|
129
|
+
itemId: item.id,
|
|
130
|
+
itemType: item.type,
|
|
131
|
+
modelType: item.modelMeta.type,
|
|
132
|
+
parentId: item.parent?.id,
|
|
133
|
+
model
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
createLinkSnapshot (link: Link<Model<any>>): LinkSnapshot {
|
|
138
|
+
const model = link._stateManager.createSnapshot();
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
linkId: link.id,
|
|
142
|
+
linkType: link.type,
|
|
143
|
+
modelType: link.modelMeta.type,
|
|
144
|
+
source: link.source.id,
|
|
145
|
+
target: link.target.id,
|
|
131
146
|
model
|
|
132
147
|
};
|
|
133
148
|
}
|
|
134
149
|
|
|
135
150
|
async restoreFromSnapshot (snapshot: DatabaseSnapshot) {
|
|
136
|
-
const items =
|
|
137
|
-
log(`Restoring ${items.length} items from snapshot.`);
|
|
151
|
+
const { items = [], links = [] } = snapshot;
|
|
138
152
|
|
|
153
|
+
log(`Restoring ${items.length} items from snapshot.`);
|
|
139
154
|
for (const item of sortItemsTopologically(items)) {
|
|
140
155
|
assert(item.itemId);
|
|
141
156
|
assert(item.modelType);
|
|
@@ -149,9 +164,29 @@ export class ItemDemuxer {
|
|
|
149
164
|
snapshot: item.model
|
|
150
165
|
});
|
|
151
166
|
}
|
|
167
|
+
|
|
168
|
+
log(`Restoring ${links.length} links from snapshot.`);
|
|
169
|
+
for (const link of links) {
|
|
170
|
+
assert(link.linkId);
|
|
171
|
+
assert(link.modelType);
|
|
172
|
+
assert(link.model);
|
|
173
|
+
|
|
174
|
+
await this._itemManager.constructLink({
|
|
175
|
+
itemId: link.linkId,
|
|
176
|
+
itemType: link.linkType,
|
|
177
|
+
modelType: link.modelType,
|
|
178
|
+
source: link.source,
|
|
179
|
+
target: link.target,
|
|
180
|
+
snapshot: link.model
|
|
181
|
+
});
|
|
182
|
+
}
|
|
152
183
|
}
|
|
153
184
|
}
|
|
154
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Sort based on parents.
|
|
188
|
+
* @param items
|
|
189
|
+
*/
|
|
155
190
|
export function sortItemsTopologically (items: ItemSnapshot[]): ItemSnapshot[] {
|
|
156
191
|
const snapshots: ItemSnapshot[] = [];
|
|
157
192
|
const seenIds = new Set<ItemID>();
|
|
@@ -98,7 +98,7 @@ export class ItemManager {
|
|
|
98
98
|
parentId?: ItemID,
|
|
99
99
|
initProps?: any // TODO(burdon): Remove/change to array of mutations.
|
|
100
100
|
): Promise<Item<Model<unknown>>> {
|
|
101
|
-
assert(this._writeStream);
|
|
101
|
+
assert(this._writeStream); // TODO(burdon): Throw ReadOnlyError();
|
|
102
102
|
assert(modelType);
|
|
103
103
|
|
|
104
104
|
if (!this._modelFactory.hasModel(modelType)) {
|
|
@@ -188,7 +188,8 @@ export class ItemManager {
|
|
|
188
188
|
modelType, itemId, snapshot
|
|
189
189
|
}: ModelConstructionOptions): Promise<StateManager<Model>> {
|
|
190
190
|
// Convert model-specific outbound mutation to outbound envelope message.
|
|
191
|
-
const outboundTransform = this._writeStream && mapFeedWriter<Uint8Array, EchoEnvelope>(
|
|
191
|
+
const outboundTransform = this._writeStream && mapFeedWriter<Uint8Array, EchoEnvelope>(
|
|
192
|
+
mutation => ({ itemId, mutation }), this._writeStream);
|
|
192
193
|
|
|
193
194
|
// Create the model with the outbound stream.
|
|
194
195
|
return this._modelFactory.createModel<Model>(modelType, itemId, snapshot, this._memberKey, outboundTransform);
|
|
@@ -197,6 +198,7 @@ export class ItemManager {
|
|
|
197
198
|
/**
|
|
198
199
|
* Adds new entity to the tracked set. Sets up events and notifies any listeners waiting for this entity to be constructed.
|
|
199
200
|
*/
|
|
201
|
+
// TODO(burdon): Parent not used.
|
|
200
202
|
private _addEntity (entity: Entity<any>, parent?: Item<any> | null) {
|
|
201
203
|
assert(!this._entities.has(entity.id));
|
|
202
204
|
this._entities.set(entity.id, entity);
|
|
@@ -217,19 +219,12 @@ export class ItemManager {
|
|
|
217
219
|
|
|
218
220
|
/**
|
|
219
221
|
* Constructs an item with the appropriate model.
|
|
220
|
-
* @param itemId
|
|
221
|
-
* @param modelType
|
|
222
|
-
* @param itemType
|
|
223
|
-
* @param readStream - Inbound mutation stream (from multiplexer).
|
|
224
|
-
* @param [parentId] - ItemID of the parent of this Item (optional).
|
|
225
|
-
* @param initialMutations
|
|
226
|
-
* @param modelSnapshot
|
|
227
222
|
*/
|
|
228
223
|
@timed(5_000)
|
|
229
224
|
async constructItem ({
|
|
230
225
|
itemId,
|
|
231
|
-
modelType,
|
|
232
226
|
itemType,
|
|
227
|
+
modelType,
|
|
233
228
|
parentId,
|
|
234
229
|
snapshot
|
|
235
230
|
}: ItemConstructionOptions): Promise<Item<any>> {
|
|
@@ -259,14 +254,12 @@ export class ItemManager {
|
|
|
259
254
|
|
|
260
255
|
/**
|
|
261
256
|
* Constructs an item with the appropriate model.
|
|
262
|
-
* @param readStream - Inbound mutation stream (from multiplexer).
|
|
263
|
-
* @param parentId - ItemID of the parent of this Item (optional).
|
|
264
257
|
*/
|
|
265
258
|
@timed(5_000)
|
|
266
259
|
async constructLink ({
|
|
267
|
-
itemId,
|
|
268
|
-
modelType,
|
|
260
|
+
itemId, // TODO(burdon): linkId?
|
|
269
261
|
itemType,
|
|
262
|
+
modelType,
|
|
270
263
|
snapshot,
|
|
271
264
|
source,
|
|
272
265
|
target
|
|
@@ -8,7 +8,7 @@ import { it as test } from 'mocha';
|
|
|
8
8
|
|
|
9
9
|
import { waitForCondition } from '@dxos/async';
|
|
10
10
|
import { PublicKey } from '@dxos/crypto';
|
|
11
|
-
import { schema, ItemID, PartyKey } from '@dxos/echo-protocol';
|
|
11
|
+
import { schema, ItemID, MockFeedWriter, PartyKey } from '@dxos/echo-protocol';
|
|
12
12
|
import { ModelFactory } from '@dxos/model-factory';
|
|
13
13
|
import { ObjectModel, ValueUtil } from '@dxos/object-model';
|
|
14
14
|
|
|
@@ -16,6 +16,7 @@ import { ItemDemuxer, ItemManager } from '../database';
|
|
|
16
16
|
import { createTestInstance } from '../testing';
|
|
17
17
|
|
|
18
18
|
const log = debug('dxos:snapshot:test');
|
|
19
|
+
debug.enable('dxos:echo-db:*');
|
|
19
20
|
|
|
20
21
|
// TODO(burdon): Remove "foo", etc from tests.
|
|
21
22
|
describe('snapshot', () => {
|
|
@@ -59,15 +60,28 @@ describe('snapshot', () => {
|
|
|
59
60
|
test('produce & serialize a snapshot', async () => {
|
|
60
61
|
const echo = await createTestInstance({ initialize: true });
|
|
61
62
|
const party = await echo.createParty();
|
|
62
|
-
const
|
|
63
|
-
await
|
|
63
|
+
const item1 = await party.database.createItem({ model: ObjectModel, props: { title: 'Item1 - Creation' } });
|
|
64
|
+
await item1.model.setProperty('title', 'Item1 - Modified');
|
|
65
|
+
const item2 = await party.database.createItem({ model: ObjectModel, props: { title: 'Item2 - Creation' } });
|
|
66
|
+
await item2.model.setProperty('title', 'Item2 - Modified');
|
|
67
|
+
|
|
68
|
+
const link = await party.database.createLink({ source: item1, target: item2 });
|
|
64
69
|
|
|
65
70
|
const snapshot = party.createSnapshot();
|
|
66
|
-
expect(snapshot.database?.items).toHaveLength(
|
|
67
|
-
expect(snapshot.database?.
|
|
71
|
+
expect(snapshot.database?.items).toHaveLength(3); // 1 party + 2 items
|
|
72
|
+
expect(snapshot.database?.links).toHaveLength(1); // 1 link
|
|
73
|
+
expect(snapshot.database?.items?.find(i => i.itemId === item1.id)?.model?.snapshot).toBeDefined();
|
|
74
|
+
expect(snapshot.database?.items?.find(i => i.itemId === item2.id)?.model?.snapshot).toBeDefined();
|
|
75
|
+
expect(snapshot.database?.links?.find(l => l.linkId === link.id)?.linkId).toBeDefined();
|
|
76
|
+
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
|
|
78
|
+
const modelSnapshot1 = ObjectModel.meta.snapshotCodec?.decode(snapshot.database?.items?.find(i => i.itemId === item1.id)?.model?.snapshot!);
|
|
79
|
+
expect(modelSnapshot1).toEqual({ root: ValueUtil.createMessage({ title: 'Item1 - Modified' }) });
|
|
80
|
+
|
|
68
81
|
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
|
|
69
|
-
const
|
|
70
|
-
expect(
|
|
82
|
+
const modelSnapshot2 = ObjectModel.meta.snapshotCodec?.decode(snapshot.database?.items?.find(i => i.itemId === item2.id)?.model?.snapshot!);
|
|
83
|
+
expect(modelSnapshot2).toEqual({ root: ValueUtil.createMessage({ title: 'Item2 - Modified' }) });
|
|
84
|
+
|
|
71
85
|
expect(snapshot.halo?.messages && snapshot.halo?.messages?.length > 0).toBeTruthy();
|
|
72
86
|
expect(snapshot.timeframe?.size()).toBe(1);
|
|
73
87
|
|
|
@@ -78,12 +92,68 @@ describe('snapshot', () => {
|
|
|
78
92
|
expect(echo.isOpen).toBe(false);
|
|
79
93
|
});
|
|
80
94
|
|
|
95
|
+
test('restore from snapshot', async () => {
|
|
96
|
+
const modelFactory = new ModelFactory().registerModel(ObjectModel);
|
|
97
|
+
|
|
98
|
+
let data;
|
|
99
|
+
{
|
|
100
|
+
const itemManager = new ItemManager(modelFactory, PublicKey.random(), new MockFeedWriter());
|
|
101
|
+
const itemDemuxer = new ItemDemuxer(itemManager, modelFactory, { snapshots: true });
|
|
102
|
+
|
|
103
|
+
await itemManager.constructItem({
|
|
104
|
+
itemId: 'item-1',
|
|
105
|
+
itemType: 'test-item-type',
|
|
106
|
+
modelType: ObjectModel.meta.type,
|
|
107
|
+
snapshot: {}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await itemManager.constructItem({
|
|
111
|
+
itemId: 'item-2',
|
|
112
|
+
itemType: 'test-item-type',
|
|
113
|
+
modelType: ObjectModel.meta.type,
|
|
114
|
+
snapshot: {}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
await itemManager.constructLink({
|
|
118
|
+
itemId: 'link-1',
|
|
119
|
+
itemType: 'test-link-type',
|
|
120
|
+
modelType: ObjectModel.meta.type,
|
|
121
|
+
source: 'item-1',
|
|
122
|
+
target: 'item-2',
|
|
123
|
+
snapshot: {}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Create snapshot.
|
|
127
|
+
console.log('encoding...');
|
|
128
|
+
const snapshot = itemDemuxer.createSnapshot();
|
|
129
|
+
data = schema.getCodecForType('dxos.echo.snapshot.DatabaseSnapshot').encode(snapshot);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
{
|
|
133
|
+
const itemManager = new ItemManager(modelFactory, PublicKey.random());
|
|
134
|
+
const itemDemuxer = new ItemDemuxer(itemManager, modelFactory, { snapshots: true });
|
|
135
|
+
|
|
136
|
+
// Decode snapshot.
|
|
137
|
+
console.log('decoding...');
|
|
138
|
+
const snapshot = schema.getCodecForType('dxos.echo.snapshot.DatabaseSnapshot').decode(data);
|
|
139
|
+
await itemDemuxer.restoreFromSnapshot(snapshot);
|
|
140
|
+
|
|
141
|
+
expect(itemManager.items).toHaveLength(2);
|
|
142
|
+
expect(itemManager.links).toHaveLength(1);
|
|
143
|
+
|
|
144
|
+
const [item1, item2] = itemManager.items;
|
|
145
|
+
const [link] = itemManager.links;
|
|
146
|
+
expect(link.source.id).toBe(item1.id);
|
|
147
|
+
expect(link.target.id).toBe(item2.id);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
81
151
|
test('restore from empty snapshot', async () => {
|
|
82
152
|
const modelFactory = new ModelFactory().registerModel(ObjectModel);
|
|
83
153
|
const itemManager = new ItemManager(modelFactory, PublicKey.random());
|
|
84
154
|
const itemDemuxer = new ItemDemuxer(itemManager, modelFactory);
|
|
85
155
|
|
|
86
|
-
// TODO(burdon):
|
|
156
|
+
// TODO(burdon): Do actual test.
|
|
87
157
|
await itemDemuxer.restoreFromSnapshot({});
|
|
88
158
|
expect(true).toBeTruthy();
|
|
89
159
|
});
|