@verdant-web/store 3.6.4 → 3.7.0
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/bundle/index.js +12 -12
- package/dist/bundle/index.js.map +3 -3
- package/dist/esm/__tests__/batching.test.js +67 -1
- package/dist/esm/__tests__/batching.test.js.map +1 -1
- package/dist/esm/__tests__/fixtures/testStorage.d.ts +3 -1
- package/dist/esm/__tests__/fixtures/testStorage.js +3 -2
- package/dist/esm/__tests__/fixtures/testStorage.js.map +1 -1
- package/dist/esm/client/Client.d.ts +22 -1
- package/dist/esm/client/Client.js.map +1 -1
- package/dist/esm/client/ClientDescriptor.d.ts +7 -1
- package/dist/esm/client/ClientDescriptor.js +1 -0
- package/dist/esm/client/ClientDescriptor.js.map +1 -1
- package/dist/esm/entities/Entity.d.ts +16 -2
- package/dist/esm/entities/Entity.js +21 -4
- package/dist/esm/entities/Entity.js.map +1 -1
- package/dist/esm/entities/Entity.test.js +23 -2
- package/dist/esm/entities/Entity.test.js.map +1 -1
- package/dist/esm/entities/EntityMetadata.d.ts +2 -0
- package/dist/esm/entities/EntityMetadata.js +9 -1
- package/dist/esm/entities/EntityMetadata.js.map +1 -1
- package/dist/esm/entities/EntityStore.d.ts +1 -2
- package/dist/esm/entities/EntityStore.js +9 -7
- package/dist/esm/entities/EntityStore.js.map +1 -1
- package/dist/esm/entities/OperationBatcher.d.ts +25 -0
- package/dist/esm/entities/OperationBatcher.js +31 -3
- package/dist/esm/entities/OperationBatcher.js.map +1 -1
- package/dist/esm/entities/types.d.ts +7 -1
- package/dist/esm/metadata/Metadata.d.ts +3 -1
- package/dist/esm/metadata/Metadata.js +8 -1
- package/dist/esm/metadata/Metadata.js.map +1 -1
- package/dist/esm/queries/BaseQuery.d.ts +3 -0
- package/dist/esm/queries/BaseQuery.js +45 -11
- package/dist/esm/queries/BaseQuery.js.map +1 -1
- package/dist/esm/sync/Sync.d.ts +8 -2
- package/dist/esm/sync/Sync.js +6 -3
- package/dist/esm/sync/Sync.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/batching.test.ts +78 -0
- package/src/__tests__/fixtures/testStorage.ts +10 -1
- package/src/client/Client.ts +14 -1
- package/src/client/ClientDescriptor.ts +9 -0
- package/src/entities/Entity.test.ts +30 -2
- package/src/entities/Entity.ts +57 -10
- package/src/entities/EntityMetadata.ts +13 -3
- package/src/entities/EntityStore.ts +9 -9
- package/src/entities/OperationBatcher.ts +69 -2
- package/src/entities/types.ts +18 -1
- package/src/metadata/Metadata.ts +13 -0
- package/src/queries/BaseQuery.ts +50 -13
- package/src/sync/Sync.ts +12 -2
package/dist/esm/sync/Sync.js
CHANGED
|
@@ -40,7 +40,7 @@ export class NoSync extends EventSubscriber {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
export class ServerSync extends EventSubscriber {
|
|
43
|
-
constructor({ authEndpoint, fetchAuth, fetch, initialPresence, automaticTransportSelection = true, autoStart, initialTransport, pullInterval, presenceUpdateBatchTimeout, defaultProfile, useBroadcastChannel, }, { meta, ctx, onData, }) {
|
|
43
|
+
constructor({ authEndpoint, fetchAuth, fetch, initialPresence, automaticTransportSelection = true, autoStart, initialTransport, pullInterval, presenceUpdateBatchTimeout, defaultProfile, useBroadcastChannel, onOutgoingMessage, }, { meta, ctx, onData, }) {
|
|
44
44
|
super();
|
|
45
45
|
this.broadcastChannel = null;
|
|
46
46
|
this._activelySyncing = false;
|
|
@@ -137,9 +137,11 @@ export class ServerSync extends EventSubscriber {
|
|
|
137
137
|
this.setPullInterval = (interval) => {
|
|
138
138
|
this.pushPullSync.setInterval(interval);
|
|
139
139
|
};
|
|
140
|
-
this.send = (message) => {
|
|
140
|
+
this.send = async (message) => {
|
|
141
|
+
var _a;
|
|
141
142
|
if (this.activeSync.status === 'active') {
|
|
142
|
-
|
|
143
|
+
await this.activeSync.send(message);
|
|
144
|
+
(_a = this.onOutgoingMessage) === null || _a === void 0 ? void 0 : _a.call(this, message);
|
|
143
145
|
}
|
|
144
146
|
};
|
|
145
147
|
this.uploadFile = async (info) => {
|
|
@@ -192,6 +194,7 @@ export class ServerSync extends EventSubscriber {
|
|
|
192
194
|
this.meta = meta;
|
|
193
195
|
this.onData = onData;
|
|
194
196
|
this.log = ctx.log;
|
|
197
|
+
this.onOutgoingMessage = onOutgoingMessage;
|
|
195
198
|
this.presence = new PresenceManager({
|
|
196
199
|
initialPresence,
|
|
197
200
|
defaultProfile,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sync.js","sourceRoot":"","sources":["../../../src/sync/Sync.ts"],"names":[],"mappings":"AAAA,OAAO,EAGN,eAAe,EAGf,WAAW,GAEX,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,EAAkB,QAAQ,EAAoB,MAAM,eAAe,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EACN,0BAA0B,GAE1B,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAmDnD,MAAM,OAAO,MACZ,SAAQ,eAA2B;IAK5B,IAAI,KAAU,CAAC;IAEf,KAAK,KAAU,CAAC;IAEhB,IAAI,KAAU,CAAC;IAEf,cAAc,KAAU,CAAC;IAIzB,SAAS,KAAU,CAAC;IAEpB,OAAO,KAAU,CAAC;IAClB,eAAe,KAAU,CAAC;IAQjC,YAAY,EAAE,IAAI,EAAsB;QACvC,KAAK,EAAE,CAAC;QAxBA,SAAI,GAAG,MAAM,CAAC;QAUhB,YAAO,GAAG,GAAG,EAAE,GAAE,CAAC,CAAC;QAOV,gBAAW,GAAG,KAAK,CAAC;QACpB,WAAM,GAAG,QAAQ,CAAC;QAClB,iBAAY,GAAG,CAAC,CAAC;QAajC,eAAU,GAAG,KAAK,IAAI,EAAE;YACvB,OAAO;gBACN,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,KAAK;aACZ,CAAC;QACH,CAAC,CAAC;QAEF,YAAO,GAAG,KAAK,IAA6B,EAAE;YAC7C,OAAO;gBACN,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,KAAK;aACZ,CAAC;QACH,CAAC,CAAC;QAEF,aAAQ,GAAwB,KAAK,IAAI,EAAE,GAAE,CAAC,CAAC;QArB9C,IAAI,CAAC,QAAQ,GAAG,IAAI,eAAe,CAAC;YACnC,eAAe,EAAE,IAAW;YAC5B,cAAc,EAAE,IAAW;YAC3B,YAAY,EAAE,IAAI,CAAC,YAAY;SAC/B,CAAC,CAAC;IACJ,CAAC;CAiBD;
|
|
1
|
+
{"version":3,"file":"Sync.js","sourceRoot":"","sources":["../../../src/sync/Sync.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@verdant-web/store",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.7.0",
|
|
4
4
|
"access": "public",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"jwt-decode": "^3.1.2",
|
|
28
28
|
"kysely": "^0.27.3",
|
|
29
29
|
"weak-event": "^2.0.5",
|
|
30
|
-
"@verdant-web/common": "2.3.
|
|
30
|
+
"@verdant-web/common": "2.3.2"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "20.10.5",
|
|
@@ -55,4 +55,82 @@ describe('batching operations', () => {
|
|
|
55
55
|
|
|
56
56
|
expect(item.deleted).toBe(true);
|
|
57
57
|
});
|
|
58
|
+
|
|
59
|
+
it('should overwrite superseded sequential set operations on the same key where applicable', async () => {
|
|
60
|
+
const onOperation = vi.fn();
|
|
61
|
+
const client = await createTestStorage({
|
|
62
|
+
onOperation,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const item = await client.todos.put({
|
|
66
|
+
content: 'hello world',
|
|
67
|
+
category: 'general',
|
|
68
|
+
});
|
|
69
|
+
onOperation.mockReset();
|
|
70
|
+
|
|
71
|
+
item.set('content', 'hello world 2');
|
|
72
|
+
item.set('content', 'hello world 3');
|
|
73
|
+
item.set('content', 'hello world 4');
|
|
74
|
+
|
|
75
|
+
await client.batch().commit();
|
|
76
|
+
|
|
77
|
+
expect(item.get('content')).toBe('hello world 4');
|
|
78
|
+
expect(onOperation.mock.calls.length).toBe(1);
|
|
79
|
+
expect(onOperation).toHaveBeenCalledWith({
|
|
80
|
+
oid: item.uid,
|
|
81
|
+
timestamp: expect.anything(),
|
|
82
|
+
isLocal: true,
|
|
83
|
+
data: {
|
|
84
|
+
op: 'set',
|
|
85
|
+
name: 'content',
|
|
86
|
+
value: 'hello world 4',
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should not interfere with other fields when superseding', async () => {
|
|
92
|
+
const onOperation = vi.fn();
|
|
93
|
+
const client = await createTestStorage({
|
|
94
|
+
onOperation,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const item = await client.todos.put({
|
|
98
|
+
content: 'hello world',
|
|
99
|
+
category: 'general',
|
|
100
|
+
});
|
|
101
|
+
onOperation.mockReset();
|
|
102
|
+
|
|
103
|
+
item.set('content', 'hello world 2');
|
|
104
|
+
item.set('category', 'never');
|
|
105
|
+
item.set('content', 'hello world 3');
|
|
106
|
+
item.set('category', 'general');
|
|
107
|
+
|
|
108
|
+
await client.batch().commit();
|
|
109
|
+
|
|
110
|
+
expect(item.get('content')).toBe('hello world 3');
|
|
111
|
+
expect(item.get('category')).toBe('general');
|
|
112
|
+
expect(onOperation.mock.calls.length).toBe(2);
|
|
113
|
+
|
|
114
|
+
expect(onOperation).toHaveBeenCalledWith({
|
|
115
|
+
oid: item.uid,
|
|
116
|
+
timestamp: expect.anything(),
|
|
117
|
+
isLocal: true,
|
|
118
|
+
data: {
|
|
119
|
+
op: 'set',
|
|
120
|
+
name: 'content',
|
|
121
|
+
value: 'hello world 3',
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
expect(onOperation).toHaveBeenCalledWith({
|
|
126
|
+
oid: item.uid,
|
|
127
|
+
timestamp: expect.anything(),
|
|
128
|
+
isLocal: true,
|
|
129
|
+
data: {
|
|
130
|
+
op: 'set',
|
|
131
|
+
name: 'category',
|
|
132
|
+
value: 'general',
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
});
|
|
58
136
|
});
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ClientMessage,
|
|
3
|
+
createMigration,
|
|
4
|
+
Operation,
|
|
5
|
+
schema,
|
|
6
|
+
} from '@verdant-web/common';
|
|
2
7
|
// @ts-ignore
|
|
3
8
|
import { IDBFactory } from 'fake-indexeddb';
|
|
4
9
|
import { ClientWithCollections, ClientDescriptor } from '../../index.js';
|
|
5
10
|
import { METADATA_VERSION_KEY } from '../../client/constants.js';
|
|
11
|
+
import { Sync } from '../../sync/Sync.js';
|
|
6
12
|
|
|
7
13
|
export const todoCollection = schema.collection({
|
|
8
14
|
name: 'todo',
|
|
@@ -91,11 +97,13 @@ export function createTestStorage({
|
|
|
91
97
|
disableRebasing = false,
|
|
92
98
|
metadataVersion,
|
|
93
99
|
log,
|
|
100
|
+
onOperation,
|
|
94
101
|
}: {
|
|
95
102
|
idb?: IDBFactory;
|
|
96
103
|
disableRebasing?: boolean;
|
|
97
104
|
metadataVersion?: number;
|
|
98
105
|
log?: (...args: any[]) => void;
|
|
106
|
+
onOperation?: (op: Operation) => void;
|
|
99
107
|
} = {}) {
|
|
100
108
|
const storage = new ClientDescriptor({
|
|
101
109
|
schema: testSchema,
|
|
@@ -105,6 +113,7 @@ export function createTestStorage({
|
|
|
105
113
|
disableRebasing,
|
|
106
114
|
log,
|
|
107
115
|
[METADATA_VERSION_KEY]: metadataVersion,
|
|
116
|
+
onOperation,
|
|
108
117
|
}).open();
|
|
109
118
|
return storage as Promise<ClientWithCollections>;
|
|
110
119
|
}
|
package/src/client/Client.ts
CHANGED
|
@@ -197,7 +197,7 @@ export class Client<Presence = any, Profile = any> extends EventSubscriber<{
|
|
|
197
197
|
return this.entities.batch;
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
stats = async () => {
|
|
200
|
+
stats = async (): Promise<ClientStats> => {
|
|
201
201
|
const collectionNames = Object.keys(this.schema.collections);
|
|
202
202
|
let collections = {} as Record<string, { count: number; size: number }>;
|
|
203
203
|
if (this.disposed) {
|
|
@@ -427,3 +427,16 @@ export class Client<Presence = any, Profile = any> extends EventSubscriber<{
|
|
|
427
427
|
await this.import(exportData);
|
|
428
428
|
};
|
|
429
429
|
}
|
|
430
|
+
|
|
431
|
+
export interface ClientStats {
|
|
432
|
+
collections: Record<string, { count: number; size: number }>;
|
|
433
|
+
meta: {
|
|
434
|
+
baselinesSize: { count: number; size: number };
|
|
435
|
+
operationsSize: { count: number; size: number };
|
|
436
|
+
};
|
|
437
|
+
storage: StorageEstimate | undefined;
|
|
438
|
+
totalMetaSize: number;
|
|
439
|
+
totalCollectionsSize: number;
|
|
440
|
+
metaToDataRatio: number;
|
|
441
|
+
quotaUsage: number | undefined;
|
|
442
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
EventSubscriber,
|
|
3
3
|
Migration,
|
|
4
|
+
Operation,
|
|
4
5
|
StorageSchema,
|
|
5
6
|
hashObject,
|
|
6
7
|
} from '@verdant-web/common';
|
|
@@ -60,6 +61,13 @@ export interface ClientDescriptorOptions<Presence = any, Profile = any> {
|
|
|
60
61
|
* Configuration for file management
|
|
61
62
|
*/
|
|
62
63
|
files?: FileManagerConfig;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Listen for operations as they are applied to the database.
|
|
67
|
+
* Wouldn't recommend using this unless you know what you're doing.
|
|
68
|
+
* It's a very hot code path...
|
|
69
|
+
*/
|
|
70
|
+
onOperation?: (operation: Operation) => void;
|
|
63
71
|
/**
|
|
64
72
|
* Enables experimental WeakRef usage to cull documents
|
|
65
73
|
* from cache that aren't being used. This is a performance
|
|
@@ -173,6 +181,7 @@ export class ClientDescriptor<
|
|
|
173
181
|
const meta = new Metadata({
|
|
174
182
|
context,
|
|
175
183
|
disableRebasing: init.disableRebasing,
|
|
184
|
+
onOperation: init.onOperation,
|
|
176
185
|
});
|
|
177
186
|
|
|
178
187
|
// verify schema integrity
|
|
@@ -51,7 +51,9 @@ describe('Entity', () => {
|
|
|
51
51
|
},
|
|
52
52
|
} as const;
|
|
53
53
|
|
|
54
|
-
function createTestEntity(
|
|
54
|
+
function createTestEntity({
|
|
55
|
+
onPendingOperations = vi.fn(),
|
|
56
|
+
}: { onPendingOperations?: () => void } = {}) {
|
|
55
57
|
const events: EntityStoreEvents = {
|
|
56
58
|
add: new WeakEvent(),
|
|
57
59
|
replace: new WeakEvent(),
|
|
@@ -70,7 +72,7 @@ describe('Entity', () => {
|
|
|
70
72
|
events,
|
|
71
73
|
metadataFamily: new EntityFamilyMetadata({
|
|
72
74
|
ctx: mockContext,
|
|
73
|
-
onPendingOperations
|
|
75
|
+
onPendingOperations,
|
|
74
76
|
rootOid: 'test/1',
|
|
75
77
|
}),
|
|
76
78
|
files: {
|
|
@@ -239,4 +241,30 @@ describe('Entity', () => {
|
|
|
239
241
|
expect(entity.get('string')).toBe('new world');
|
|
240
242
|
});
|
|
241
243
|
});
|
|
244
|
+
|
|
245
|
+
describe('dropping pending operations', () => {
|
|
246
|
+
it('drops the correct operation', () => {
|
|
247
|
+
const onPendingOperations = vi.fn();
|
|
248
|
+
const { entity, initialize } = createTestEntity({ onPendingOperations });
|
|
249
|
+
|
|
250
|
+
initialize({
|
|
251
|
+
id: 'hi',
|
|
252
|
+
string: 'world',
|
|
253
|
+
number: 1,
|
|
254
|
+
nullable: null,
|
|
255
|
+
nonNullable: { first: { inner: 'foo' } },
|
|
256
|
+
map: {},
|
|
257
|
+
list: [],
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
entity.update({ string: 'new world' });
|
|
261
|
+
expect(onPendingOperations).toHaveBeenCalledTimes(1);
|
|
262
|
+
const operation = onPendingOperations.mock.calls[0][0][0];
|
|
263
|
+
console.log(operation);
|
|
264
|
+
|
|
265
|
+
entity.__discardPendingOperation__(operation);
|
|
266
|
+
|
|
267
|
+
expect(entity.get('string')).toBe('world');
|
|
268
|
+
});
|
|
269
|
+
});
|
|
242
270
|
});
|
package/src/entities/Entity.ts
CHANGED
|
@@ -475,11 +475,15 @@ export class Entity<
|
|
|
475
475
|
}
|
|
476
476
|
};
|
|
477
477
|
|
|
478
|
+
private invalidateCachedView = () => {
|
|
479
|
+
this._viewData = undefined;
|
|
480
|
+
this.cachedView = undefined;
|
|
481
|
+
};
|
|
482
|
+
|
|
478
483
|
private change = (ev: EntityChange) => {
|
|
479
484
|
if (ev.oid === this.oid) {
|
|
480
485
|
// reset cached view
|
|
481
|
-
this.
|
|
482
|
-
this.cachedView = undefined;
|
|
486
|
+
this.invalidateCachedView();
|
|
483
487
|
if (!this.parent) {
|
|
484
488
|
this.changeRoot(ev);
|
|
485
489
|
} else {
|
|
@@ -751,15 +755,42 @@ export class Entity<
|
|
|
751
755
|
}
|
|
752
756
|
};
|
|
753
757
|
|
|
754
|
-
set =
|
|
758
|
+
set = (
|
|
759
|
+
key: any,
|
|
760
|
+
value: any,
|
|
761
|
+
options?: {
|
|
762
|
+
/**
|
|
763
|
+
* Forces the creation of a change for this set even if the value is the same
|
|
764
|
+
* as the current value for this key. This has an effect in situations where
|
|
765
|
+
* offline changes from others are interleaved with local changes; when setting
|
|
766
|
+
* a value to its current value (force=true), if that value were retroactively changed from
|
|
767
|
+
* offline changes, the set would put it back to the value specified. If the
|
|
768
|
+
* set is ignored because the value is the same (force=false), then retroactive
|
|
769
|
+
* changes will be preserved.
|
|
770
|
+
*/
|
|
771
|
+
force: boolean;
|
|
772
|
+
},
|
|
773
|
+
) => {
|
|
755
774
|
assertNotSymbol(key);
|
|
756
|
-
this.
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
this.
|
|
761
|
-
|
|
762
|
-
|
|
775
|
+
if (!options?.force && this.get(key) === value) return;
|
|
776
|
+
|
|
777
|
+
if (this.isList) {
|
|
778
|
+
this.addPendingOperations(
|
|
779
|
+
this.patchCreator.createListSet(
|
|
780
|
+
this.oid,
|
|
781
|
+
key,
|
|
782
|
+
this.processInputValue(value, key),
|
|
783
|
+
),
|
|
784
|
+
);
|
|
785
|
+
} else {
|
|
786
|
+
this.addPendingOperations(
|
|
787
|
+
this.patchCreator.createSet(
|
|
788
|
+
this.oid,
|
|
789
|
+
key,
|
|
790
|
+
this.processInputValue(value, key),
|
|
791
|
+
),
|
|
792
|
+
);
|
|
793
|
+
}
|
|
763
794
|
};
|
|
764
795
|
|
|
765
796
|
/**
|
|
@@ -998,6 +1029,17 @@ export class Entity<
|
|
|
998
1029
|
this.view.forEach(callback);
|
|
999
1030
|
};
|
|
1000
1031
|
|
|
1032
|
+
reduce = <U>(
|
|
1033
|
+
callback: (
|
|
1034
|
+
previousValue: U,
|
|
1035
|
+
currentValue: ListItemValue<KeyValue>,
|
|
1036
|
+
index: number,
|
|
1037
|
+
) => U,
|
|
1038
|
+
initialValue: U,
|
|
1039
|
+
): U => {
|
|
1040
|
+
return this.view.reduce(callback, initialValue);
|
|
1041
|
+
};
|
|
1042
|
+
|
|
1001
1043
|
some = (predicate: (value: ListItemValue<KeyValue>) => boolean): boolean => {
|
|
1002
1044
|
return this.view.some(predicate);
|
|
1003
1045
|
};
|
|
@@ -1034,6 +1076,11 @@ export class Entity<
|
|
|
1034
1076
|
return this.metadataFamily.get(oid).computeView(type === 'confirmed');
|
|
1035
1077
|
};
|
|
1036
1078
|
__getFamilyOids__ = () => this.metadataFamily.getAllOids();
|
|
1079
|
+
|
|
1080
|
+
__discardPendingOperation__ = (operation: Operation) => {
|
|
1081
|
+
this.metadataFamily.discardPendingOperation(operation);
|
|
1082
|
+
this.invalidateCachedView();
|
|
1083
|
+
};
|
|
1037
1084
|
}
|
|
1038
1085
|
|
|
1039
1086
|
function assertNotSymbol<T>(key: T): asserts key is Exclude<T, symbol> {
|
|
@@ -176,19 +176,24 @@ export class EntityMetadata {
|
|
|
176
176
|
// FIXME: seems inefficient
|
|
177
177
|
// remove this incoming op from pending if it's in there
|
|
178
178
|
const pendingPrior = this.pendingOperations.length;
|
|
179
|
-
this.
|
|
180
|
-
(pendingOp) => op.timestamp !== pendingOp.timestamp,
|
|
181
|
-
);
|
|
179
|
+
this.discardPendingOperation(op);
|
|
182
180
|
totalAdded -= pendingPrior - this.pendingOperations.length;
|
|
183
181
|
}
|
|
184
182
|
return totalAdded;
|
|
185
183
|
};
|
|
186
184
|
|
|
187
185
|
addPendingOperation = (operation: Operation) => {
|
|
186
|
+
// check to see if new operation supersedes the previous one
|
|
188
187
|
// we can assume pending ops are always newer
|
|
189
188
|
this.pendingOperations.push(operation);
|
|
190
189
|
};
|
|
191
190
|
|
|
191
|
+
discardPendingOperation = (operation: Operation) => {
|
|
192
|
+
this.pendingOperations = this.pendingOperations.filter(
|
|
193
|
+
(op) => op.timestamp !== operation.timestamp,
|
|
194
|
+
);
|
|
195
|
+
};
|
|
196
|
+
|
|
192
197
|
private applyOperations = (
|
|
193
198
|
base: any,
|
|
194
199
|
deleted: boolean,
|
|
@@ -361,4 +366,9 @@ export class EntityFamilyMetadata {
|
|
|
361
366
|
}
|
|
362
367
|
return Object.values(changes);
|
|
363
368
|
};
|
|
369
|
+
|
|
370
|
+
discardPendingOperation = (operation: Operation) => {
|
|
371
|
+
const ent = this.entities.get(operation.oid);
|
|
372
|
+
ent?.discardPendingOperation(operation);
|
|
373
|
+
};
|
|
364
374
|
}
|
|
@@ -24,7 +24,6 @@ import { OperationBatcher } from './OperationBatcher.js';
|
|
|
24
24
|
import { QueryableStorage } from '../queries/QueryableStorage.js';
|
|
25
25
|
import { WeakEvent } from 'weak-event';
|
|
26
26
|
import { processValueFiles } from '../files/utils.js';
|
|
27
|
-
import { abort } from 'process';
|
|
28
27
|
|
|
29
28
|
enum AbortReason {
|
|
30
29
|
Reset,
|
|
@@ -197,9 +196,6 @@ export class EntityStore extends Disposable {
|
|
|
197
196
|
});
|
|
198
197
|
});
|
|
199
198
|
} else {
|
|
200
|
-
if (this.cache.has(oid)) {
|
|
201
|
-
this.ctx.log('debug', 'Cache has', oid, ', an event should follow.');
|
|
202
|
-
}
|
|
203
199
|
event.invoke(this, {
|
|
204
200
|
oid,
|
|
205
201
|
baselines,
|
|
@@ -214,13 +210,12 @@ export class EntityStore extends Disposable {
|
|
|
214
210
|
};
|
|
215
211
|
|
|
216
212
|
// then, asynchronously add to the database
|
|
213
|
+
// this also emits messages to sync
|
|
214
|
+
// TODO: could messages be sent to sync before storage,
|
|
215
|
+
// so that realtime is lower latency? What would happen
|
|
216
|
+
// if the storage failed?
|
|
217
217
|
await this.meta.insertData(data, abortOptions);
|
|
218
218
|
|
|
219
|
-
// FIXME: entities hydrated here are not seeing
|
|
220
|
-
// the operations just inserted above!!
|
|
221
|
-
// IDEA: can we coordinate here with hydrate promises
|
|
222
|
-
// based on affected OIDs?
|
|
223
|
-
|
|
224
219
|
// recompute all affected documents for querying
|
|
225
220
|
const entities = await Promise.all(
|
|
226
221
|
allDocumentOids.map(async (oid) => {
|
|
@@ -459,6 +454,11 @@ export class EntityStore extends Disposable {
|
|
|
459
454
|
this.batcher.addOperations(operations);
|
|
460
455
|
};
|
|
461
456
|
|
|
457
|
+
discardPendingOperation = (operation: Operation) => {
|
|
458
|
+
const root = getOidRoot(operation.oid);
|
|
459
|
+
this.cache.get(root)?.deref()?.__discardPendingOperation__(operation);
|
|
460
|
+
};
|
|
461
|
+
|
|
462
462
|
/**
|
|
463
463
|
* Loads initial Entity data from storage
|
|
464
464
|
*/
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Batcher,
|
|
3
|
+
ObjectIdentifier,
|
|
3
4
|
Operation,
|
|
5
|
+
PropertyName,
|
|
4
6
|
generateId,
|
|
5
7
|
getOidRoot,
|
|
6
8
|
getUndoOperations,
|
|
7
9
|
groupPatchesByOid,
|
|
10
|
+
isSuperseded,
|
|
11
|
+
operationSupersedes,
|
|
8
12
|
} from '@verdant-web/common';
|
|
9
13
|
import { Metadata } from '../metadata/Metadata.js';
|
|
10
14
|
import { Context } from '../context.js';
|
|
@@ -74,6 +78,40 @@ export class OperationBatcher {
|
|
|
74
78
|
'to storage / sync',
|
|
75
79
|
);
|
|
76
80
|
if (!operations.length) return;
|
|
81
|
+
|
|
82
|
+
// next block of logic computes superseding rules to eliminate
|
|
83
|
+
// operations which are 'overshadowed' by later ones on the same
|
|
84
|
+
// key.
|
|
85
|
+
|
|
86
|
+
const committed: Operation[] = [];
|
|
87
|
+
const supersessions: Record<
|
|
88
|
+
ObjectIdentifier,
|
|
89
|
+
Set<boolean | PropertyName>
|
|
90
|
+
> = {};
|
|
91
|
+
for (let i = operations.length - 1; i >= 0; i--) {
|
|
92
|
+
const op = operations[i];
|
|
93
|
+
|
|
94
|
+
// check for supersession from later operation which either
|
|
95
|
+
// covers the whole id (true) or this key
|
|
96
|
+
const existingSupersession = supersessions[op.oid];
|
|
97
|
+
if (existingSupersession && isSuperseded(op, existingSupersession)) {
|
|
98
|
+
this.entities.discardPendingOperation(op);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// determine if this operation supersedes others
|
|
103
|
+
const supersession = operationSupersedes(op);
|
|
104
|
+
if (supersession !== false) {
|
|
105
|
+
if (!supersessions[op.oid]) {
|
|
106
|
+
supersessions[op.oid] = new Set<boolean | PropertyName>();
|
|
107
|
+
}
|
|
108
|
+
supersessions[op.oid]!.add(supersession);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// add this operation to final list
|
|
112
|
+
committed.unshift(op);
|
|
113
|
+
}
|
|
114
|
+
|
|
77
115
|
// rewrite timestamps of all operations to now - this preserves
|
|
78
116
|
// the linear history of operations which are sent to the server.
|
|
79
117
|
// even if multiple batches are spun up in parallel and flushed
|
|
@@ -86,10 +124,14 @@ export class OperationBatcher {
|
|
|
86
124
|
// despite the provisional timestamp being earlier
|
|
87
125
|
// NOTE: this MUST be mutating the original operation object! this timestamp
|
|
88
126
|
// also serves as a unique ID for deduplication later.
|
|
89
|
-
|
|
127
|
+
|
|
128
|
+
// NOTE: need to rewind back in order to set timestamps correctly.
|
|
129
|
+
// cannot be done in reversed loop above or timestamps would be
|
|
130
|
+
// in reverse order.
|
|
131
|
+
for (const op of committed) {
|
|
90
132
|
op.timestamp = this.meta.now;
|
|
91
133
|
}
|
|
92
|
-
await this.commitOperations(
|
|
134
|
+
await this.commitOperations(committed, meta);
|
|
93
135
|
};
|
|
94
136
|
|
|
95
137
|
/**
|
|
@@ -148,9 +190,34 @@ export class OperationBatcher {
|
|
|
148
190
|
max = null,
|
|
149
191
|
timeout = this.defaultBatchTimeout,
|
|
150
192
|
}: {
|
|
193
|
+
/** Allows turning off undo for this batch, making it 'permanent' */
|
|
151
194
|
undoable?: boolean;
|
|
195
|
+
/**
|
|
196
|
+
* Provide a stable name to any invocation of .batch() and the changes made
|
|
197
|
+
* within run() will all be added to the same batch. If a batch hits the max
|
|
198
|
+
* limit or timeout and is flushed, the name will be reused for a new batch
|
|
199
|
+
* automatically. Provide a stable name to make changes from anywhere in your
|
|
200
|
+
* app to be grouped together in the same batch with the same limit behavior.
|
|
201
|
+
*
|
|
202
|
+
* Limit configuration provided to each invocation of .batch() with the same
|
|
203
|
+
* name will overwrite any other invocation's limit configuration. It's
|
|
204
|
+
* recommended to provide limits in one place and only provide a name
|
|
205
|
+
* in others.
|
|
206
|
+
*/
|
|
152
207
|
batchName?: string;
|
|
208
|
+
/**
|
|
209
|
+
* The maximum number of operations the batch will hold before flushing
|
|
210
|
+
* automatically. If null, the batch will not flush automatically based
|
|
211
|
+
* on operation count.
|
|
212
|
+
*/
|
|
153
213
|
max?: number | null;
|
|
214
|
+
/**
|
|
215
|
+
* The number of milliseconds to wait before flushing the batch automatically.
|
|
216
|
+
* If null, the batch will not flush automatically based on time. It is not
|
|
217
|
+
* recommended to set this to null, as an unflushed batch will never be written
|
|
218
|
+
* to storage or sync. If you do require undefined timing in a batch, make sure
|
|
219
|
+
* to always call .commit() on the batch yourself.
|
|
220
|
+
*/
|
|
154
221
|
timeout?: number | null;
|
|
155
222
|
} = {}): OperationBatch => {
|
|
156
223
|
const internalBatch = this.batcher.add({
|
package/src/entities/types.ts
CHANGED
|
@@ -87,7 +87,11 @@ export interface ObjectEntity<
|
|
|
87
87
|
readonly size: number;
|
|
88
88
|
entries(): [string, Exclude<Value[keyof Value], undefined>][];
|
|
89
89
|
values(): Exclude<Value[keyof Value], undefined>[];
|
|
90
|
-
set<Key extends keyof Init>(
|
|
90
|
+
set<Key extends keyof Init>(
|
|
91
|
+
key: Key,
|
|
92
|
+
value: Init[Key],
|
|
93
|
+
options?: { force?: boolean },
|
|
94
|
+
): void;
|
|
91
95
|
delete(key: DeletableKeys<Value>): void;
|
|
92
96
|
update(
|
|
93
97
|
value: DeepPartial<Init>,
|
|
@@ -136,6 +140,11 @@ export interface ListEntity<
|
|
|
136
140
|
BaseEntity<Init, Value, Snapshot> {
|
|
137
141
|
readonly isList: true;
|
|
138
142
|
readonly length: number;
|
|
143
|
+
set(
|
|
144
|
+
index: number,
|
|
145
|
+
value: ListItemInit<Init>,
|
|
146
|
+
options?: { force?: boolean },
|
|
147
|
+
): void;
|
|
139
148
|
push(value: ListItemInit<Init>): void;
|
|
140
149
|
insert(index: number, value: ListItemInit<Init>): void;
|
|
141
150
|
move(from: number, to: number): void;
|
|
@@ -149,6 +158,14 @@ export interface ListEntity<
|
|
|
149
158
|
removeFirst(item: ListItemValue<Value>): void;
|
|
150
159
|
removeLast(item: ListItemValue<Value>): void;
|
|
151
160
|
map<U>(callback: (value: ListItemValue<Value>, index: number) => U): U[];
|
|
161
|
+
reduce<U>(
|
|
162
|
+
callback: (
|
|
163
|
+
accumulator: U,
|
|
164
|
+
currentValue: ListItemValue<Value>,
|
|
165
|
+
index: number,
|
|
166
|
+
) => U,
|
|
167
|
+
initialValue: U,
|
|
168
|
+
): U;
|
|
152
169
|
filter(
|
|
153
170
|
callback: (value: ListItemValue<Value>, index: number) => boolean,
|
|
154
171
|
): ListItemValue<Value>[];
|