@livestore/livestore 0.0.19 → 0.0.22

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 (136) hide show
  1. package/README.md +29 -22
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/QueryCache.d.ts +1 -1
  4. package/dist/QueryCache.d.ts.map +1 -1
  5. package/dist/QueryCache.js.map +1 -1
  6. package/dist/__tests__/react/fixture.d.ts +5 -4
  7. package/dist/__tests__/react/fixture.d.ts.map +1 -1
  8. package/dist/__tests__/react/fixture.js +3 -5
  9. package/dist/__tests__/react/fixture.js.map +1 -1
  10. package/dist/__tests__/react/useComponentState.test.d.ts +2 -0
  11. package/dist/__tests__/react/useComponentState.test.d.ts.map +1 -0
  12. package/dist/__tests__/react/useComponentState.test.js +68 -0
  13. package/dist/__tests__/react/useComponentState.test.js.map +1 -0
  14. package/dist/__tests__/react/useLQuery.test.d.ts +2 -0
  15. package/dist/__tests__/react/useLQuery.test.d.ts.map +1 -0
  16. package/dist/__tests__/react/useLQuery.test.js +38 -0
  17. package/dist/__tests__/react/useLQuery.test.js.map +1 -0
  18. package/dist/__tests__/react/useLiveStoreComponent.test.js +4 -9
  19. package/dist/__tests__/react/useLiveStoreComponent.test.js.map +1 -1
  20. package/dist/__tests__/react/useQuery.test.d.ts +2 -0
  21. package/dist/__tests__/react/useQuery.test.d.ts.map +1 -0
  22. package/dist/__tests__/react/useQuery.test.js +33 -0
  23. package/dist/__tests__/react/useQuery.test.js.map +1 -0
  24. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.d.ts +2 -0
  25. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.d.ts.map +1 -0
  26. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.js +38 -0
  27. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.js.map +1 -0
  28. package/dist/__tests__/react/utils/stack-info.test.d.ts +2 -0
  29. package/dist/__tests__/react/utils/stack-info.test.d.ts.map +1 -0
  30. package/dist/__tests__/react/utils/stack-info.test.js +43 -0
  31. package/dist/__tests__/react/utils/stack-info.test.js.map +1 -0
  32. package/dist/__tests__/reactive.test.js +179 -93
  33. package/dist/__tests__/reactive.test.js.map +1 -1
  34. package/dist/__tests__/reactiveQueries/sql.test.d.ts +2 -0
  35. package/dist/__tests__/reactiveQueries/sql.test.d.ts.map +1 -0
  36. package/dist/__tests__/reactiveQueries/sql.test.js +337 -0
  37. package/dist/__tests__/reactiveQueries/sql.test.js.map +1 -0
  38. package/dist/inMemoryDatabase.d.ts +4 -3
  39. package/dist/inMemoryDatabase.d.ts.map +1 -1
  40. package/dist/inMemoryDatabase.js +3 -2
  41. package/dist/inMemoryDatabase.js.map +1 -1
  42. package/dist/index.d.ts +7 -5
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +4 -0
  45. package/dist/index.js.map +1 -1
  46. package/dist/react/index.d.ts +4 -3
  47. package/dist/react/index.d.ts.map +1 -1
  48. package/dist/react/index.js +3 -2
  49. package/dist/react/index.js.map +1 -1
  50. package/dist/react/useComponentState.d.ts +50 -0
  51. package/dist/react/useComponentState.d.ts.map +1 -0
  52. package/dist/react/useComponentState.js +240 -0
  53. package/dist/react/useComponentState.js.map +1 -0
  54. package/dist/react/useGlobalQuery.d.ts +3 -0
  55. package/dist/react/useGlobalQuery.d.ts.map +1 -0
  56. package/dist/react/useGlobalQuery.js +26 -0
  57. package/dist/react/useGlobalQuery.js.map +1 -0
  58. package/dist/react/useGraphQL.d.ts +3 -3
  59. package/dist/react/useGraphQL.d.ts.map +1 -1
  60. package/dist/react/useGraphQL.js +10 -8
  61. package/dist/react/useGraphQL.js.map +1 -1
  62. package/dist/react/useLiveStoreComponent.d.ts +6 -6
  63. package/dist/react/useLiveStoreComponent.d.ts.map +1 -1
  64. package/dist/react/useLiveStoreComponent.js +143 -99
  65. package/dist/react/useLiveStoreComponent.js.map +1 -1
  66. package/dist/react/useQuery.d.ts +2 -2
  67. package/dist/react/useQuery.d.ts.map +1 -1
  68. package/dist/react/useQuery.js +54 -30
  69. package/dist/react/useQuery.js.map +1 -1
  70. package/dist/react/useTemporaryQuery.d.ts +8 -0
  71. package/dist/react/useTemporaryQuery.d.ts.map +1 -0
  72. package/dist/react/useTemporaryQuery.js +19 -0
  73. package/dist/react/useTemporaryQuery.js.map +1 -0
  74. package/dist/react/utils/extractNamesFromStackTrace.d.ts +3 -0
  75. package/dist/react/utils/extractNamesFromStackTrace.d.ts.map +1 -0
  76. package/dist/react/utils/extractNamesFromStackTrace.js +40 -0
  77. package/dist/react/utils/extractNamesFromStackTrace.js.map +1 -0
  78. package/dist/react/utils/extractStackInfoFromStackTrace.d.ts +7 -0
  79. package/dist/react/utils/extractStackInfoFromStackTrace.d.ts.map +1 -0
  80. package/dist/react/utils/extractStackInfoFromStackTrace.js +40 -0
  81. package/dist/react/utils/extractStackInfoFromStackTrace.js.map +1 -0
  82. package/dist/react/utils/stack-info.d.ts +11 -0
  83. package/dist/react/utils/stack-info.d.ts.map +1 -0
  84. package/dist/react/utils/stack-info.js +49 -0
  85. package/dist/react/utils/stack-info.js.map +1 -0
  86. package/dist/reactive.d.ts +51 -67
  87. package/dist/reactive.d.ts.map +1 -1
  88. package/dist/reactive.js +138 -220
  89. package/dist/reactive.js.map +1 -1
  90. package/dist/reactiveQueries/base-class.d.ts +28 -21
  91. package/dist/reactiveQueries/base-class.d.ts.map +1 -1
  92. package/dist/reactiveQueries/base-class.js +22 -18
  93. package/dist/reactiveQueries/base-class.js.map +1 -1
  94. package/dist/reactiveQueries/graph.d.ts +10 -0
  95. package/dist/reactiveQueries/graph.d.ts.map +1 -0
  96. package/dist/reactiveQueries/graph.js +6 -0
  97. package/dist/reactiveQueries/graph.js.map +1 -0
  98. package/dist/reactiveQueries/graphql.d.ts +35 -17
  99. package/dist/reactiveQueries/graphql.d.ts.map +1 -1
  100. package/dist/reactiveQueries/graphql.js +86 -10
  101. package/dist/reactiveQueries/graphql.js.map +1 -1
  102. package/dist/reactiveQueries/js.d.ts +17 -12
  103. package/dist/reactiveQueries/js.d.ts.map +1 -1
  104. package/dist/reactiveQueries/js.js +30 -8
  105. package/dist/reactiveQueries/js.js.map +1 -1
  106. package/dist/reactiveQueries/sql.d.ts +28 -18
  107. package/dist/reactiveQueries/sql.d.ts.map +1 -1
  108. package/dist/reactiveQueries/sql.js +79 -16
  109. package/dist/reactiveQueries/sql.js.map +1 -1
  110. package/dist/store.d.ts +35 -61
  111. package/dist/store.d.ts.map +1 -1
  112. package/dist/store.js +77 -272
  113. package/dist/store.js.map +1 -1
  114. package/package.json +4 -3
  115. package/src/QueryCache.ts +1 -1
  116. package/src/__tests__/react/fixture.tsx +10 -8
  117. package/src/__tests__/react/{useLiveStoreComponent.test.tsx → useComponentState.test.tsx} +9 -20
  118. package/src/__tests__/react/useQuery.test.tsx +48 -0
  119. package/src/__tests__/react/utils/stack-info.test.ts +45 -0
  120. package/src/__tests__/reactive.test.ts +212 -140
  121. package/src/__tests__/reactiveQueries/sql.test.ts +372 -0
  122. package/src/inMemoryDatabase.ts +11 -8
  123. package/src/index.ts +7 -11
  124. package/src/react/index.ts +4 -7
  125. package/src/react/{useLiveStoreComponent.ts → useComponentState.ts} +90 -253
  126. package/src/react/useQuery.ts +74 -40
  127. package/src/react/useTemporaryQuery.ts +23 -0
  128. package/src/react/utils/stack-info.ts +63 -0
  129. package/src/reactive.ts +234 -308
  130. package/src/reactiveQueries/base-class.ts +59 -42
  131. package/src/reactiveQueries/graph.ts +15 -0
  132. package/src/reactiveQueries/graphql.ts +143 -29
  133. package/src/reactiveQueries/js.ts +57 -20
  134. package/src/reactiveQueries/sql.ts +136 -36
  135. package/src/store.ts +121 -426
  136. package/src/react/useGraphQL.ts +0 -138
