@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.
- package/README.md +29 -22
- package/dist/.tsbuildinfo +1 -1
- package/dist/QueryCache.d.ts +1 -1
- package/dist/QueryCache.d.ts.map +1 -1
- package/dist/QueryCache.js.map +1 -1
- package/dist/__tests__/react/fixture.d.ts +5 -4
- package/dist/__tests__/react/fixture.d.ts.map +1 -1
- package/dist/__tests__/react/fixture.js +3 -5
- package/dist/__tests__/react/fixture.js.map +1 -1
- package/dist/__tests__/react/useComponentState.test.d.ts +2 -0
- package/dist/__tests__/react/useComponentState.test.d.ts.map +1 -0
- package/dist/__tests__/react/useComponentState.test.js +68 -0
- package/dist/__tests__/react/useComponentState.test.js.map +1 -0
- package/dist/__tests__/react/useLQuery.test.d.ts +2 -0
- package/dist/__tests__/react/useLQuery.test.d.ts.map +1 -0
- package/dist/__tests__/react/useLQuery.test.js +38 -0
- package/dist/__tests__/react/useLQuery.test.js.map +1 -0
- package/dist/__tests__/react/useLiveStoreComponent.test.js +4 -9
- package/dist/__tests__/react/useLiveStoreComponent.test.js.map +1 -1
- package/dist/__tests__/react/useQuery.test.d.ts +2 -0
- package/dist/__tests__/react/useQuery.test.d.ts.map +1 -0
- package/dist/__tests__/react/useQuery.test.js +33 -0
- package/dist/__tests__/react/useQuery.test.js.map +1 -0
- package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.d.ts +2 -0
- package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.d.ts.map +1 -0
- package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.js +38 -0
- package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.js.map +1 -0
- package/dist/__tests__/react/utils/stack-info.test.d.ts +2 -0
- package/dist/__tests__/react/utils/stack-info.test.d.ts.map +1 -0
- package/dist/__tests__/react/utils/stack-info.test.js +43 -0
- package/dist/__tests__/react/utils/stack-info.test.js.map +1 -0
- package/dist/__tests__/reactive.test.js +179 -93
- package/dist/__tests__/reactive.test.js.map +1 -1
- package/dist/__tests__/reactiveQueries/sql.test.d.ts +2 -0
- package/dist/__tests__/reactiveQueries/sql.test.d.ts.map +1 -0
- package/dist/__tests__/reactiveQueries/sql.test.js +337 -0
- package/dist/__tests__/reactiveQueries/sql.test.js.map +1 -0
- package/dist/inMemoryDatabase.d.ts +4 -3
- package/dist/inMemoryDatabase.d.ts.map +1 -1
- package/dist/inMemoryDatabase.js +3 -2
- package/dist/inMemoryDatabase.js.map +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.ts +4 -3
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +3 -2
- package/dist/react/index.js.map +1 -1
- package/dist/react/useComponentState.d.ts +50 -0
- package/dist/react/useComponentState.d.ts.map +1 -0
- package/dist/react/useComponentState.js +240 -0
- package/dist/react/useComponentState.js.map +1 -0
- package/dist/react/useGlobalQuery.d.ts +3 -0
- package/dist/react/useGlobalQuery.d.ts.map +1 -0
- package/dist/react/useGlobalQuery.js +26 -0
- package/dist/react/useGlobalQuery.js.map +1 -0
- package/dist/react/useGraphQL.d.ts +3 -3
- package/dist/react/useGraphQL.d.ts.map +1 -1
- package/dist/react/useGraphQL.js +10 -8
- package/dist/react/useGraphQL.js.map +1 -1
- package/dist/react/useLiveStoreComponent.d.ts +6 -6
- package/dist/react/useLiveStoreComponent.d.ts.map +1 -1
- package/dist/react/useLiveStoreComponent.js +143 -99
- package/dist/react/useLiveStoreComponent.js.map +1 -1
- package/dist/react/useQuery.d.ts +2 -2
- package/dist/react/useQuery.d.ts.map +1 -1
- package/dist/react/useQuery.js +54 -30
- package/dist/react/useQuery.js.map +1 -1
- package/dist/react/useTemporaryQuery.d.ts +8 -0
- package/dist/react/useTemporaryQuery.d.ts.map +1 -0
- package/dist/react/useTemporaryQuery.js +19 -0
- package/dist/react/useTemporaryQuery.js.map +1 -0
- package/dist/react/utils/extractNamesFromStackTrace.d.ts +3 -0
- package/dist/react/utils/extractNamesFromStackTrace.d.ts.map +1 -0
- package/dist/react/utils/extractNamesFromStackTrace.js +40 -0
- package/dist/react/utils/extractNamesFromStackTrace.js.map +1 -0
- package/dist/react/utils/extractStackInfoFromStackTrace.d.ts +7 -0
- package/dist/react/utils/extractStackInfoFromStackTrace.d.ts.map +1 -0
- package/dist/react/utils/extractStackInfoFromStackTrace.js +40 -0
- package/dist/react/utils/extractStackInfoFromStackTrace.js.map +1 -0
- package/dist/react/utils/stack-info.d.ts +11 -0
- package/dist/react/utils/stack-info.d.ts.map +1 -0
- package/dist/react/utils/stack-info.js +49 -0
- package/dist/react/utils/stack-info.js.map +1 -0
- package/dist/reactive.d.ts +51 -67
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +138 -220
- package/dist/reactive.js.map +1 -1
- package/dist/reactiveQueries/base-class.d.ts +28 -21
- package/dist/reactiveQueries/base-class.d.ts.map +1 -1
- package/dist/reactiveQueries/base-class.js +22 -18
- package/dist/reactiveQueries/base-class.js.map +1 -1
- package/dist/reactiveQueries/graph.d.ts +10 -0
- package/dist/reactiveQueries/graph.d.ts.map +1 -0
- package/dist/reactiveQueries/graph.js +6 -0
- package/dist/reactiveQueries/graph.js.map +1 -0
- package/dist/reactiveQueries/graphql.d.ts +35 -17
- package/dist/reactiveQueries/graphql.d.ts.map +1 -1
- package/dist/reactiveQueries/graphql.js +86 -10
- package/dist/reactiveQueries/graphql.js.map +1 -1
- package/dist/reactiveQueries/js.d.ts +17 -12
- package/dist/reactiveQueries/js.d.ts.map +1 -1
- package/dist/reactiveQueries/js.js +30 -8
- package/dist/reactiveQueries/js.js.map +1 -1
- package/dist/reactiveQueries/sql.d.ts +28 -18
- package/dist/reactiveQueries/sql.d.ts.map +1 -1
- package/dist/reactiveQueries/sql.js +79 -16
- package/dist/reactiveQueries/sql.js.map +1 -1
- package/dist/store.d.ts +35 -61
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +77 -272
- package/dist/store.js.map +1 -1
- package/package.json +4 -3
- package/src/QueryCache.ts +1 -1
- package/src/__tests__/react/fixture.tsx +10 -8
- package/src/__tests__/react/{useLiveStoreComponent.test.tsx → useComponentState.test.tsx} +9 -20
- package/src/__tests__/react/useQuery.test.tsx +48 -0
- package/src/__tests__/react/utils/stack-info.test.ts +45 -0
- package/src/__tests__/reactive.test.ts +212 -140
- package/src/__tests__/reactiveQueries/sql.test.ts +372 -0
- package/src/inMemoryDatabase.ts +11 -8
- package/src/index.ts +7 -11
- package/src/react/index.ts +4 -7
- package/src/react/{useLiveStoreComponent.ts → useComponentState.ts} +90 -253
- package/src/react/useQuery.ts +74 -40
- package/src/react/useTemporaryQuery.ts +23 -0
- package/src/react/utils/stack-info.ts +63 -0
- package/src/reactive.ts +234 -308
- package/src/reactiveQueries/base-class.ts +59 -42
- package/src/reactiveQueries/graph.ts +15 -0
- package/src/reactiveQueries/graphql.ts +143 -29
- package/src/reactiveQueries/js.ts +57 -20
- package/src/reactiveQueries/sql.ts +136 -36
- package/src/store.ts +121 -426
- 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 {
|
|
10
|
-
import { ReactiveGraph } from './reactive.js';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
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 {
|
|
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
|
-
|
|
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: <
|
|
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[]) =>
|
|
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
|
package/dist/store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"
|
|
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 {
|
|
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,
|
|
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
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
212
|
-
|
|
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
|
-
|
|
247
|
-
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
},
|
|
299
|
-
|
|
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
|
-
|
|
364
|
-
|
|
365
|
-
debugRefreshReason:
|
|
366
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|