@verdant-web/store 5.4.0 → 5.4.3

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 (84) hide show
  1. package/dist/bundle/index.js +8 -8
  2. package/dist/bundle/index.js.map +4 -4
  3. package/dist/esm/__tests__/queries.test.js +16 -0
  4. package/dist/esm/__tests__/queries.test.js.map +1 -1
  5. package/dist/esm/client/Client.js +12 -4
  6. package/dist/esm/client/Client.js.map +1 -1
  7. package/dist/esm/entities/Entity.d.ts +2 -5
  8. package/dist/esm/entities/Entity.js +17 -10
  9. package/dist/esm/entities/Entity.js.map +1 -1
  10. package/dist/esm/entities/EntityMetadata.js +14 -2
  11. package/dist/esm/entities/EntityMetadata.js.map +1 -1
  12. package/dist/esm/entities/EntityStore.js +1 -0
  13. package/dist/esm/entities/EntityStore.js.map +1 -1
  14. package/dist/esm/persistence/MessageCreator.js +1 -1
  15. package/dist/esm/persistence/MessageCreator.js.map +1 -1
  16. package/dist/esm/persistence/PersistenceMetadata.d.ts +6 -1
  17. package/dist/esm/persistence/PersistenceMetadata.js +40 -2
  18. package/dist/esm/persistence/PersistenceMetadata.js.map +1 -1
  19. package/dist/esm/persistence/PersistenceRebaser.js +1 -1
  20. package/dist/esm/persistence/PersistenceRebaser.js.map +1 -1
  21. package/dist/esm/persistence/idb/IdbService.js +1 -1
  22. package/dist/esm/persistence/idb/IdbService.js.map +1 -1
  23. package/dist/esm/persistence/idb/indexNames.d.ts +5 -0
  24. package/dist/esm/persistence/idb/indexNames.js +6 -0
  25. package/dist/esm/persistence/idb/indexNames.js.map +1 -0
  26. package/dist/esm/persistence/idb/metadata/IdbMetadataDb.d.ts +6 -3
  27. package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js +33 -8
  28. package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js.map +1 -1
  29. package/dist/esm/persistence/idb/metadata/openMetadataDatabase.js +5 -5
  30. package/dist/esm/persistence/idb/metadata/openMetadataDatabase.js.map +1 -1
  31. package/dist/esm/persistence/idb/queries/ranges.js +3 -4
  32. package/dist/esm/persistence/idb/queries/ranges.js.map +1 -1
  33. package/dist/esm/persistence/interfaces.d.ts +2 -0
  34. package/dist/esm/persistence/migration/engine.js +3 -4
  35. package/dist/esm/persistence/migration/engine.js.map +1 -1
  36. package/dist/esm/queries/BaseQuery.d.ts +8 -0
  37. package/dist/esm/queries/BaseQuery.js +49 -5
  38. package/dist/esm/queries/BaseQuery.js.map +1 -1
  39. package/dist/esm/queries/FindAllQuery.js +3 -3
  40. package/dist/esm/queries/FindAllQuery.js.map +1 -1
  41. package/dist/esm/queries/FindInfiniteQuery.js +6 -6
  42. package/dist/esm/queries/FindInfiniteQuery.js.map +1 -1
  43. package/dist/esm/queries/FindOneQuery.js +3 -3
  44. package/dist/esm/queries/FindOneQuery.js.map +1 -1
  45. package/dist/esm/queries/FindPageQuery.js +3 -3
  46. package/dist/esm/queries/FindPageQuery.js.map +1 -1
  47. package/dist/esm/queries/GetQuery.js +1 -1
  48. package/dist/esm/queries/GetQuery.js.map +1 -1
  49. package/dist/esm/queries/QueryCache.d.ts +4 -1
  50. package/dist/esm/queries/QueryCache.js +15 -1
  51. package/dist/esm/queries/QueryCache.js.map +1 -1
  52. package/dist/esm/queries/diagnostics.d.ts +24 -0
  53. package/dist/esm/queries/diagnostics.js +11 -0
  54. package/dist/esm/queries/diagnostics.js.map +1 -0
  55. package/dist/esm/queries/ranges.js +3 -4
  56. package/dist/esm/queries/ranges.js.map +1 -1
  57. package/dist/esm/sync/WebSocketSync.js +4 -2
  58. package/dist/esm/sync/WebSocketSync.js.map +1 -1
  59. package/package.json +7 -2
  60. package/src/__tests__/queries.test.ts +19 -0
  61. package/src/client/Client.ts +15 -4
  62. package/src/entities/Entity.ts +31 -22
  63. package/src/entities/EntityMetadata.ts +15 -3
  64. package/src/entities/EntityStore.ts +1 -0
  65. package/src/persistence/MessageCreator.ts +1 -1
  66. package/src/persistence/PersistenceMetadata.ts +67 -7
  67. package/src/persistence/PersistenceRebaser.ts +1 -1
  68. package/src/persistence/idb/IdbService.ts +1 -1
  69. package/src/persistence/idb/indexNames.ts +5 -0
  70. package/src/persistence/idb/metadata/IdbMetadataDb.ts +71 -14
  71. package/src/persistence/idb/metadata/openMetadataDatabase.ts +15 -7
  72. package/src/persistence/idb/queries/ranges.ts +2 -3
  73. package/src/persistence/interfaces.ts +5 -1
  74. package/src/persistence/migration/engine.ts +3 -4
  75. package/src/queries/BaseQuery.ts +56 -10
  76. package/src/queries/FindAllQuery.ts +10 -8
  77. package/src/queries/FindInfiniteQuery.ts +22 -18
  78. package/src/queries/FindOneQuery.ts +9 -7
  79. package/src/queries/FindPageQuery.ts +11 -9
  80. package/src/queries/GetQuery.ts +1 -1
  81. package/src/queries/QueryCache.ts +19 -2
  82. package/src/queries/diagnostics.ts +38 -0
  83. package/src/queries/ranges.ts +2 -3
  84. package/src/sync/WebSocketSync.ts +2 -0