package/dist/store.d.ts CHANGED
@@ -1,20 +1,18 @@
1
- import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
2
1
  import * as otel from '@opentelemetry/api';
3
2
  import type { GraphQLSchema } from 'graphql';
4
3
  import type * as Sqlite from 'sqlite-esm';
5
- import type { ComponentKey } from './componentKey.js';
6
- import type { QueryDefinition } from './effect/LiveStore.js';
7
4
  import type { LiveStoreEvent } from './events.js';
8
5
  import { InMemoryDatabase } from './inMemoryDatabase.js';
9
- import type { Atom, Ref } from './reactive.js';
10
- import { ReactiveGraph } from './reactive.js';
11
- import { LiveStoreGraphQLQuery } from './reactiveQueries/graphql.js';
12
- import { LiveStoreJSQuery } from './reactiveQueries/js.js';
13
- import { LiveStoreSQLQuery } from './reactiveQueries/sql.js';
6
+ import type { StackInfo } from './react/utils/stack-info.js';
7
+ import type { ReactiveGraph, Ref } from './reactive.js';
8
+ import type { ILiveStoreQuery } from './reactiveQueries/base-class.js';
9
+ import { type DbContext } from './reactiveQueries/graph.js';
10
+ import type { LiveStoreGraphQLQuery } from './reactiveQueries/graphql.js';
11
+ import type { LiveStoreJSQuery } from './reactiveQueries/js.js';
12
+ import type { LiveStoreSQLQuery } from './reactiveQueries/sql.js';
14
13
  import type { GetActionArgs, Schema } from './schema.js';
