@verdant-web/store 5.3.0 → 5.4.2
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 +8 -8
- package/dist/bundle/index.js.map +4 -4
- package/dist/esm/__tests__/queries.test.js +35 -0
- package/dist/esm/__tests__/queries.test.js.map +1 -1
- package/dist/esm/client/Client.js +12 -4
- package/dist/esm/client/Client.js.map +1 -1
- package/dist/esm/entities/Entity.d.ts +2 -5
- package/dist/esm/entities/Entity.js +17 -10
- package/dist/esm/entities/Entity.js.map +1 -1
- package/dist/esm/entities/EntityMetadata.js +14 -2
- package/dist/esm/entities/EntityMetadata.js.map +1 -1
- package/dist/esm/entities/EntityStore.js +1 -0
- package/dist/esm/entities/EntityStore.js.map +1 -1
- package/dist/esm/persistence/MessageCreator.js +1 -1
- package/dist/esm/persistence/MessageCreator.js.map +1 -1
- package/dist/esm/persistence/PersistenceMetadata.d.ts +6 -1
- package/dist/esm/persistence/PersistenceMetadata.js +40 -2
- package/dist/esm/persistence/PersistenceMetadata.js.map +1 -1
- package/dist/esm/persistence/PersistenceRebaser.js +1 -1
- package/dist/esm/persistence/PersistenceRebaser.js.map +1 -1
- package/dist/esm/persistence/idb/IdbService.js +1 -1
- package/dist/esm/persistence/idb/IdbService.js.map +1 -1
- package/dist/esm/persistence/idb/indexNames.d.ts +5 -0
- package/dist/esm/persistence/idb/indexNames.js +6 -0
- package/dist/esm/persistence/idb/indexNames.js.map +1 -0
- package/dist/esm/persistence/idb/metadata/IdbMetadataDb.d.ts +6 -3
- package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js +33 -8
- package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js.map +1 -1
- package/dist/esm/persistence/idb/metadata/openMetadataDatabase.js +5 -5
- package/dist/esm/persistence/idb/metadata/openMetadataDatabase.js.map +1 -1
- package/dist/esm/persistence/idb/queries/ranges.js +3 -4
- package/dist/esm/persistence/idb/queries/ranges.js.map +1 -1
- package/dist/esm/persistence/interfaces.d.ts +2 -0
- package/dist/esm/persistence/migration/engine.js +3 -4
- package/dist/esm/persistence/migration/engine.js.map +1 -1
- package/dist/esm/queries/BaseQuery.d.ts +6 -0
- package/dist/esm/queries/BaseQuery.js +39 -5
- package/dist/esm/queries/BaseQuery.js.map +1 -1
- package/dist/esm/queries/CollectionQueries.d.ts +8 -7
- package/dist/esm/queries/CollectionQueries.js +8 -6
- package/dist/esm/queries/CollectionQueries.js.map +1 -1
- package/dist/esm/queries/FindAllQuery.d.ts +3 -1
- package/dist/esm/queries/FindAllQuery.js +6 -4
- package/dist/esm/queries/FindAllQuery.js.map +1 -1
- package/dist/esm/queries/FindInfiniteQuery.js +6 -6
- package/dist/esm/queries/FindInfiniteQuery.js.map +1 -1
- package/dist/esm/queries/FindOneQuery.js +3 -3
- package/dist/esm/queries/FindOneQuery.js.map +1 -1
- package/dist/esm/queries/FindPageQuery.js +3 -3
- package/dist/esm/queries/FindPageQuery.js.map +1 -1
- package/dist/esm/queries/GetQuery.js +1 -1
- package/dist/esm/queries/GetQuery.js.map +1 -1
- package/dist/esm/queries/QueryCache.d.ts +4 -1
- package/dist/esm/queries/QueryCache.js +15 -1
- package/dist/esm/queries/QueryCache.js.map +1 -1
- package/dist/esm/queries/diagnostics.d.ts +19 -0
- package/dist/esm/queries/diagnostics.js +11 -0
- package/dist/esm/queries/diagnostics.js.map +1 -0
- package/dist/esm/queries/ranges.js +3 -4
- package/dist/esm/queries/ranges.js.map +1 -1
- package/dist/esm/sync/WebSocketSync.js +4 -2
- package/dist/esm/sync/WebSocketSync.js.map +1 -1
- package/package.json +7 -2
- package/src/__tests__/queries.test.ts +42 -0
- package/src/client/Client.ts +15 -4
- package/src/entities/Entity.ts +31 -22
- package/src/entities/EntityMetadata.ts +15 -3
- package/src/entities/EntityStore.ts +1 -0
- package/src/persistence/MessageCreator.ts +1 -1
- package/src/persistence/PersistenceMetadata.ts +67 -7
- package/src/persistence/PersistenceRebaser.ts +1 -1
- package/src/persistence/idb/IdbService.ts +1 -1
- package/src/persistence/idb/indexNames.ts +5 -0
- package/src/persistence/idb/metadata/IdbMetadataDb.ts +71 -14
- package/src/persistence/idb/metadata/openMetadataDatabase.ts +15 -7
- package/src/persistence/idb/queries/ranges.ts +2 -3
- package/src/persistence/interfaces.ts +5 -1
- package/src/persistence/migration/engine.ts +3 -4
- package/src/queries/BaseQuery.ts +41 -5
- package/src/queries/CollectionQueries.ts +12 -9
- package/src/queries/FindAllQuery.ts +14 -7
- package/src/queries/FindInfiniteQuery.ts +22 -18
- package/src/queries/FindOneQuery.ts +9 -7
- package/src/queries/FindPageQuery.ts +11 -9
- package/src/queries/GetQuery.ts +1 -1
- package/src/queries/QueryCache.ts +19 -2
- package/src/queries/diagnostics.ts +32 -0
- package/src/queries/ranges.ts +2 -3
- package/src/sync/WebSocketSync.ts +2 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { replaceLegacyOidsInObject } from '@verdant-web/common';
|
|
2
|
-
import { getMetadataDbName } from '../util.js';
|
|
3
2
|
import { Context } from '../../../context/context.js';
|
|
3
|
+
import { OPERATION_INDEX_NAMES } from '../indexNames.js';
|
|
4
|
+
import { getMetadataDbName } from '../util.js';
|
|
4
5
|
|
|
5
6
|
const migrations = [version1, version2, version3, version4, version5, version6];
|
|
6
7
|
export const CURRENT_METADATA_VERSION = migrations.length;
|
|
@@ -55,7 +56,7 @@ async function version1(db: IDBDatabase, tx: IDBTransaction) {
|
|
|
55
56
|
keyPath: 'oid',
|
|
56
57
|
});
|
|
57
58
|
const operationsStore = db.createObjectStore('operations', {
|
|
58
|
-
keyPath:
|
|
59
|
+
keyPath: OPERATION_INDEX_NAMES.OID__TIMESTAMP,
|
|
59
60
|
});
|
|
60
61
|
const infoStore = db.createObjectStore('info', { keyPath: 'type' });
|
|
61
62
|
baselinesStore.createIndex('timestamp', 'timestamp');
|
|
@@ -84,8 +85,8 @@ async function version2(db: IDBDatabase, tx: IDBTransaction) {
|
|
|
84
85
|
cursor.value;
|
|
85
86
|
cursor.update({
|
|
86
87
|
...value,
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
[OPERATION_INDEX_NAMES.IS_LOCAL__TIMESTAMP]: isLocal_timestamp,
|
|
89
|
+
[OPERATION_INDEX_NAMES.ROOT_OID__TIMESTAMP]: documentOid_timestamp,
|
|
89
90
|
});
|
|
90
91
|
cursor.continue();
|
|
91
92
|
} else {
|
|
@@ -100,9 +101,16 @@ async function version2(db: IDBDatabase, tx: IDBTransaction) {
|
|
|
100
101
|
operations.deleteIndex('isLocal_timestamp');
|
|
101
102
|
operations.deleteIndex('documentOid_timestamp');
|
|
102
103
|
// create the new indexes
|
|
103
|
-
operations.createIndex(
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
operations.createIndex(
|
|
105
|
+
OPERATION_INDEX_NAMES.IS_LOCAL__TIMESTAMP,
|
|
106
|
+
OPERATION_INDEX_NAMES.IS_LOCAL__TIMESTAMP,
|
|
107
|
+
{ unique: false },
|
|
108
|
+
);
|
|
109
|
+
operations.createIndex(
|
|
110
|
+
OPERATION_INDEX_NAMES.ROOT_OID__TIMESTAMP,
|
|
111
|
+
OPERATION_INDEX_NAMES.ROOT_OID__TIMESTAMP,
|
|
112
|
+
{ unique: false },
|
|
113
|
+
);
|
|
106
114
|
}
|
|
107
115
|
|
|
108
116
|
/**
|
|
@@ -46,15 +46,14 @@ const rangeIndexToIdbKeyRange = (filter: RangeCollectionIndexFilter) => {
|
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
const compoundIndexToIdbKeyRange = (
|
|
49
|
-
|
|
50
|
-
schema: any,
|
|
49
|
+
schema: StorageSchema,
|
|
51
50
|
collection: string,
|
|
52
51
|
filter: CollectionCompoundIndexFilter,
|
|
53
52
|
) => {
|
|
54
53
|
// validate the usage of the compound index:
|
|
55
54
|
// - all match fields must be contiguous at the start of the compound order
|
|
56
55
|
const indexDefinition =
|
|
57
|
-
schema.collections[collection].compounds[filter.where];
|
|
56
|
+
schema.collections[collection].compounds?.[filter.where];
|
|
58
57
|
assert(
|
|
59
58
|
indexDefinition,
|
|
60
59
|
`Index ${filter.where} does not exist on collection ${collection}`,
|
|
@@ -105,7 +105,7 @@ export interface PersistenceMetadataDb<
|
|
|
105
105
|
iterateEntityOperations(
|
|
106
106
|
oid: string,
|
|
107
107
|
iterator: Iterator<ClientOperation>,
|
|
108
|
-
opts?: CommonQueryOptions<Tx> & { to?: string | null },
|
|
108
|
+
opts?: CommonQueryOptions<Tx> & { to?: string | null; reverse?: boolean },
|
|
109
109
|
): Promise<void>;
|
|
110
110
|
iterateCollectionOperations(
|
|
111
111
|
collection: string,
|
|
@@ -138,6 +138,10 @@ export interface PersistenceMetadataDb<
|
|
|
138
138
|
ops: ClientOperation[],
|
|
139
139
|
opts?: CommonQueryOptions<Tx>,
|
|
140
140
|
): Promise<ObjectIdentifier[]>;
|
|
141
|
+
purgeDocumentData(
|
|
142
|
+
oid: ObjectIdentifier,
|
|
143
|
+
opts?: CommonQueryOptions<Tx>,
|
|
144
|
+
): Promise<void>;
|
|
141
145
|
|
|
142
146
|
/* WARNING: deletes all data */
|
|
143
147
|
reset(opts?: { clearReplica?: boolean; transaction?: Tx }): Promise<void>;
|
|
@@ -174,10 +174,9 @@ export async function getMigrationEngine({
|
|
|
174
174
|
!!rootOid,
|
|
175
175
|
`Document is missing an OID: ${JSON.stringify(doc)}`,
|
|
176
176
|
);
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
// lower to retain access to lower level data here?
|
|
177
|
+
// while querying again for data we already discarded in the findAll
|
|
178
|
+
// above isn't ideal, in practice the get authz method is
|
|
179
|
+
// fairly quick because it short-circuits.
|
|
181
180
|
const authz = await meta.getDocumentAuthz(rootOid);
|
|
182
181
|
const original = cloneDeep(doc);
|
|
183
182
|
// @ts-ignore - excessive type resolution
|
package/src/queries/BaseQuery.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { EventSubscriber } from '@verdant-web/common';
|
|
|
2
2
|
import { Context } from '../context/context.js';
|
|
3
3
|
import { Entity } from '../entities/Entity.js';
|
|
4
4
|
import { Disposable } from '../utils/Disposable.js';
|
|
5
|
+
import { getDiagnosticResult, QueryDiagnostic, QueryTiming } from './diagnostics.js';
|
|
5
6
|
import { filterResultSet } from './utils.js';
|
|
6
7
|
|
|
7
8
|
export type BaseQueryEvents = {
|
|
@@ -37,12 +38,18 @@ export abstract class BaseQuery<T> extends Disposable {
|
|
|
37
38
|
private _allUnsubscribedHandler?: (query: BaseQuery<T>) => void;
|
|
38
39
|
private _status: QueryStatus = 'initial';
|
|
39
40
|
private _executionPromise: Promise<T> | null = null;
|
|
41
|
+
private _timing: QueryTiming = {
|
|
42
|
+
sweep: null,
|
|
43
|
+
hydration: null,
|
|
44
|
+
total: null,
|
|
45
|
+
};
|
|
40
46
|
|
|
41
47
|
protected context;
|
|
42
48
|
|
|
43
49
|
readonly collection;
|
|
44
50
|
readonly key;
|
|
45
51
|
readonly isListQuery;
|
|
52
|
+
readonly type;
|
|
46
53
|
|
|
47
54
|
constructor({
|
|
48
55
|
initial,
|
|
@@ -65,6 +72,7 @@ export abstract class BaseQuery<T> extends Disposable {
|
|
|
65
72
|
this.context = context;
|
|
66
73
|
this.key = key;
|
|
67
74
|
this.collection = collection;
|
|
75
|
+
this.type = this.constructor.name;
|
|
68
76
|
const shouldUpdateFn =
|
|
69
77
|
shouldUpdate ||
|
|
70
78
|
((collections: string[]) => collections.includes(collection));
|
|
@@ -223,10 +231,9 @@ export abstract class BaseQuery<T> extends Disposable {
|
|
|
223
231
|
};
|
|
224
232
|
|
|
225
233
|
execute = () => {
|
|
226
|
-
const startTime =
|
|
234
|
+
const startTime = performance.now();
|
|
227
235
|
this.context.log(
|
|
228
236
|
'debug',
|
|
229
|
-
`[${startTime.toLocaleTimeString()}]`,
|
|
230
237
|
'Executing query',
|
|
231
238
|
this.key,
|
|
232
239
|
);
|
|
@@ -255,11 +262,10 @@ export abstract class BaseQuery<T> extends Disposable {
|
|
|
255
262
|
}
|
|
256
263
|
})
|
|
257
264
|
.finally(() => {
|
|
258
|
-
const
|
|
259
|
-
|
|
265
|
+
const duration = performance.now() - startTime;
|
|
266
|
+
this._timing.total = duration;
|
|
260
267
|
this.context.log(
|
|
261
268
|
'debug',
|
|
262
|
-
`[${endTime.toLocaleTimeString()}]`,
|
|
263
269
|
'Query executed',
|
|
264
270
|
this.key,
|
|
265
271
|
`Duration: ${duration}ms`,
|
|
@@ -267,6 +273,36 @@ export abstract class BaseQuery<T> extends Disposable {
|
|
|
267
273
|
});
|
|
268
274
|
return this._executionPromise;
|
|
269
275
|
};
|
|
276
|
+
|
|
277
|
+
protected measureSweep = async <V>(work: () => Promise<V>): Promise<V> => {
|
|
278
|
+
const start = performance.now();
|
|
279
|
+
try {
|
|
280
|
+
return await work();
|
|
281
|
+
} finally {
|
|
282
|
+
this._timing.sweep = performance.now() - start;
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
protected measureHydration = async <V>(
|
|
287
|
+
work: () => Promise<V>,
|
|
288
|
+
): Promise<V> => {
|
|
289
|
+
const start = performance.now();
|
|
290
|
+
try {
|
|
291
|
+
return await work();
|
|
292
|
+
} finally {
|
|
293
|
+
this._timing.hydration = performance.now() - start;
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
getDiagnostic = (active: boolean): QueryDiagnostic => ({
|
|
298
|
+
key: this.key,
|
|
299
|
+
collection: this.collection,
|
|
300
|
+
type: this.type,
|
|
301
|
+
status: this.status,
|
|
302
|
+
active,
|
|
303
|
+
result: getDiagnosticResult(this.current),
|
|
304
|
+
timing: { ...this._timing },
|
|
305
|
+
});
|
|
270
306
|
protected abstract run(): Promise<void>;
|
|
271
307
|
|
|
272
308
|
[ON_ALL_UNSUBSCRIBED] = (handler: (query: BaseQuery<T>) => void) => {
|
|
@@ -4,16 +4,16 @@ import {
|
|
|
4
4
|
hashObject,
|
|
5
5
|
} from '@verdant-web/common';
|
|
6
6
|
import { Context } from '../context/context.js';
|
|
7
|
-
import { EntityStore } from '../entities/EntityStore.js';
|
|
8
|
-
import { GetQuery } from './GetQuery.js';
|
|
9
|
-
import { QueryCache } from './QueryCache.js';
|
|
10
|
-
import { FindOneQuery } from './FindOneQuery.js';
|
|
11
|
-
import { FindPageQuery } from './FindPageQuery.js';
|
|
12
|
-
import { FindInfiniteQuery } from './FindInfiniteQuery.js';
|
|
13
|
-
import { FindAllQuery } from './FindAllQuery.js';
|
|
14
7
|
import { DocumentManager } from '../entities/DocumentManager.js';
|
|
8
|
+
import { EntityStore } from '../entities/EntityStore.js';
|
|
15
9
|
import { ObjectEntity } from '../index.js';
|
|
16
10
|
import { UPDATE } from './BaseQuery.js';
|
|
11
|
+
import { FindAllQuery } from './FindAllQuery.js';
|
|
12
|
+
import { FindInfiniteQuery } from './FindInfiniteQuery.js';
|
|
13
|
+
import { FindOneQuery } from './FindOneQuery.js';
|
|
14
|
+
import { FindPageQuery } from './FindPageQuery.js';
|
|
15
|
+
import { GetQuery } from './GetQuery.js';
|
|
16
|
+
import { QueryCache } from './QueryCache.js';
|
|
17
17
|
|
|
18
18
|
export class CollectionQueries<
|
|
19
19
|
T extends ObjectEntity<any, any>,
|
|
@@ -123,9 +123,11 @@ export class CollectionQueries<
|
|
|
123
123
|
findAll = ({
|
|
124
124
|
index,
|
|
125
125
|
key: providedKey,
|
|
126
|
-
|
|
126
|
+
limit,
|
|
127
|
+
}: { index?: Filter; key?: string; limit?: number } = {}) => {
|
|
127
128
|
const key =
|
|
128
|
-
providedKey ||
|
|
129
|
+
providedKey ||
|
|
130
|
+
`findAll:${this.collection}:${limit}:${this.serializeIndex(index)}`;
|
|
129
131
|
return this.cache.getOrSet(
|
|
130
132
|
key,
|
|
131
133
|
() =>
|
|
@@ -135,6 +137,7 @@ export class CollectionQueries<
|
|
|
135
137
|
hydrate: this.hydrate,
|
|
136
138
|
context: this.context,
|
|
137
139
|
key,
|
|
140
|
+
limit,
|
|
138
141
|
}),
|
|
139
142
|
(existing) => {
|
|
140
143
|
existing[UPDATE](index);
|
|
@@ -5,35 +5,42 @@ import { areIndexesEqual } from './utils.js';
|
|
|
5
5
|
export class FindAllQuery<T> extends BaseQuery<T[]> {
|
|
6
6
|
private index;
|
|
7
7
|
private hydrate;
|
|
8
|
+
private limit;
|
|
8
9
|
|
|
9
10
|
constructor({
|
|
10
11
|
index,
|
|
11
12
|
hydrate,
|
|
13
|
+
limit,
|
|
12
14
|
...rest
|
|
13
15
|
}: {
|
|
14
16
|
index?: CollectionFilter;
|
|
15
17
|
hydrate: (oid: string) => Promise<T>;
|
|
18
|
+
limit?: number;
|
|
16
19
|
} & Omit<BaseQueryOptions<T[]>, 'initial'>) {
|
|
17
20
|
super({
|
|
18
21
|
initial: [],
|
|
19
22
|
...rest,
|
|
20
23
|
});
|
|
24
|
+
this.limit = limit;
|
|
21
25
|
this.index = index;
|
|
22
26
|
this.hydrate = hydrate;
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
protected run = async () => {
|
|
26
|
-
const { result: oids } = await (
|
|
27
|
-
await this.context.documents
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
const { result: oids } = await this.measureSweep(async () =>
|
|
31
|
+
(await this.context.documents).findAllOids({
|
|
32
|
+
collection: this.collection,
|
|
33
|
+
index: this.index,
|
|
34
|
+
limit: this.limit,
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
32
37
|
this.context.log(
|
|
33
38
|
'debug',
|
|
34
39
|
`FindAllQuery: ${oids.length} oids found: ${oids}`,
|
|
35
40
|
);
|
|
36
|
-
this.setValue(
|
|
41
|
+
this.setValue(
|
|
42
|
+
await this.measureHydration(() => Promise.all(oids.map(this.hydrate))),
|
|
43
|
+
);
|
|
37
44
|
};
|
|
38
45
|
|
|
39
46
|
[UPDATE] = (index: CollectionFilter | undefined) => {
|
|
@@ -37,32 +37,36 @@ export class FindInfiniteQuery<T> extends BaseQuery<T[]> {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
protected run = async () => {
|
|
40
|
-
const { result, hasNextPage } = await (
|
|
41
|
-
await this.context.documents
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
40
|
+
const { result, hasNextPage } = await this.measureSweep(async () =>
|
|
41
|
+
(await this.context.documents).findAllOids({
|
|
42
|
+
collection: this.collection,
|
|
43
|
+
limit: this._pageSize * this._upToPage,
|
|
44
|
+
offset: 0,
|
|
45
|
+
index: this.index,
|
|
46
|
+
}),
|
|
47
|
+
);
|
|
48
48
|
this._hasNextPage = hasNextPage;
|
|
49
|
-
this.setValue(
|
|
49
|
+
this.setValue(
|
|
50
|
+
await this.measureHydration(() => Promise.all(result.map(this.hydrate))),
|
|
51
|
+
);
|
|
50
52
|
};
|
|
51
53
|
|
|
52
54
|
public loadMore = async () => {
|
|
53
|
-
const { result, hasNextPage } = await (
|
|
54
|
-
await this.context.documents
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
const { result, hasNextPage } = await this.measureSweep(async () =>
|
|
56
|
+
(await this.context.documents).findAllOids({
|
|
57
|
+
collection: this.collection,
|
|
58
|
+
limit: this._pageSize,
|
|
59
|
+
offset: this._pageSize * this._upToPage,
|
|
60
|
+
index: this.index,
|
|
61
|
+
}),
|
|
62
|
+
);
|
|
61
63
|
this._hasNextPage = hasNextPage;
|
|
62
64
|
this._upToPage++;
|
|
63
65
|
this.setValue([
|
|
64
66
|
...this.current,
|
|
65
|
-
...(await
|
|
67
|
+
...(await this.measureHydration(() =>
|
|
68
|
+
Promise.all(result.map(this.hydrate)),
|
|
69
|
+
)),
|
|
66
70
|
]);
|
|
67
71
|
};
|
|
68
72
|
|
|
@@ -23,13 +23,15 @@ export class FindOneQuery<T> extends BaseQuery<T | null> {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
protected run = async () => {
|
|
26
|
-
const oid = await (
|
|
27
|
-
await this.context.documents
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
this.setValue(
|
|
26
|
+
const oid = await this.measureSweep(async () =>
|
|
27
|
+
(await this.context.documents).findOneOid({
|
|
28
|
+
collection: this.collection,
|
|
29
|
+
index: this.index,
|
|
30
|
+
}),
|
|
31
|
+
);
|
|
32
|
+
this.setValue(
|
|
33
|
+
oid ? await this.measureHydration(() => this.hydrate(oid)) : null,
|
|
34
|
+
);
|
|
33
35
|
};
|
|
34
36
|
|
|
35
37
|
[UPDATE] = (index: CollectionFilter | undefined) => {
|
|
@@ -48,16 +48,18 @@ export class FindPageQuery<T> extends BaseQuery<T[]> {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
protected run = async () => {
|
|
51
|
-
const { result, hasNextPage } = await (
|
|
52
|
-
await this.context.documents
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
51
|
+
const { result, hasNextPage } = await this.measureSweep(async () =>
|
|
52
|
+
(await this.context.documents).findAllOids({
|
|
53
|
+
collection: this.collection,
|
|
54
|
+
index: this.index,
|
|
55
|
+
limit: this._pageSize,
|
|
56
|
+
offset: this._page * this._pageSize,
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
59
59
|
this._hasNextPage = hasNextPage;
|
|
60
|
-
this.setValue(
|
|
60
|
+
this.setValue(
|
|
61
|
+
await this.measureHydration(() => Promise.all(result.map(this.hydrate))),
|
|
62
|
+
);
|
|
61
63
|
};
|
|
62
64
|
|
|
63
65
|
nextPage = async () => {
|
package/src/queries/GetQuery.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Context } from '../context/context.js';
|
|
2
2
|
import { Disposable } from '../utils/Disposable.js';
|
|
3
3
|
import { BaseQuery, ON_ALL_UNSUBSCRIBED } from './BaseQuery.js';
|
|
4
|
+
import { QueryDiagnostic, QueryDiagnostics } from './diagnostics.js';
|
|
4
5
|
|
|
5
6
|
export class QueryCache extends Disposable {
|
|
6
7
|
private _cache: Map<string, BaseQuery<any>> = new Map();
|
|
@@ -8,6 +9,7 @@ export class QueryCache extends Disposable {
|
|
|
8
9
|
private context;
|
|
9
10
|
/** A set of query keys to keep alive even if they unsubscribe */
|
|
10
11
|
private _holds = new Set<string>();
|
|
12
|
+
private _inactiveDiagnostics = new Map<string, QueryDiagnostic>();
|
|
11
13
|
|
|
12
14
|
constructor({
|
|
13
15
|
evictionTime = 5 * 1000,
|
|
@@ -38,6 +40,7 @@ export class QueryCache extends Disposable {
|
|
|
38
40
|
|
|
39
41
|
set<V extends BaseQuery<any>>(value: V) {
|
|
40
42
|
this._cache.set(value.key, value);
|
|
43
|
+
this._inactiveDiagnostics.delete(value.key);
|
|
41
44
|
value[ON_ALL_UNSUBSCRIBED](this.enqueueQueryEviction);
|
|
42
45
|
// immediately enqueue a check to see if this query should be evicted --
|
|
43
46
|
// this basically gives code X seconds to subscribe to the query before
|
|
@@ -47,6 +50,15 @@ export class QueryCache extends Disposable {
|
|
|
47
50
|
return value;
|
|
48
51
|
}
|
|
49
52
|
|
|
53
|
+
get diagnostics(): QueryDiagnostics {
|
|
54
|
+
return {
|
|
55
|
+
queries: [
|
|
56
|
+
...this._cache.values().map((query) => query.getDiagnostic(true)),
|
|
57
|
+
...this._inactiveDiagnostics.values(),
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
50
62
|
getOrSet<V extends BaseQuery<any>>(
|
|
51
63
|
key: string,
|
|
52
64
|
create: () => V,
|
|
@@ -78,19 +90,23 @@ export class QueryCache extends Disposable {
|
|
|
78
90
|
// got a different version of this query.
|
|
79
91
|
if (this._cache.get(query.key) === query) {
|
|
80
92
|
this._cache.delete(query.key);
|
|
93
|
+
this._inactiveDiagnostics.set(query.key, query.getDiagnostic(false));
|
|
81
94
|
this.context.log('debug', 'QueryCache: evicted query', query.key);
|
|
82
95
|
}
|
|
83
96
|
}, this._evictionTime);
|
|
84
97
|
};
|
|
85
98
|
|
|
86
|
-
|
|
99
|
+
dropAll = () => {
|
|
87
100
|
this.context.log(
|
|
88
101
|
'debug',
|
|
89
102
|
'QueryCache: drop all',
|
|
90
103
|
this._cache.size,
|
|
91
104
|
'queries',
|
|
92
105
|
);
|
|
93
|
-
this._cache.forEach((query) =>
|
|
106
|
+
this._cache.forEach((query) => {
|
|
107
|
+
this._inactiveDiagnostics.set(query.key, query.getDiagnostic(false));
|
|
108
|
+
query.dispose();
|
|
109
|
+
});
|
|
94
110
|
this._cache.clear();
|
|
95
111
|
};
|
|
96
112
|
|
|
@@ -135,4 +151,5 @@ export type PublicQueryCacheAPI = Pick<
|
|
|
135
151
|
| 'keepAlives'
|
|
136
152
|
| 'forceRefreshAll'
|
|
137
153
|
| 'activeKeys'
|
|
154
|
+
| 'diagnostics'
|
|
138
155
|
>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Entity } from '../entities/Entity.js';
|
|
2
|
+
import type { QueryStatus } from './BaseQuery.js';
|
|
3
|
+
|
|
4
|
+
export type QueryTiming = {
|
|
5
|
+
sweep: number | null;
|
|
6
|
+
hydration: number | null;
|
|
7
|
+
total: number | null;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type QueryDiagnostic = {
|
|
11
|
+
key: string;
|
|
12
|
+
collection: string;
|
|
13
|
+
type: string;
|
|
14
|
+
status: QueryStatus;
|
|
15
|
+
active: boolean;
|
|
16
|
+
result: unknown;
|
|
17
|
+
timing: QueryTiming;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type QueryDiagnostics = {
|
|
21
|
+
queries: QueryDiagnostic[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const getDiagnosticResult = (value: unknown): unknown => {
|
|
25
|
+
if (Array.isArray(value)) {
|
|
26
|
+
return value.map(getDiagnosticResult);
|
|
27
|
+
}
|
|
28
|
+
if (value instanceof Entity) {
|
|
29
|
+
return value.getSnapshot();
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
};
|
package/src/queries/ranges.ts
CHANGED
|
@@ -46,15 +46,14 @@ const rangeIndexToIdbKeyRange = (filter: RangeCollectionIndexFilter) => {
|
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
const compoundIndexToIdbKeyRange = (
|
|
49
|
-
|
|
50
|
-
schema: any,
|
|
49
|
+
schema: StorageSchema,
|
|
51
50
|
collection: string,
|
|
52
51
|
filter: CollectionCompoundIndexFilter,
|
|
53
52
|
) => {
|
|
54
53
|
// validate the usage of the compound index:
|
|
55
54
|
// - all match fields must be contiguous at the start of the compound order
|
|
56
55
|
const indexDefinition =
|
|
57
|
-
schema.collections[collection].compounds[filter.where];
|
|
56
|
+
schema.collections[collection].compounds?.[filter.where];
|
|
58
57
|
assert(
|
|
59
58
|
indexDefinition,
|
|
60
59
|
`Index ${filter.where} does not exist on collection ${collection}`,
|
|
@@ -314,6 +314,8 @@ export class WebSocketSync
|
|
|
314
314
|
await this.sendDisconnecting();
|
|
315
315
|
this.socket?.removeEventListener('message', this.onMessage);
|
|
316
316
|
this.socket?.removeEventListener('close', this.onClose);
|
|
317
|
+
this.socket?.removeEventListener('error', this.onError);
|
|
318
|
+
this.socket?.removeEventListener('open', this.onOpen);
|
|
317
319
|
if (this.socket?.readyState === WEBSOCKET_OPEN) {
|
|
318
320
|
this.socket.close();
|
|
319
321
|
}
|