@@ -27,18 +27,20 @@ export class FindAllQuery<T> extends BaseQuery<T[]> {
27
27
  }
28
28
 
29
29
  protected run = async () => {
30
- const { result: oids } = await (
31
- await this.context.documents
32
- ).findAllOids({
33
- collection: this.collection,
34
- index: this.index,
35
- limit: this.limit,
36
- });
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
+ );
37
37
  this.context.log(
38
38
  'debug',
39
39
  `FindAllQuery: ${oids.length} oids found: ${oids}`,
40
40
  );
41
- this.setValue(await Promise.all(oids.map(this.hydrate)));
41
+ this.setValue(
42
+ await this.measureHydration(() => Promise.all(oids.map(this.hydrate))),
43
+ );
42
44
  };
43
45
 
44
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
- ).findAllOids({
43
- collection: this.collection,
44
- limit: this._pageSize * this._upToPage,
45
- offset: 0,
46
- index: this.index,
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(await Promise.all(result.map(this.hydrate)));
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
- ).findAllOids({
56
- collection: this.collection,
57
- limit: this._pageSize,
58
- offset: this._pageSize * this._upToPage,
59
- index: this.index,
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 Promise.all(result.map(this.hydrate))),
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
- ).findOneOid({
29
- collection: this.collection,
30
- index: this.index,
31
- });
32
- this.setValue(oid ? await this.hydrate(oid) : null);
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
- ).findAllOids({
54
- collection: this.collection,
55
- index: this.index,
56
- limit: this._pageSize,
57
- offset: this._page * this._pageSize,
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(await Promise.all(result.map(this.hydrate)));
60
+ this.setValue(
61
+ await this.measureHydration(() => Promise.all(result.map(this.hydrate))),
62
+ );
61
63
  };
62
64
 
63
65
  nextPage = async () => {
@@ -22,7 +22,7 @@ export class GetQuery<T> extends BaseQuery<T | null> {
22
22
  }
23
23
 
24
24
  protected run = async () => {
25
- const value = await this.hydrate(this.oid);
25
+ const value = await this.measureHydration(() => this.hydrate(this.oid));
26
26
  this.setValue(value);
27
27
  };
28
28
  }
@@ -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
- dropAll = () => {
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) => query.dispose());
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,38 @@
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 QueryRun = QueryTiming & {
11
+ startedAt: number;
12
+ };
13
+
14
+ export type QueryDiagnostic = {
15
+ key: string;
16
+ startedAt: number | null;
17
+ collection: string;
18
+ type: string;
19
+ status: QueryStatus;
20
+ active: boolean;
21
+ result: unknown;
22
+ timing: QueryTiming;
23
+ runs: QueryRun[];
24
+ };
25
+
26
+ export type QueryDiagnostics = {
27
+ queries: QueryDiagnostic[];
28
+ };
29
+
30
+ export const getDiagnosticResult = (value: unknown): unknown => {
31
+ if (Array.isArray(value)) {
32
+ return value.map(getDiagnosticResult);
33
+ }
34
+ if (value instanceof Entity) {
35
+ return value.getSnapshot();
36
+ }
37
+ return value;
38
+ };
@@ -46,15 +46,14 @@ const rangeIndexToIdbKeyRange = (filter: RangeCollectionIndexFilter) => {
46
46
  };
47
47
 
48
48
  const compoundIndexToIdbKeyRange = (
49
- // FIXME:
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
  }