@livestore/livestore 0.0.58-dev.9 → 0.1.0-dev.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/README.md +1 -117
- package/dist/.tsbuildinfo +1 -1
- package/dist/effect/LiveStore.d.ts +6 -6
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +1 -1
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/global-state.d.ts.map +1 -1
- package/dist/global-state.js +2 -1
- package/dist/global-state.js.map +1 -1
- package/dist/index.d.ts +11 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -4
- package/dist/index.js.map +1 -1
- package/dist/reactiveQueries/base-class.d.ts +5 -4
- package/dist/reactiveQueries/base-class.d.ts.map +1 -1
- package/dist/reactiveQueries/base-class.js.map +1 -1
- package/dist/reactiveQueries/computed.d.ts +35 -0
- package/dist/reactiveQueries/computed.d.ts.map +1 -0
- package/dist/reactiveQueries/computed.js +57 -0
- package/dist/reactiveQueries/computed.js.map +1 -0
- package/dist/reactiveQueries/graphql.d.ts +2 -1
- package/dist/reactiveQueries/graphql.d.ts.map +1 -1
- package/dist/reactiveQueries/graphql.js.map +1 -1
- package/dist/reactiveQueries/sql.d.ts +2 -2
- package/dist/reactiveQueries/sql.d.ts.map +1 -1
- package/dist/reactiveQueries/sql.js +3 -3
- package/dist/reactiveQueries/sql.js.map +1 -1
- package/dist/reactiveQueries/sql.test.js +2 -2
- package/dist/reactiveQueries/sql.test.js.map +1 -1
- package/dist/row-query.d.ts +3 -2
- package/dist/row-query.d.ts.map +1 -1
- package/dist/row-query.js +14 -10
- package/dist/row-query.js.map +1 -1
- package/dist/store/create-store.d.ts +28 -0
- package/dist/store/create-store.d.ts.map +1 -0
- package/dist/store/create-store.js +85 -0
- package/dist/store/create-store.js.map +1 -0
- package/dist/store/devtools.d.ts +19 -0
- package/dist/store/devtools.d.ts.map +1 -0
- package/dist/store/devtools.js +141 -0
- package/dist/store/devtools.js.map +1 -0
- package/dist/store/store-context.d.ts +26 -0
- package/dist/store/store-context.d.ts.map +1 -0
- package/dist/store/store-context.js +6 -0
- package/dist/store/store-context.js.map +1 -0
- package/dist/store/store-types.d.ts +98 -0
- package/dist/store/store-types.d.ts.map +1 -0
- package/dist/store/store-types.js +6 -0
- package/dist/store/store-types.js.map +1 -0
- package/dist/store/store.d.ts +88 -0
- package/dist/store/store.d.ts.map +1 -0
- package/dist/store/store.js +367 -0
- package/dist/store/store.js.map +1 -0
- package/dist/store-devtools.d.ts +2 -2
- package/dist/store-devtools.d.ts.map +1 -1
- package/dist/store-devtools.js +2 -2
- package/dist/store-devtools.js.map +1 -1
- package/dist/store.d.ts +8 -8
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +54 -54
- package/dist/store.js.map +1 -1
- package/dist/utils/dev.d.ts +1 -0
- package/dist/utils/dev.d.ts.map +1 -1
- package/dist/utils/dev.js +5 -0
- package/dist/utils/dev.js.map +1 -1
- package/dist/{react/utils → utils}/stack-info.d.ts +1 -2
- package/dist/utils/stack-info.d.ts.map +1 -0
- package/dist/{react/utils → utils}/stack-info.js +1 -9
- package/dist/utils/stack-info.js.map +1 -0
- package/dist/utils/stack-info.test.d.ts.map +1 -0
- package/dist/{__tests__/react/utils → utils}/stack-info.test.js +1 -1
- package/dist/utils/stack-info.test.js.map +1 -0
- package/dist/utils/tests/fixture.d.ts +259 -0
- package/dist/utils/tests/fixture.d.ts.map +1 -0
- package/dist/utils/tests/fixture.js +32 -0
- package/dist/utils/tests/fixture.js.map +1 -0
- package/dist/utils/tests/mod.d.ts +3 -0
- package/dist/utils/tests/mod.d.ts.map +1 -0
- package/dist/utils/tests/mod.js +3 -0
- package/dist/utils/tests/mod.js.map +1 -0
- package/dist/utils/tests/otel.d.ts.map +1 -0
- package/dist/{__tests__/react/utils → utils/tests}/otel.js +3 -3
- package/dist/utils/tests/otel.js.map +1 -0
- package/package.json +13 -20
- package/src/ambient.d.ts +3 -1
- package/src/effect/LiveStore.ts +7 -7
- package/src/global-state.ts +5 -1
- package/src/index.ts +19 -7
- package/src/reactiveQueries/base-class.ts +5 -4
- package/src/reactiveQueries/{js.ts → computed.ts} +3 -3
- package/src/reactiveQueries/graphql.ts +2 -1
- package/src/reactiveQueries/sql.test.ts +2 -2
- package/src/reactiveQueries/sql.ts +5 -5
- package/src/row-query.ts +33 -17
- package/src/store/create-store.ts +214 -0
- package/src/{store-devtools.ts → store/devtools.ts} +9 -9
- package/src/store/store-types.ts +110 -0
- package/src/{store.ts → store/store.ts} +56 -415
- package/src/utils/dev.ts +6 -0
- package/src/{__tests__/react/utils → utils}/stack-info.test.ts +1 -1
- package/src/{react/utils → utils}/stack-info.ts +2 -12
- package/src/utils/tests/fixture.ts +73 -0
- package/src/utils/tests/mod.ts +2 -0
- package/src/{__tests__/react/utils → utils/tests}/otel.ts +4 -4
- package/tsconfig.json +1 -2
- package/vitest.config.js +0 -8
- package/dist/__tests__/react/fixture.d.ts +0 -461
- package/dist/__tests__/react/fixture.d.ts.map +0 -1
- package/dist/__tests__/react/fixture.js +0 -68
- package/dist/__tests__/react/fixture.js.map +0 -1
- package/dist/__tests__/react/utils/otel.d.ts.map +0 -1
- package/dist/__tests__/react/utils/otel.js.map +0 -1
- package/dist/__tests__/react/utils/stack-info.test.d.ts.map +0 -1
- package/dist/__tests__/react/utils/stack-info.test.js.map +0 -1
- package/dist/react/LiveStoreContext.d.ts +0 -7
- package/dist/react/LiveStoreContext.d.ts.map +0 -1
- package/dist/react/LiveStoreContext.js +0 -13
- package/dist/react/LiveStoreContext.js.map +0 -1
- package/dist/react/LiveStoreProvider.d.ts +0 -49
- package/dist/react/LiveStoreProvider.d.ts.map +0 -1
- package/dist/react/LiveStoreProvider.js +0 -169
- package/dist/react/LiveStoreProvider.js.map +0 -1
- package/dist/react/LiveStoreProvider.test.d.ts +0 -2
- package/dist/react/LiveStoreProvider.test.d.ts.map +0 -1
- package/dist/react/LiveStoreProvider.test.js +0 -62
- package/dist/react/LiveStoreProvider.test.js.map +0 -1
- package/dist/react/components/LiveList.d.ts +0 -21
- package/dist/react/components/LiveList.d.ts.map +0 -1
- package/dist/react/components/LiveList.js +0 -31
- package/dist/react/components/LiveList.js.map +0 -1
- package/dist/react/index.d.ts +0 -11
- package/dist/react/index.d.ts.map +0 -1
- package/dist/react/index.js +0 -10
- package/dist/react/index.js.map +0 -1
- package/dist/react/useAtom.d.ts +0 -10
- package/dist/react/useAtom.d.ts.map +0 -1
- package/dist/react/useAtom.js +0 -37
- package/dist/react/useAtom.js.map +0 -1
- package/dist/react/useLocalId.d.ts +0 -10
- package/dist/react/useLocalId.d.ts.map +0 -1
- package/dist/react/useLocalId.js +0 -22
- package/dist/react/useLocalId.js.map +0 -1
- package/dist/react/useQuery.d.ts +0 -9
- package/dist/react/useQuery.d.ts.map +0 -1
- package/dist/react/useQuery.js +0 -70
- package/dist/react/useQuery.js.map +0 -1
- package/dist/react/useQuery.test.d.ts +0 -2
- package/dist/react/useQuery.test.d.ts.map +0 -1
- package/dist/react/useQuery.test.js +0 -51
- package/dist/react/useQuery.test.js.map +0 -1
- package/dist/react/useRow.d.ts +0 -46
- package/dist/react/useRow.d.ts.map +0 -1
- package/dist/react/useRow.js +0 -94
- package/dist/react/useRow.js.map +0 -1
- package/dist/react/useRow.test.d.ts +0 -2
- package/dist/react/useRow.test.d.ts.map +0 -1
- package/dist/react/useRow.test.js +0 -208
- package/dist/react/useRow.test.js.map +0 -1
- package/dist/react/useTemporaryQuery.d.ts +0 -22
- package/dist/react/useTemporaryQuery.d.ts.map +0 -1
- package/dist/react/useTemporaryQuery.js +0 -75
- package/dist/react/useTemporaryQuery.js.map +0 -1
- package/dist/react/useTemporaryQuery.test.d.ts +0 -2
- package/dist/react/useTemporaryQuery.test.d.ts.map +0 -1
- package/dist/react/useTemporaryQuery.test.js +0 -59
- package/dist/react/useTemporaryQuery.test.js.map +0 -1
- package/dist/react/utils/stack-info.d.ts.map +0 -1
- package/dist/react/utils/stack-info.js.map +0 -1
- package/dist/react/utils/useStateRefWithReactiveInput.d.ts +0 -13
- package/dist/react/utils/useStateRefWithReactiveInput.d.ts.map +0 -1
- package/dist/react/utils/useStateRefWithReactiveInput.js +0 -38
- package/dist/react/utils/useStateRefWithReactiveInput.js.map +0 -1
- package/src/__tests__/react/fixture.tsx +0 -126
- package/src/react/LiveStoreContext.ts +0 -20
- package/src/react/LiveStoreProvider.test.tsx +0 -109
- package/src/react/LiveStoreProvider.tsx +0 -291
- package/src/react/__snapshots__/useRow.test.tsx.snap +0 -359
- package/src/react/components/LiveList.tsx +0 -84
- package/src/react/index.ts +0 -19
- package/src/react/useAtom.ts +0 -55
- package/src/react/useLocalId.ts +0 -34
- package/src/react/useQuery.test.tsx +0 -82
- package/src/react/useQuery.ts +0 -106
- package/src/react/useRow.test.tsx +0 -345
- package/src/react/useRow.ts +0 -180
- package/src/react/useTemporaryQuery.test.tsx +0 -98
- package/src/react/useTemporaryQuery.ts +0 -131
- package/src/react/utils/useStateRefWithReactiveInput.ts +0 -51
- package/src/store-context.ts +0 -23
- /package/dist/{__tests__/react/utils → utils}/stack-info.test.d.ts +0 -0
- /package/dist/{__tests__/react/utils → utils/tests}/otel.d.ts +0 -0
|
@@ -6,7 +6,7 @@ import * as otel from '@opentelemetry/api'
|
|
|
6
6
|
import { globalReactivityGraph } from '../global-state.js'
|
|
7
7
|
import type { Thunk } from '../reactive.js'
|
|
8
8
|
import { NOT_REFRESHED_YET } from '../reactive.js'
|
|
9
|
-
import type { RefreshReason } from '../store.js'
|
|
9
|
+
import type { RefreshReason } from '../store/store-types.js'
|
|
10
10
|
import { getDurationMsFromSpan } from '../utils/otel.js'
|
|
11
11
|
import type { GetAtomResult, LiveQuery, QueryContext, ReactivityGraph } from './base-class.js'
|
|
12
12
|
import { LiveStoreQueryBase, makeGetAtomResult } from './base-class.js'
|
|
@@ -79,7 +79,7 @@ export class LiveStoreSQLQuery<
|
|
|
79
79
|
queryInfo,
|
|
80
80
|
}: {
|
|
81
81
|
label?: string
|
|
82
|
-
genQueryString: string | ((get: GetAtomResult) => string)
|
|
82
|
+
genQueryString: string | ((get: GetAtomResult, ctx: QueryContext) => string)
|
|
83
83
|
queriedTables?: Set<string>
|
|
84
84
|
bindValues?: Bindable
|
|
85
85
|
reactivityGraph?: ReactivityGraph
|
|
@@ -101,11 +101,11 @@ export class LiveStoreSQLQuery<
|
|
|
101
101
|
let queryString$OrQueryString: string | Thunk<string, QueryContext, RefreshReason>
|
|
102
102
|
if (typeof genQueryString === 'function') {
|
|
103
103
|
queryString$OrQueryString = this.reactivityGraph.makeThunk(
|
|
104
|
-
(get, setDebugInfo,
|
|
104
|
+
(get, setDebugInfo, ctx, otelContext) => {
|
|
105
105
|
const startMs = performance.now()
|
|
106
|
-
const queryString = genQueryString(makeGetAtomResult(get, otelContext ?? rootOtelContext))
|
|
106
|
+
const queryString = genQueryString(makeGetAtomResult(get, otelContext ?? ctx.rootOtelContext), ctx)
|
|
107
107
|
const durationMs = performance.now() - startMs
|
|
108
|
-
setDebugInfo({ _tag: '
|
|
108
|
+
setDebugInfo({ _tag: 'computed', label: `${label}:queryString`, query: queryString, durationMs })
|
|
109
109
|
return queryString
|
|
110
110
|
},
|
|
111
111
|
{
|
package/src/row-query.ts
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import type { QueryInfoCol, QueryInfoNone, QueryInfoRow } from '@livestore/common'
|
|
2
|
-
import { sql } from '@livestore/common'
|
|
2
|
+
import { SessionIdSymbol, sql } from '@livestore/common'
|
|
3
3
|
import { DbSchema } from '@livestore/common/schema'
|
|
4
|
+
import type { SqliteDsl } from '@livestore/db-schema'
|
|
4
5
|
import type { GetValForKey } from '@livestore/utils'
|
|
5
6
|
import { shouldNeverHappen } from '@livestore/utils'
|
|
6
7
|
import { Schema } from '@livestore/utils/effect'
|
|
7
8
|
import type * as otel from '@opentelemetry/api'
|
|
8
|
-
import type { SqliteDsl } from 'effect-db-schema'
|
|
9
9
|
|
|
10
|
-
import type {
|
|
11
|
-
|
|
10
|
+
import type {
|
|
11
|
+
GetAtomResult,
|
|
12
|
+
LiveQuery,
|
|
13
|
+
LiveQueryAny,
|
|
14
|
+
QueryContext,
|
|
15
|
+
ReactivityGraph,
|
|
16
|
+
} from './reactiveQueries/base-class.js'
|
|
17
|
+
import { computed } from './reactiveQueries/computed.js'
|
|
12
18
|
import { LiveStoreSQLQuery } from './reactiveQueries/sql.js'
|
|
13
|
-
import type { Store } from './store.js'
|
|
19
|
+
import type { Store } from './store/store.js'
|
|
14
20
|
|
|
15
21
|
export type RowQueryOptions<TTableDef extends DbSchema.TableDef, TResult = RowResult<TTableDef>> = {
|
|
16
22
|
otelContext?: otel.Context
|
|
@@ -46,7 +52,7 @@ export type MakeRowQuery = {
|
|
|
46
52
|
>(
|
|
47
53
|
table: TTableDef,
|
|
48
54
|
// TODO adjust so it works with arbitrary primary keys or unique constraints
|
|
49
|
-
id: string,
|
|
55
|
+
id: string | SessionIdSymbol,
|
|
50
56
|
options?: RowQueryOptions<TTableDef, TResult> & RowQueryOptionsDefaulValues<TTableDef>,
|
|
51
57
|
): LiveQuery<TResult, QueryInfoRow<TTableDef>>
|
|
52
58
|
}
|
|
@@ -54,15 +60,15 @@ export type MakeRowQuery = {
|
|
|
54
60
|
// TODO also allow other where clauses and multiple rows
|
|
55
61
|
export const rowQuery: MakeRowQuery = <TTableDef extends DbSchema.TableDef>(
|
|
56
62
|
table: TTableDef,
|
|
57
|
-
idOrOptions?: string | RowQueryOptions<TTableDef, any>,
|
|
63
|
+
idOrOptions?: string | SessionIdSymbol | RowQueryOptions<TTableDef, any>,
|
|
58
64
|
options_?: RowQueryOptions<TTableDef, any> & RowQueryOptionsDefaulValues<TTableDef>,
|
|
59
65
|
) => {
|
|
60
|
-
const id = typeof idOrOptions === 'string' ? idOrOptions : undefined
|
|
61
|
-
const options = typeof idOrOptions === 'string' ? options_ : idOrOptions
|
|
66
|
+
const id = typeof idOrOptions === 'string' || idOrOptions === SessionIdSymbol ? idOrOptions : undefined
|
|
67
|
+
const options = typeof idOrOptions === 'string' || idOrOptions === SessionIdSymbol ? options_ : idOrOptions
|
|
62
68
|
const defaultValues: Partial<RowResult<TTableDef>> | undefined = (options as any)?.defaultValues ?? {}
|
|
63
69
|
|
|
64
70
|
// Validate query args
|
|
65
|
-
if (table.options.isSingleton === true && id !== undefined) {
|
|
71
|
+
if (table.options.isSingleton === true && id !== undefined && id !== SessionIdSymbol) {
|
|
66
72
|
shouldNeverHappen(`Cannot query state table ${table.sqliteDef.name} with id "${id}" as it is a singleton`)
|
|
67
73
|
} else if (table.options.isSingleton !== true && id === undefined) {
|
|
68
74
|
shouldNeverHappen(`Cannot query state table ${table.sqliteDef.name} without id`)
|
|
@@ -71,14 +77,21 @@ export const rowQuery: MakeRowQuery = <TTableDef extends DbSchema.TableDef>(
|
|
|
71
77
|
const tableSchema = table.sqliteDef
|
|
72
78
|
const tableName = tableSchema.name
|
|
73
79
|
|
|
74
|
-
const
|
|
75
|
-
|
|
80
|
+
const makeQueryString = (id: string | undefined) =>
|
|
81
|
+
sql`select * from ${tableName} ${id === undefined ? '' : `where id = '${id}'`} limit 1`
|
|
82
|
+
|
|
83
|
+
const genQueryString =
|
|
84
|
+
id === SessionIdSymbol
|
|
85
|
+
? (_: GetAtomResult, ctx: QueryContext) => makeQueryString(ctx.store.sessionId)
|
|
86
|
+
: makeQueryString(id)
|
|
76
87
|
|
|
77
88
|
const rowSchema = table.isSingleColumn === true ? table.schema.pipe(Schema.pluck('value' as any)) : table.schema
|
|
78
89
|
|
|
79
90
|
return new LiveStoreSQLQuery({
|
|
80
|
-
label:
|
|
81
|
-
|
|
91
|
+
label:
|
|
92
|
+
options?.label ??
|
|
93
|
+
`rowQuery:query:${tableSchema.name}${id === undefined ? '' : id === SessionIdSymbol ? `:sessionId` : `:${id}`}`,
|
|
94
|
+
genQueryString,
|
|
82
95
|
queriedTables: new Set([tableName]),
|
|
83
96
|
reactivityGraph: options?.reactivityGraph,
|
|
84
97
|
// While this code-path is not needed for singleton tables, it's still needed for `useRow` with non-existing rows for a given ID
|
|
@@ -130,7 +143,7 @@ const makeExecBeforeFirstRun =
|
|
|
130
143
|
otelContext: otelContext_,
|
|
131
144
|
table,
|
|
132
145
|
}: {
|
|
133
|
-
id?: string
|
|
146
|
+
id?: string | SessionIdSymbol
|
|
134
147
|
defaultValues?: any
|
|
135
148
|
skipInsertDefaultRow?: boolean
|
|
136
149
|
otelContext?: otel.Context
|
|
@@ -158,12 +171,15 @@ const insertRowWithDefaultValuesOrIgnore = ({
|
|
|
158
171
|
explicitDefaultValues,
|
|
159
172
|
}: {
|
|
160
173
|
store: Store
|
|
161
|
-
id: string
|
|
174
|
+
id: string | SessionIdSymbol
|
|
162
175
|
table: DbSchema.TableDef
|
|
163
176
|
otelContext: otel.Context
|
|
164
177
|
explicitDefaultValues: Partial<RowResult<DbSchema.TableDef>> | undefined
|
|
165
178
|
}) => {
|
|
166
|
-
const
|
|
179
|
+
const idStr = id === SessionIdSymbol ? store.sessionId : id
|
|
180
|
+
const rowExists =
|
|
181
|
+
store.syncDbWrapper.select(`select 1 from ${table.sqliteDef.name} where id = '${idStr}'`).length === 1
|
|
182
|
+
|
|
167
183
|
if (rowExists) return
|
|
168
184
|
|
|
169
185
|
// const mutationDef = deriveCreateMutationDef(table)
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Adapter,
|
|
3
|
+
BootStatus,
|
|
4
|
+
ClientSession,
|
|
5
|
+
EventId,
|
|
6
|
+
IntentionalShutdownCause,
|
|
7
|
+
StoreDevtoolsChannel,
|
|
8
|
+
} from '@livestore/common'
|
|
9
|
+
import { UnexpectedError } from '@livestore/common'
|
|
10
|
+
import type { LiveStoreSchema, MutationEvent } from '@livestore/common/schema'
|
|
11
|
+
import { makeNoopTracer } from '@livestore/utils'
|
|
12
|
+
import {
|
|
13
|
+
Cause,
|
|
14
|
+
Deferred,
|
|
15
|
+
Duration,
|
|
16
|
+
Effect,
|
|
17
|
+
Exit,
|
|
18
|
+
FiberSet,
|
|
19
|
+
Layer,
|
|
20
|
+
Logger,
|
|
21
|
+
LogLevel,
|
|
22
|
+
MutableHashMap,
|
|
23
|
+
OtelTracer,
|
|
24
|
+
Queue,
|
|
25
|
+
Runtime,
|
|
26
|
+
Scope,
|
|
27
|
+
} from '@livestore/utils/effect'
|
|
28
|
+
import * as otel from '@opentelemetry/api'
|
|
29
|
+
|
|
30
|
+
import { globalReactivityGraph } from '../global-state.js'
|
|
31
|
+
import type { ReactivityGraph } from '../reactiveQueries/base-class.js'
|
|
32
|
+
import { connectDevtoolsToStore } from './devtools.js'
|
|
33
|
+
import { Store } from './store.js'
|
|
34
|
+
import type { BaseGraphQLContext, GraphQLOptions, OtelOptions } from './store-types.js'
|
|
35
|
+
|
|
36
|
+
export type CreateStoreOptions<TGraphQLContext extends BaseGraphQLContext, TSchema extends LiveStoreSchema> = {
|
|
37
|
+
schema: TSchema
|
|
38
|
+
adapter: Adapter
|
|
39
|
+
storeId: string
|
|
40
|
+
reactivityGraph?: ReactivityGraph
|
|
41
|
+
graphQLOptions?: GraphQLOptions<TGraphQLContext>
|
|
42
|
+
otelOptions?: Partial<OtelOptions>
|
|
43
|
+
boot?: (
|
|
44
|
+
store: Store<TGraphQLContext, TSchema>,
|
|
45
|
+
parentSpan: otel.Span,
|
|
46
|
+
) => void | Promise<void> | Effect.Effect<void, unknown, otel.Tracer>
|
|
47
|
+
batchUpdates?: (run: () => void) => void
|
|
48
|
+
disableDevtools?: boolean
|
|
49
|
+
onBootStatus?: (status: BootStatus) => void
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Create a new LiveStore Store */
|
|
53
|
+
export const createStorePromise = async <
|
|
54
|
+
TGraphQLContext extends BaseGraphQLContext,
|
|
55
|
+
TSchema extends LiveStoreSchema = LiveStoreSchema,
|
|
56
|
+
>({
|
|
57
|
+
signal,
|
|
58
|
+
...options
|
|
59
|
+
}: CreateStoreOptions<TGraphQLContext, TSchema> & { signal?: AbortSignal }): Promise<Store<TGraphQLContext, TSchema>> =>
|
|
60
|
+
Effect.gen(function* () {
|
|
61
|
+
const scope = yield* Scope.make()
|
|
62
|
+
const runtime = yield* Effect.runtime()
|
|
63
|
+
|
|
64
|
+
if (signal !== undefined) {
|
|
65
|
+
signal.addEventListener('abort', () => {
|
|
66
|
+
Scope.close(scope, Exit.void).pipe(Effect.tapCauseLogPretty, Runtime.runFork(runtime))
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return yield* FiberSet.make().pipe(
|
|
71
|
+
Effect.andThen((fiberSet) => createStore({ ...options, fiberSet })),
|
|
72
|
+
Scope.extend(scope),
|
|
73
|
+
)
|
|
74
|
+
}).pipe(
|
|
75
|
+
Effect.withSpan('createStore'),
|
|
76
|
+
Effect.tapCauseLogPretty,
|
|
77
|
+
Effect.annotateLogs({ thread: 'window' }),
|
|
78
|
+
Effect.provide(Logger.pretty),
|
|
79
|
+
Logger.withMinimumLogLevel(LogLevel.Debug),
|
|
80
|
+
Effect.runPromise,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
export const createStore = <
|
|
84
|
+
TGraphQLContext extends BaseGraphQLContext,
|
|
85
|
+
TSchema extends LiveStoreSchema = LiveStoreSchema,
|
|
86
|
+
>({
|
|
87
|
+
schema,
|
|
88
|
+
adapter,
|
|
89
|
+
storeId,
|
|
90
|
+
graphQLOptions,
|
|
91
|
+
otelOptions,
|
|
92
|
+
boot,
|
|
93
|
+
reactivityGraph = globalReactivityGraph,
|
|
94
|
+
batchUpdates,
|
|
95
|
+
disableDevtools,
|
|
96
|
+
onBootStatus,
|
|
97
|
+
fiberSet,
|
|
98
|
+
}: CreateStoreOptions<TGraphQLContext, TSchema> & { fiberSet: FiberSet.FiberSet }): Effect.Effect<
|
|
99
|
+
Store<TGraphQLContext, TSchema>,
|
|
100
|
+
UnexpectedError,
|
|
101
|
+
Scope.Scope
|
|
102
|
+
> => {
|
|
103
|
+
const otelTracer = otelOptions?.tracer ?? makeNoopTracer()
|
|
104
|
+
const otelRootSpanContext = otelOptions?.rootSpanContext ?? otel.context.active()
|
|
105
|
+
|
|
106
|
+
const TracingLive = Layer.unwrapEffect(Effect.map(OtelTracer.make, Layer.setTracer)).pipe(
|
|
107
|
+
Layer.provide(Layer.sync(OtelTracer.Tracer, () => otelTracer)),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
return Effect.gen(function* () {
|
|
111
|
+
const span = yield* OtelTracer.currentOtelSpan.pipe(Effect.orDie)
|
|
112
|
+
|
|
113
|
+
const bootStatusQueue = yield* Queue.unbounded<BootStatus>().pipe(Effect.acquireRelease(Queue.shutdown))
|
|
114
|
+
|
|
115
|
+
yield* Queue.take(bootStatusQueue).pipe(
|
|
116
|
+
Effect.tapSync((status) => onBootStatus?.(status)),
|
|
117
|
+
Effect.tap((status) => (status.stage === 'done' ? Queue.shutdown(bootStatusQueue) : Effect.void)),
|
|
118
|
+
Effect.forever,
|
|
119
|
+
Effect.tapCauseLogPretty,
|
|
120
|
+
Effect.forkScoped,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
const storeDeferred = yield* Deferred.make<Store>()
|
|
124
|
+
|
|
125
|
+
const connectDevtoolsToStore_ = (storeDevtoolsChannel: StoreDevtoolsChannel) =>
|
|
126
|
+
Effect.gen(function* () {
|
|
127
|
+
const store = yield* Deferred.await(storeDeferred)
|
|
128
|
+
yield* connectDevtoolsToStore({ storeDevtoolsChannel, store })
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
const runtime = yield* Effect.runtime<Scope.Scope>()
|
|
132
|
+
|
|
133
|
+
// TODO close parent scope? (Needs refactor with Mike Arnaldi)
|
|
134
|
+
const shutdown = (cause: Cause.Cause<UnexpectedError | IntentionalShutdownCause>) =>
|
|
135
|
+
Effect.gen(function* () {
|
|
136
|
+
// NOTE we're calling `cause.toString()` here to avoid triggering a `console.error` in the grouped log
|
|
137
|
+
const logCause =
|
|
138
|
+
Cause.isFailType(cause) && cause.error._tag === 'LiveStore.IntentionalShutdownCause'
|
|
139
|
+
? cause.toString()
|
|
140
|
+
: cause
|
|
141
|
+
yield* Effect.logDebug(`Shutting down LiveStore`, logCause)
|
|
142
|
+
|
|
143
|
+
FiberSet.clear(fiberSet).pipe(
|
|
144
|
+
Effect.andThen(() => FiberSet.run(fiberSet, Effect.failCause(cause))),
|
|
145
|
+
Effect.timeout(Duration.seconds(1)),
|
|
146
|
+
Effect.logWarnIfTakesLongerThan({ label: '@livestore/livestore:shutdown:clear-fiber-set', duration: 500 }),
|
|
147
|
+
Effect.catchTag('TimeoutException', (err) =>
|
|
148
|
+
Effect.logError('Store shutdown timed out. Forcing shutdown.', err).pipe(
|
|
149
|
+
Effect.andThen(FiberSet.run(fiberSet, Effect.failCause(cause))),
|
|
150
|
+
),
|
|
151
|
+
),
|
|
152
|
+
Runtime.runFork(runtime), // NOTE we need to fork this separately otherwise it will also be interrupted
|
|
153
|
+
)
|
|
154
|
+
}).pipe(Effect.withSpan('livestore:shutdown'))
|
|
155
|
+
|
|
156
|
+
const clientSession: ClientSession = yield* adapter({
|
|
157
|
+
schema,
|
|
158
|
+
storeId,
|
|
159
|
+
devtoolsEnabled: disableDevtools !== true,
|
|
160
|
+
bootStatusQueue,
|
|
161
|
+
shutdown,
|
|
162
|
+
connectDevtoolsToStore: connectDevtoolsToStore_,
|
|
163
|
+
}).pipe(Effect.withPerformanceMeasure('livestore:makeAdapter'), Effect.withSpan('createStore:makeAdapter'))
|
|
164
|
+
|
|
165
|
+
// TODO fill up with unsynced mutation events from the client session
|
|
166
|
+
const unsyncedMutationEvents = MutableHashMap.empty<EventId, MutationEvent.ForSchema<TSchema>>()
|
|
167
|
+
|
|
168
|
+
const store = Store.createStore<TGraphQLContext, TSchema>(
|
|
169
|
+
{
|
|
170
|
+
clientSession,
|
|
171
|
+
schema,
|
|
172
|
+
graphQLOptions,
|
|
173
|
+
otelOptions: { tracer: otelTracer, rootSpanContext: otelRootSpanContext },
|
|
174
|
+
reactivityGraph,
|
|
175
|
+
disableDevtools,
|
|
176
|
+
unsyncedMutationEvents,
|
|
177
|
+
fiberSet,
|
|
178
|
+
runtime,
|
|
179
|
+
// NOTE during boot we're not yet executing mutations in a batched context
|
|
180
|
+
// but only set the provided `batchUpdates` function after boot
|
|
181
|
+
batchUpdates: (run) => run(),
|
|
182
|
+
storeId,
|
|
183
|
+
},
|
|
184
|
+
span,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
if (boot !== undefined) {
|
|
188
|
+
// TODO also incorporate `boot` function progress into `bootStatusQueue`
|
|
189
|
+
yield* Effect.tryAll(() => boot(store, span)).pipe(
|
|
190
|
+
UnexpectedError.mapToUnexpectedError,
|
|
191
|
+
Effect.withSpan('createStore:boot'),
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// NOTE it's important to yield here to allow the forked Effect in the store constructor to run
|
|
196
|
+
yield* Effect.yieldNow()
|
|
197
|
+
|
|
198
|
+
if (batchUpdates !== undefined) {
|
|
199
|
+
// Replacing the default batchUpdates function with the provided one after boot
|
|
200
|
+
store.reactivityGraph.context!.effectsWrapper = batchUpdates
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
yield* Deferred.succeed(storeDeferred, store as any as Store)
|
|
204
|
+
|
|
205
|
+
return store
|
|
206
|
+
}).pipe(
|
|
207
|
+
Effect.withSpan('createStore', {
|
|
208
|
+
parent: otelOptions?.rootSpanContext
|
|
209
|
+
? OtelTracer.makeExternalSpan(otel.trace.getSpanContext(otelOptions.rootSpanContext)!)
|
|
210
|
+
: undefined,
|
|
211
|
+
}),
|
|
212
|
+
Effect.provide(TracingLive),
|
|
213
|
+
)
|
|
214
|
+
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ClientSession, DebugInfo } from '@livestore/common'
|
|
2
2
|
import { Devtools, liveStoreVersion, UnexpectedError } from '@livestore/common'
|
|
3
3
|
import { throttle } from '@livestore/utils'
|
|
4
4
|
import type { WebChannel } from '@livestore/utils/effect'
|
|
5
5
|
import { Effect, Stream } from '@livestore/utils/effect'
|
|
6
6
|
|
|
7
|
-
import { NOT_REFRESHED_YET } from '
|
|
8
|
-
import type { LiveQuery, ReactivityGraph } from '
|
|
9
|
-
import type { SynchronousDatabaseWrapper } from '
|
|
10
|
-
import { emptyDebugInfo as makeEmptyDebugInfo } from '
|
|
11
|
-
import type { ReferenceCountedSet } from '
|
|
7
|
+
import { NOT_REFRESHED_YET } from '../reactive.js'
|
|
8
|
+
import type { LiveQuery, ReactivityGraph } from '../reactiveQueries/base-class.js'
|
|
9
|
+
import type { SynchronousDatabaseWrapper } from '../SynchronousDatabaseWrapper.js'
|
|
10
|
+
import { emptyDebugInfo as makeEmptyDebugInfo } from '../SynchronousDatabaseWrapper.js'
|
|
11
|
+
import type { ReferenceCountedSet } from '../utils/data-structures.js'
|
|
12
12
|
|
|
13
13
|
type IStore = {
|
|
14
|
-
|
|
14
|
+
clientSession: ClientSession
|
|
15
15
|
reactivityGraph: ReactivityGraph
|
|
16
16
|
syncDbWrapper: SynchronousDatabaseWrapper
|
|
17
17
|
activeQueries: ReferenceCountedSet<LiveQuery<any>>
|
|
@@ -29,7 +29,7 @@ export const connectDevtoolsToStore = ({
|
|
|
29
29
|
store: IStore
|
|
30
30
|
}) =>
|
|
31
31
|
Effect.gen(function* () {
|
|
32
|
-
const appHostId = store.
|
|
32
|
+
const appHostId = store.clientSession.coordinator.devtools.appHostId
|
|
33
33
|
|
|
34
34
|
const reactivityGraphSubcriptions: SubMap = new Map()
|
|
35
35
|
const liveQueriesSubscriptions: SubMap = new Map()
|
|
@@ -49,7 +49,7 @@ export const connectDevtoolsToStore = ({
|
|
|
49
49
|
const onMessage = (decodedMessage: typeof Devtools.MessageToAppHostStore.Type) => {
|
|
50
50
|
// console.log('storeMessagePort message', decodedMessage)
|
|
51
51
|
|
|
52
|
-
if (decodedMessage.appHostId !== store.
|
|
52
|
+
if (decodedMessage.appHostId !== store.clientSession.coordinator.devtools.appHostId) {
|
|
53
53
|
// console.log(`Unknown message`, event)
|
|
54
54
|
return
|
|
55
55
|
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { ClientSession, EventId, IntentionalShutdownCause, UnexpectedError } from '@livestore/common'
|
|
2
|
+
import type { LiveStoreSchema, MutationEvent } from '@livestore/common/schema'
|
|
3
|
+
import type { FiberSet, MutableHashMap, Runtime, Scope } from '@livestore/utils/effect'
|
|
4
|
+
import { Schema } from '@livestore/utils/effect'
|
|
5
|
+
import type * as otel from '@opentelemetry/api'
|
|
6
|
+
import type { GraphQLSchema } from 'graphql'
|
|
7
|
+
|
|
8
|
+
import type { DebugRefreshReasonBase } from '../reactive.js'
|
|
9
|
+
import type { ReactivityGraph } from '../reactiveQueries/base-class.js'
|
|
10
|
+
import type { SynchronousDatabaseWrapper } from '../SynchronousDatabaseWrapper.js'
|
|
11
|
+
import type { StackInfo } from '../utils/stack-info.js'
|
|
12
|
+
import type { Store } from './store.js'
|
|
13
|
+
|
|
14
|
+
export type LiveStoreContext =
|
|
15
|
+
| LiveStoreContextRunning
|
|
16
|
+
| {
|
|
17
|
+
stage: 'error'
|
|
18
|
+
error: UnexpectedError | unknown
|
|
19
|
+
}
|
|
20
|
+
| {
|
|
21
|
+
stage: 'shutdown'
|
|
22
|
+
cause: IntentionalShutdownCause | StoreAbort
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class StoreAbort extends Schema.TaggedError<StoreAbort>()('LiveStore.StoreAbort', {}) {}
|
|
26
|
+
export class StoreInterrupted extends Schema.TaggedError<StoreInterrupted>()('LiveStore.StoreInterrupted', {}) {}
|
|
27
|
+
|
|
28
|
+
export type LiveStoreContextRunning = {
|
|
29
|
+
stage: 'running'
|
|
30
|
+
store: Store
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type BaseGraphQLContext = {
|
|
34
|
+
queriedTables: Set<string>
|
|
35
|
+
/** Needed by Pothos Otel plugin for resolver tracing to work */
|
|
36
|
+
otelContext?: otel.Context
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type GraphQLOptions<TContext> = {
|
|
40
|
+
schema: GraphQLSchema
|
|
41
|
+
makeContext: (db: SynchronousDatabaseWrapper, tracer: otel.Tracer, sessionId: string) => TContext
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type OtelOptions = {
|
|
45
|
+
tracer: otel.Tracer
|
|
46
|
+
rootSpanContext: otel.Context
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type StoreOptions<
|
|
50
|
+
TGraphQLContext extends BaseGraphQLContext,
|
|
51
|
+
TSchema extends LiveStoreSchema = LiveStoreSchema,
|
|
52
|
+
> = {
|
|
53
|
+
clientSession: ClientSession
|
|
54
|
+
schema: TSchema
|
|
55
|
+
storeId: string
|
|
56
|
+
// TODO remove graphql-related stuff from store and move to GraphQL query directly
|
|
57
|
+
graphQLOptions?: GraphQLOptions<TGraphQLContext>
|
|
58
|
+
otelOptions: OtelOptions
|
|
59
|
+
reactivityGraph: ReactivityGraph
|
|
60
|
+
disableDevtools?: boolean
|
|
61
|
+
fiberSet: FiberSet.FiberSet
|
|
62
|
+
runtime: Runtime.Runtime<Scope.Scope>
|
|
63
|
+
batchUpdates: (runUpdates: () => void) => void
|
|
64
|
+
unsyncedMutationEvents: MutableHashMap.MutableHashMap<EventId, MutationEvent.ForSchema<TSchema>>
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type RefreshReason =
|
|
68
|
+
| DebugRefreshReasonBase
|
|
69
|
+
| {
|
|
70
|
+
_tag: 'mutate'
|
|
71
|
+
/** The mutations that were applied */
|
|
72
|
+
mutations: ReadonlyArray<MutationEvent.Any>
|
|
73
|
+
|
|
74
|
+
/** The tables that were written to by the event */
|
|
75
|
+
writeTables: ReadonlyArray<string>
|
|
76
|
+
}
|
|
77
|
+
| {
|
|
78
|
+
// TODO rename to a more appropriate name which is framework-agnostic
|
|
79
|
+
_tag: 'react'
|
|
80
|
+
api: string
|
|
81
|
+
label?: string
|
|
82
|
+
stackInfo?: StackInfo
|
|
83
|
+
}
|
|
84
|
+
| { _tag: 'manual'; label?: string }
|
|
85
|
+
|
|
86
|
+
export type QueryDebugInfo = {
|
|
87
|
+
_tag: 'graphql' | 'sql' | 'computed' | 'unknown'
|
|
88
|
+
label: string
|
|
89
|
+
query: string
|
|
90
|
+
durationMs: number
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type StoreOtel = {
|
|
94
|
+
tracer: otel.Tracer
|
|
95
|
+
mutationsSpanContext: otel.Context
|
|
96
|
+
queriesSpanContext: otel.Context
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export type StoreMutateOptions = {
|
|
100
|
+
label?: string
|
|
101
|
+
skipRefresh?: boolean
|
|
102
|
+
wasSyncMessage?: boolean
|
|
103
|
+
/**
|
|
104
|
+
* When set to `false` the mutation won't be persisted in the mutation log and sync server (but still synced).
|
|
105
|
+
* This can be useful e.g. for fine-granular update events (e.g. position updates during drag & drop)
|
|
106
|
+
*
|
|
107
|
+
* @default true
|
|
108
|
+
*/
|
|
109
|
+
persisted?: boolean
|
|
110
|
+
}
|