15
14
  import type { Storage, StorageInit } from './storage/index.js';
16
- import type { Bindable, ParamsObject } from './util.js';
17
- export type GetAtomResult = <T>(atom: Atom<T> | LiveStoreJSQuery<T>) => T;
15
+ import type { ParamsObject } from './util.js';
18
16
  export type LiveStoreQuery<TResult extends Record<string, any> = any> = LiveStoreSQLQuery<TResult> | LiveStoreJSQuery<TResult> | LiveStoreGraphQLQuery<TResult, any, any>;
19
17
  export type BaseGraphQLContext = {
20
18
  queriedTables: Set<string>;
@@ -55,6 +53,14 @@ export type RefreshReason = {
55
53
  } | {
56
54
  _tag: 'makeThunk';
57
55
  label?: string;
56
+ } | {
57
+ _tag: 'react';
58
+ api: string;
59
+ label?: string;
60
+ stackInfo?: StackInfo;
61
+ } | {
62
+ _tag: 'manual';
63
+ label?: string;
58
64
  } | {
59
65
  _tag: 'unknown';
60
66
  };
@@ -62,6 +68,7 @@ export type QueryDebugInfo = {
62
68
  _tag: 'graphql' | 'sql' | 'js' | 'unknown';
63
69
  label: string;
64
70
  query: string;
71
+ durationMs: number;
65
72
  };
66
73
  export type StoreOtel = {
67
74
  tracer: otel.Tracer;
@@ -69,7 +76,7 @@ export type StoreOtel = {
69
76
  queriesSpanContext: otel.Context;
70
77
  };
71
78
  export declare class Store<TGraphQLContext extends BaseGraphQLContext = BaseGraphQLContext> {
72
- graph: ReactiveGraph<RefreshReason, QueryDebugInfo>;
79
+ graph: ReactiveGraph<RefreshReason, QueryDebugInfo, DbContext>;
73
80
  inMemoryDB: InMemoryDatabase;
74
81
  _proxyDb: InMemoryDatabase;
75
82
  schema: Schema;
@@ -81,70 +88,28 @@ export declare class Store<TGraphQLContext extends BaseGraphQLContext = BaseGrap
81
88
  * This only works in combination with `equal: () => false` which will always trigger a refresh.
82
89
  */
83
90
  tableRefs: {
84
- [key: string]: Ref<null>;
91
+ [key: string]: Ref<null, DbContext, RefreshReason>;
85
92
  };
86
- activeQueries: Set<LiveStoreQuery>;
93
+ /** RC-based set to see which queries are currently subscribed to */
94
+ activeQueries: ReferenceCountedSet<LiveStoreQuery>;
87
95
  storage?: Storage;
88
- temporaryQueries: Set<LiveStoreQuery> | undefined;
89
96
  private constructor();
90
97
  static createStore: <TGraphQLContext_1 extends BaseGraphQLContext>(storeOptions: StoreOptions<TGraphQLContext_1>, parentSpan: otel.Span) => Store<TGraphQLContext_1>;
91
- /**
92
- * Creates a reactive LiveStore SQL query
93
- *
94
- * NOTE The query is actually running (even if no one has subscribed to it yet) and will be kept up to date.
95
- */
96
- querySQL: <TResult>(genQueryString: string | ((get: GetAtomResult) => string), { queriedTables, bindValues, componentKey, label, otelContext, }: {
97
- /**
98
- * List of tables that are queried in this query;
99
- * used to determine reactive dependencies.
100
- *
101
- * NOTE In the future we want to auto-generate this via parsing the query
102
- */
103
- queriedTables: string[];
104
- bindValues?: Bindable | undefined;
105
- componentKey?: ComponentKey | undefined;
106
- label?: string | undefined;
107
- otelContext?: otel.Context | undefined;
108
- }) => LiveStoreSQLQuery<TResult>;
109
- queryJS: <TResult>(genResults: (get: GetAtomResult) => TResult, { componentKey, label, otelContext, }: {
110
- componentKey?: ComponentKey | undefined;
111
- label?: string | undefined;
112
- otelContext?: otel.Context | undefined;
113
- }) => LiveStoreJSQuery<TResult>;
114
- queryGraphQL: <TResult extends Record<string, any>, TVariableValues extends Record<string, any>>(document: DocumentNode<TResult, TVariableValues>, genVariableValues: TVariableValues | ((get: GetAtomResult) => TVariableValues), { componentKey, label, otelContext, }: {
115
- componentKey: ComponentKey;
116
- label?: string | undefined;
117
- otelContext?: otel.Context | undefined;
118
- }) => LiveStoreGraphQLQuery<TResult, TVariableValues, TGraphQLContext>;
119
- queryGraphQLOnce: <TResult extends Record<string, any>, TVariableValues extends Record<string, any>>(document: DocumentNode<TResult, TVariableValues>, variableValues: TVariableValues, otelContext?: otel.Context) => {
120
- result: TResult;
121
- queriedTables: string[];
122
- };
123
98
  /**
124
99
  * Subscribe to the results of a query
125
100
  * Returns a function to cancel the subscription.
126
101
  */
127
- subscribe: <TQuery extends LiveStoreQuery<any>>(query: TQuery, onNewValue: (value: QueryResult<TQuery>) => void, onSubsubscribe?: () => void, options?: {
102
+ subscribe: <TResult>(query: ILiveStoreQuery<TResult>, onNewValue: (value: TResult) => void, onUnsubsubscribe?: () => void, options?: {
128
103
  label?: string;
104
+ otelContext?: otel.Context;
105
+ skipInitialRun?: boolean;
129
106
  } | undefined) => (() => void);
130
- /**
131
- * Any queries created in the callback will be destroyed when the callback is complete.
132
- * Useful for temporarily creating reactive queries, which is an idempotent operation
133
- * that can be safely called inside a React useMemo hook.
134
- */
135
- inTempQueryContext: <TResult>(callback: () => TResult) => TResult;
136
107
  /**
137
108
  * Destroys the entire store, including all queries and subscriptions.
138
109
  *
139
110
  * Currently only used when shutting down the app for debugging purposes (e.g. to close Otel spans).
140
111
  */
141
112
  destroy: () => void;
142
- private destroyQuery;
143
- /**
144
- * Clean up queries and downstream subscriptions associated with a component.
145
- * This is critical to avoid memory leaks.
146
- */
147
- unmountComponent: (componentKey: ComponentKey) => void;
148
113
  applyEvent: <TEventType extends string>(eventType: TEventType, args?: any, options?: {
149
114
  skipRefresh?: boolean;
150
115
  }) => {
@@ -171,7 +136,6 @@ export declare class Store<TGraphQLContext extends BaseGraphQLContext = BaseGrap
171
136
  manualRefresh: (options?: {
172
137
  label?: string;
173
138
  }) => void;
174
- runOnce: <TQueryDef extends QueryDefinition>(queryDef: TQueryDef) => QueryResult<ReturnType<TQueryDef>>;
175
139
  /**
176
140
  * Apply an event to the store.
177
141
  * Returns the tables that were affected by the event.
@@ -184,7 +148,7 @@ export declare class Store<TGraphQLContext extends BaseGraphQLContext = BaseGrap
184
148
  * This should only be used for framework-internal purposes;
185
149
  * all app writes should go through applyEvent.
186
150
  */
187
- execute: (query: string, params?: ParamsObject, writeTables?: string[]) => Promise<void>;
151
+ execute: (query: string, params?: ParamsObject, writeTables?: string[], otelContext?: otel.Context) => void;
188
152
  }
189
153
  /** Create a new LiveStore Store */
190
154
  export declare const createStore: <TGraphQLContext extends BaseGraphQLContext>({ schema, loadStorage, graphQLOptions, otelTracer, otelRootSpanContext, boot, sqlite3, }: {
@@ -196,4 +160,14 @@ export declare const createStore: <TGraphQLContext extends BaseGraphQLContext>({
196
160
  boot?: ((db: InMemoryDatabase, parentSpan: otel.Span) => unknown | Promise<unknown>) | undefined;
197
161
  sqlite3: Sqlite.Sqlite3Static;
198
162
  }) => Promise<Store<TGraphQLContext>>;
163
+ declare class ReferenceCountedSet<T> {
164
+ private map;
165
+ constructor();
166
+ add: (key: T) => void;
167
+ remove: (key: T) => void;
168
+ has: (key: T) => boolean;
169
+ get size(): number;
170
+ [Symbol.iterator](): Generator<T, void, unknown>;
171
+ }
172
+ export {};
199
173
  //# sourceMappingURL=store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
package/dist/store.js CHANGED
@@ -1,276 +1,52 @@
1
1
  import { assertNever, makeNoopSpan, makeNoopTracer, shouldNeverHappen } from '@livestore/utils';
2
2
  import { identity } from '@livestore/utils/effect';
3
3
  import * as otel from '@opentelemetry/api';
4
- import * as graphql from 'graphql';
5
- import { uniqueId } from 'lodash-es';
6
- import * as ReactDOM from 'react-dom';
7
4
  import { v4 as uuid } from 'uuid';
8
5
  import { tableNameForComponentKey } from './componentKey.js';
9
6
  import { InMemoryDatabase } from './inMemoryDatabase.js';
10
7
  import { migrateDb } from './migrations.js';
11
8
  import { getDurationMsFromSpan } from './otel.js';
12
- import { ReactiveGraph } from './reactive.js';
13
- import { LiveStoreGraphQLQuery } from './reactiveQueries/graphql.js';
14
- import { LiveStoreJSQuery } from './reactiveQueries/js.js';
15
- import { LiveStoreSQLQuery } from './reactiveQueries/sql.js';
9
+ import { dbGraph } from './reactiveQueries/graph.js';
16
10
  import { componentStateTables } from './schema.js';
17
11
  import { isPromise, prepareBindValues, sql } from './util.js';
18
- const globalComponentKey = { _tag: 'singleton', componentName: '__global', id: 'singleton' };
19
12
  export class Store {
20
13
  constructor({ db, dbProxy, schema, storage, graphQLOptions, otelTracer, otelRootSpanContext, }) {
21
- /**
22
- * Creates a reactive LiveStore SQL query
23
- *
24
- * NOTE The query is actually running (even if no one has subscribed to it yet) and will be kept up to date.
25
- */
26
- this.querySQL = (genQueryString, { queriedTables, bindValues, componentKey, label, otelContext = otel.context.active(), }) => this.otel.tracer.startActiveSpan('querySQL', // NOTE span name will be overridden further down
27
- { attributes: { label } }, otelContext, (span) => {
28
- const otelContext = otel.trace.setSpan(otel.context.active(), span);
29
- const queryString$ = this.graph.makeThunk((get, addDebugInfo) => {
30
- if (typeof genQueryString === 'function') {
31
- const getAtom = (atom) => {
32
- if (atom._tag === 'thunk' || atom._tag === 'ref')
33
- return get(atom);
34
- return get(atom.results$);
35
- };
36
- const queryString = genQueryString(getAtom);
37
- addDebugInfo({ _tag: 'js', label: `${label}:queryString`, query: queryString });
38
- return queryString;
39
- }
40
- else {
41
- return genQueryString;
42
- }
43
- }, { label: `${label}:queryString`, meta: { liveStoreThunkType: 'sqlQueryString' } }, otelContext);
44
- label = label ?? queryString$.result;
45
- span.updateName(`querySQL:${label}`);
46
- const queryLabel = `${label}:results` + (this.temporaryQueries ? ':temp' : '');
47
- const results$ = this.graph.makeThunk((get, addDebugInfo) => this.otel.tracer.startActiveSpan('sql:', // NOTE span name will be overridden further down
48
- {}, otelContext, (span) => {
49
- try {
50
- const otelContext = otel.trace.setSpan(otel.context.active(), span);
51
- // Establish a reactive dependency on the tables used in the query
52
- for (const tableName of queriedTables) {
53
- const tableRef = this.tableRefs[tableName] ?? shouldNeverHappen(`No table ref found for ${tableName}`);
54
- get(tableRef);
55
- }
56
- const sqlString = get(queryString$);
57
- span.setAttribute('sql.query', sqlString);
58
- span.updateName(`sql:${sqlString.slice(0, 50)}`);
59
- const results = this.inMemoryDB.select(sqlString, {
60
- queriedTables,
61
- bindValues: bindValues ? prepareBindValues(bindValues, sqlString) : undefined,
62
- otelContext,
63
- });
64
- span.setAttribute('sql.rowsCount', results.length);
65
- addDebugInfo({ _tag: 'sql', label: label ?? '', query: sqlString });
66
- return results;
67
- }
68
- finally {
69
- span.end();
70
- }
71
- }), { label: queryLabel }, otelContext);
72
- const query = new LiveStoreSQLQuery({
73
- label,
74
- queryString$,
75
- results$,
76
- componentKey: componentKey ?? globalComponentKey,
77
- store: this,
78
- otelContext,
79
- });
80
- this.activeQueries.add(query);
81
- // TODO get rid of temporary query workaround
82
- if (this.temporaryQueries !== undefined) {
83
- this.temporaryQueries.add(query);
84
- }
85
- // NOTE we are not ending the span here but in the query `destroy` method
86
- return query;
87
- });
88
- this.queryJS = (genResults, { componentKey = globalComponentKey, label = `js${uniqueId()}`, otelContext = otel.context.active(), }) => this.otel.tracer.startActiveSpan(`queryJS:${label}`, { attributes: { label } }, otelContext, (span) => {
89
- const otelContext = otel.trace.setSpan(otel.context.active(), span);
90
- const queryLabel = `${label}:results` + (this.temporaryQueries ? ':temp' : '');
91
- const results$ = this.graph.makeThunk((get, addDebugInfo) => {
92
- const getAtom = (atom) => {
93
- if (atom._tag === 'thunk' || atom._tag === 'ref')
94
- return get(atom);
95
- return get(atom.results$);
96
- };
97
- addDebugInfo({ _tag: 'js', label, query: genResults.toString() });
98
- return genResults(getAtom);
99
- }, { label: queryLabel, meta: { liveStoreThunkType: 'jsResults' } }, otelContext);
100
- const query = new LiveStoreJSQuery({
101
- label,
102
- results$,
103
- componentKey,
104
- store: this,
105
- otelContext,
106
- });
107
- this.activeQueries.add(query);
108
- // TODO get rid of temporary query workaround
109
- if (this.temporaryQueries !== undefined) {
110
- this.temporaryQueries.add(query);
111
- }
112
- // NOTE we are not ending the span here but in the query `destroy` method
113
- return query;
114
- });
115
- this.queryGraphQL = (document, genVariableValues, { componentKey, label, otelContext = otel.context.active(), }) => this.otel.tracer.startActiveSpan(`queryGraphQL:`, // NOTE span name will be overridden further down
116
- {}, otelContext, (span) => {
117
- const otelContext = otel.trace.setSpan(otel.context.active(), span);
118
- if (this.graphQLContext === undefined) {
119
- return shouldNeverHappen("Can't run a GraphQL query on a store without GraphQL context");
120
- }
121
- const labelWithDefault = label ?? graphql.getOperationAST(document)?.name?.value ?? 'graphql';
122
- span.updateName(`queryGraphQL:${labelWithDefault}`);
123
- const variableValues$ = this.graph.makeThunk((get) => {
124
- if (typeof genVariableValues === 'function') {
125
- const getAtom = (atom) => {
126
- if (atom._tag === 'thunk' || atom._tag === 'ref')
127
- return get(atom);
128
- return get(atom.results$);
129
- };
130
- return genVariableValues(getAtom);
131
- }
132
- else {
133
- return genVariableValues;
134
- }
135
- }, { label: `${labelWithDefault}:variableValues`, meta: { liveStoreThunkType: 'graphqlVariableValues' } }, otelContext);
136
- const resultsLabel = `${labelWithDefault}:results` + (this.temporaryQueries ? ':temp' : '');
137
- const results$ = this.graph.makeThunk((get, addDebugInfo) => {
138
- const variableValues = get(variableValues$);
139
- const { result, queriedTables } = this.queryGraphQLOnce(document, variableValues, otelContext);
140
- // Add dependencies on any tables that were used
141
- for (const tableName of queriedTables) {
142
- const tableRef = this.tableRefs[tableName];
143
- assertNever(tableRef !== undefined, `No table ref found for ${tableName}`);
144
- get(tableRef);
145
- }
146
- addDebugInfo({ _tag: 'graphql', label: resultsLabel, query: graphql.print(document) });
147
- return result;
148
- }, { label: resultsLabel, meta: { liveStoreThunkType: 'graphqlResults' } }, otelContext);
149
- const query = new LiveStoreGraphQLQuery({
150
- document,
151
- context: this.graphQLContext,
152
- results$,
153
- componentKey,
154
- label: labelWithDefault,
155
- store: this,
156
- otelContext,
157
- });
158
- this.activeQueries.add(query);
159
- // TODO get rid of temporary query workaround
160
- if (this.temporaryQueries !== undefined) {
161
- this.temporaryQueries.add(query);
162
- }
163
- // NOTE we are not ending the span here but in the query `destroy` method
164
- return query;
165
- });
166
- this.queryGraphQLOnce = (document, variableValues, otelContext = this.otel.queriesSpanContext) => {
167
- const schema = this.graphQLSchema ?? shouldNeverHappen("Can't run a GraphQL query on a store without GraphQL schema");
168
- const context = this.graphQLContext ?? shouldNeverHappen("Can't run a GraphQL query on a store without GraphQL context");
169
- const tracer = this.otel.tracer;
170
- const operationName = graphql.getOperationAST(document)?.name?.value;
171
- return tracer.startActiveSpan(`executeGraphQLQuery: ${operationName}`, {}, otelContext, (span) => {
172
- try {
173
- span.setAttribute('graphql.variables', JSON.stringify(variableValues));
174
- span.setAttribute('graphql.query', graphql.print(document));
175
- context.queriedTables.clear();
176
- context.otelContext = otel.trace.setSpan(otel.context.active(), span);
177
- const res = graphql.executeSync({
178
- document,
179
- contextValue: context,
180
- schema: schema,
181
- variableValues,
182
- });
183
- // TODO track number of nested SQL queries via Otel + debug info
184
- if (res.errors) {
185
- span.setStatus({ code: otel.SpanStatusCode.ERROR, message: 'GraphQL error' });
186
- span.setAttribute('graphql.error', res.errors.join('\n'));
187
- span.setAttribute('graphql.error-detail', JSON.stringify(res.errors));
188
- console.error(`graphql error (${operationName})`, res.errors);
189
- }
190
- return { result: res.data, queriedTables: Array.from(context.queriedTables.values()) };
191
- }
192
- finally {
193
- span.end();
194
- }
195
- });
196
- };
197
14
  /**
198
15
  * Subscribe to the results of a query
199
16
  * Returns a function to cancel the subscription.
200
17
  */
201
- this.subscribe = (query, onNewValue, onSubsubscribe, options) => this.otel.tracer.startActiveSpan(`LiveStore.subscribe`, { attributes: { label: options?.label } }, query.otelContext, (span) => {
18
+ this.subscribe = (query, onNewValue, onUnsubsubscribe, options) => this.otel.tracer.startActiveSpan(`LiveStore.subscribe`, { attributes: { label: options?.label } }, options?.otelContext ?? this.otel.queriesSpanContext, (span) => {
202
19
  const otelContext = otel.trace.setSpan(otel.context.active(), span);
203
- const effect = this.graph.makeEffect((get) => {
204
- const result = get(query.results$);
205
- onNewValue(result);
206
- }, { label: `subscribe:${options?.label}` }, otelContext);
207
- const subscriptionKey = uuid();
20
+ const label = `subscribe:${options?.label}`;
21
+ const effect = this.graph.makeEffect((get) => onNewValue(get(query.results$)), { label });
22
+ this.activeQueries.add(query);
23
+ // Running effect right away to get initial value (unless `skipInitialRun` is set)
24
+ if (options?.skipInitialRun !== true) {
25
+ effect.doEffect(otelContext);
26
+ }
208
27
  const unsubscribe = () => {
209
28
  try {
210
29
  this.graph.destroy(effect);
211
- query.activeSubscriptions.delete(subscriptionKey);
212
- onSubsubscribe?.();
30
+ this.activeQueries.remove(query);
31
+ onUnsubsubscribe?.();
213
32
  }
214
33
  finally {
215
34
  span.end();
216
35
  }
217
36
  };
218
- query.activeSubscriptions.set(subscriptionKey, unsubscribe);
219
37
  return unsubscribe;
220
38
  });
221
- /**
222
- * Any queries created in the callback will be destroyed when the callback is complete.
223
- * Useful for temporarily creating reactive queries, which is an idempotent operation
224
- * that can be safely called inside a React useMemo hook.
225
- */
226
- this.inTempQueryContext = (callback) => {
227
- this.temporaryQueries = new Set();
228
- // TODO: consider errors / try/finally here?
229
- const result = callback();
230
- for (const query of this.temporaryQueries) {
231
- this.destroyQuery(query);
232
- }
233
- this.temporaryQueries = undefined;
234
- return result;
235
- };
236
39
  /**
237
40
  * Destroys the entire store, including all queries and subscriptions.
238
41
  *
239
42
  * Currently only used when shutting down the app for debugging purposes (e.g. to close Otel spans).
240
43
  */
241
44
  this.destroy = () => {
242
- for (const query of this.activeQueries) {
243
- this.destroyQuery(query);
244
- }
245
45
  Object.values(this.tableRefs).forEach((tableRef) => this.graph.destroy(tableRef));
246
- const applyEventsSpan = otel.trace.getSpan(this.otel.applyEventsSpanContext);
247
- applyEventsSpan.end();
248
- const queriesSpan = otel.trace.getSpan(this.otel.queriesSpanContext);
249
- queriesSpan.end();
46
+ otel.trace.getSpan(this.otel.applyEventsSpanContext).end();
47
+ otel.trace.getSpan(this.otel.queriesSpanContext).end();
250
48
  // TODO destroy active subscriptions
251
49
  };
252
- this.destroyQuery = (query) => {
253
- if (query._tag === 'sql') {
254
- // results are downstream of query string, so will automatically be destroyed together
255
- this.graph.destroy(query.queryString$);
256
- }
257
- else {
258
- this.graph.destroy(query.results$);
259
- }
260
- this.activeQueries.delete(query);
261
- query.destroy();
262
- };
263
- /**
264
- * Clean up queries and downstream subscriptions associated with a component.
265
- * This is critical to avoid memory leaks.
266
- */
267
- this.unmountComponent = (componentKey) => {
268
- for (const query of this.activeQueries) {
269
- if (query.componentKey === componentKey) {
270
- this.destroyQuery(query);
271
- }
272
- }
273
- };
274
50
  /* Apply a single write event to the store, and refresh all queries in response */
275
51
  this.applyEvent = (eventType, args = {}, options) => {
276
52
  const skipRefresh = options?.skipRefresh ?? false;
@@ -287,16 +63,23 @@ export class Store {
287
63
  assertNever(tableRef !== undefined, `No table ref found for ${tableName}`);
288
64
  tablesToUpdate.push([tableRef, null]);
289
65
  }
66
+ const debugRefreshReason = {
67
+ _tag: 'applyEvent',
68
+ event: { type: eventType, args },
69
+ writeTables: [...writeTables],
70
+ };
290
71
  // Update all table refs together in a batch, to only trigger one reactive update
291
- this.graph.setRefs(tablesToUpdate, {
292
- otelHint: 'applyEvents',
293
- skipRefresh,
294
- debugRefreshReason: {
295
- _tag: 'applyEvent',
296
- event: { type: eventType, args },
297
- writeTables: [...writeTables],
298
- },
299
- }, otelContext);
72
+ this.graph.setRefs(tablesToUpdate, { debugRefreshReason, otelContext });
73
+ if (skipRefresh === false) {
74
+ // TODO update the graph
75
+ // this.graph.refresh(
76
+ // {
77
+ // otelHint: 'applyEvents',
78
+ // debugRefreshReason,
79
+ // },
80
+ // otelContext,
81
+ // )
82
+ }
300
83
  }
301
84
  catch (e) {
302
85
  span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() });
@@ -358,16 +141,17 @@ export class Store {
358
141
  assertNever(tableRef !== undefined, `No table ref found for ${tableName}`);
359
142
  tablesToUpdate.push([tableRef, null]);
360
143
  }
144
+ const debugRefreshReason = {
145
+ _tag: 'applyEvents',
146
+ events: [...events].map((e) => ({ type: e.eventType, args: e.args })),
147
+ writeTables: [...writeTables],
148
+ };
361
149
  // Update all table refs together in a batch, to only trigger one reactive update
362
- this.graph.setRefs(tablesToUpdate, {
363
- otelHint: 'applyEvents',
364
- skipRefresh,
365
- debugRefreshReason: {
366
- _tag: 'applyEvents',
367
- events: [...events].map((e) => ({ type: e.eventType, args: e.args })),
368
- writeTables: [...writeTables],
369
- },
370
- }, otelContext);
150
+ this.graph.setRefs(tablesToUpdate, { debugRefreshReason, otelContext });
151
+ if (skipRefresh === false) {
152
+ // TODO update the graph
153
+ // this.graph.refresh({ debugRefreshReason, otelHint: 'applyEvents' }, otelContext)
154
+ }
371
155
  }
372
156
  catch (e) {
373
157
  span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() });
@@ -385,17 +169,12 @@ export class Store {
385
169
  this.manualRefresh = (options) => {
386
170
  const { label } = options ?? {};
387
171
  this.otel.tracer.startActiveSpan('LiveStore:manualRefresh', { attributes: { 'livestore.manualRefreshLabel': label } }, this.otel.applyEventsSpanContext, (span) => {
388
- const otelContext = otel.trace.setSpan(otel.context.active(), span);
389
- this.graph.refresh({ otelHint: 'manualRefresh', debugRefreshReason: { _tag: 'manualRefresh' } }, otelContext);
172
+ // const otelContext = otel.trace.setSpan(otel.context.active(), span)
173
+ // TODO update the graph
174
+ // this.graph.refresh({ otelHint: 'manualRefresh', debugRefreshReason: { _tag: 'manualRefresh' } }, otelContext)
390
175
  span.end();
391
176
  });
392
177
  };
393
- // TODO get rid of this as part of new query definition approach https://www.notion.so/schickling/New-query-definition-approach-1097a78ef0e9495bac25f90417374756?pvs=4
394
- this.runOnce = (queryDef) => {
395
- return this.inTempQueryContext(() => {
396
- return queryDef(this).results$.result;
397
- });
398
- };
399
178
  /**
400
179
  * Apply an event to the store.
401
180
  * Returns the tables that were affected by the event.
@@ -462,8 +241,8 @@ export class Store {
462
241
  * This should only be used for framework-internal purposes;
463
242
  * all app writes should go through applyEvent.
464
243
  */
465
- this.execute = async (query, params = {}, writeTables) => {
466
- this.inMemoryDB.execute(query, prepareBindValues(params, query), writeTables);
244
+ this.execute = (query, params = {}, writeTables, otelContext) => {
245
+ this.inMemoryDB.execute(query, prepareBindValues(params, query), writeTables, { otelContext });
467
246
  if (this.storage !== undefined) {
468
247
  const parentSpan = otel.trace.getSpan(otel.context.active());
469
248
  this.storage.execute(query, prepareBindValues(params, query), parentSpan);
@@ -471,21 +250,18 @@ export class Store {
471
250
  };
472
251
  this.inMemoryDB = db;
473
252
  this._proxyDb = dbProxy;
474
- this.graph = new ReactiveGraph({
475
- // TODO move this into React module
476
- // Do all our updates inside a single React setState batch to avoid multiple UI re-renders
477
- effectsWrapper: (run) => ReactDOM.unstable_batchedUpdates(() => run()),
478
- otelTracer,
479
- });
480
253
  this.schema = schema;
481
254
  // TODO generalize the `tableRefs` concept to allow finer-grained refs
482
255
  this.tableRefs = {};
483
- this.activeQueries = new Set();
256
+ this.activeQueries = new ReferenceCountedSet();
484
257
  this.storage = storage;
485
258
  const applyEventsSpan = otelTracer.startSpan('LiveStore:applyEvents', {}, otelRootSpanContext);
486
259
  const otelApplyEventsSpanContext = otel.trace.setSpan(otel.context.active(), applyEventsSpan);
487
260
  const queriesSpan = otelTracer.startSpan('LiveStore:queries', {}, otelRootSpanContext);
488
261
  const otelQueriesSpanContext = otel.trace.setSpan(otel.context.active(), queriesSpan);
262
+ // TODO allow passing in a custom graph
263
+ this.graph = dbGraph;
264
+ this.graph.context = { store: this, otelTracer, rootOtelContext: otelQueriesSpanContext };
489
265
  this.otel = {
490
266
  tracer: otelTracer,
491
267
  applyEventsSpanContext: otelApplyEventsSpanContext,
@@ -600,4 +376,33 @@ const eventToSql = (event, eventDefinition) => {
600
376
  const bindValues = typeof eventDefinition.statement === 'function' && statement.argsAlreadyBound ? {} : prepareBindValues(event.args);
601
377
  return { statement, bindValues };
602
378
  };
379
+ class ReferenceCountedSet {
380
+ constructor() {
381
+ this.add = (key) => {
382
+ const count = this.map.get(key) ?? 0;
383
+ this.map.set(key, count + 1);
384
+ };
385
+ this.remove = (key) => {
386
+ const count = this.map.get(key) ?? 0;
387
+ if (count === 1) {
388
+ this.map.delete(key);
389
+ }
390
+ else {
391
+ this.map.set(key, count - 1);
392
+ }
393
+ };
394
+ this.has = (key) => {
395
+ return this.map.has(key);
396
+ };
397
+ this.map = new Map();
398
+ }
399
+ get size() {
400
+ return this.map.size;
401
+ }
402
+ *[Symbol.iterator]() {
403
+ for (const key of this.map.keys()) {
404
+ yield key;
405
+ }
406
+ }
407
+ }
603
408
  //# sourceMappingURL=store.js.map