@livestore/livestore 0.0.24 → 0.0.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/QueryCache.d.ts +3 -3
- package/dist/QueryCache.d.ts.map +1 -1
- package/dist/QueryCache.js +50 -60
- package/dist/QueryCache.js.map +1 -1
- package/dist/__tests__/react/fixture.d.ts +22 -7
- package/dist/__tests__/react/fixture.d.ts.map +1 -1
- package/dist/__tests__/react/fixture.js +14 -15
- package/dist/__tests__/react/fixture.js.map +1 -1
- package/dist/__tests__/react/useQuery.test.js +37 -12
- package/dist/__tests__/react/useQuery.test.js.map +1 -1
- package/dist/__tests__/react/useRow.test.d.ts +2 -0
- package/dist/__tests__/react/useRow.test.d.ts.map +1 -0
- package/dist/__tests__/react/useRow.test.js +131 -0
- package/dist/__tests__/react/useRow.test.js.map +1 -0
- package/dist/__tests__/react/utils/stack-info.test.js +32 -0
- package/dist/__tests__/react/utils/stack-info.test.js.map +1 -1
- package/dist/__tests__/reactive.test.js +51 -0
- package/dist/__tests__/reactive.test.js.map +1 -1
- package/dist/__tests__/reactiveQueries/sql.test.js +6 -13
- package/dist/__tests__/reactiveQueries/sql.test.js.map +1 -1
- package/dist/effect/LiveStore.d.ts +3 -3
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +2 -2
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/global-state.d.ts +19 -0
- package/dist/global-state.d.ts.map +1 -0
- package/dist/global-state.js +20 -0
- package/dist/global-state.js.map +1 -0
- package/dist/inMemoryDatabase.d.ts +6 -6
- package/dist/inMemoryDatabase.d.ts.map +1 -1
- package/dist/inMemoryDatabase.js +16 -10
- package/dist/inMemoryDatabase.js.map +1 -1
- package/dist/index.d.ts +9 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -8
- package/dist/index.js.map +1 -1
- package/dist/migrations.d.ts +4 -4
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +34 -28
- package/dist/migrations.js.map +1 -1
- package/dist/react/LiveStoreContext.js.map +1 -1
- package/dist/react/LiveStoreProvider.d.ts +2 -2
- package/dist/react/LiveStoreProvider.d.ts.map +1 -1
- package/dist/react/LiveStoreProvider.js.map +1 -1
- package/dist/react/index.d.ts +1 -2
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/useQuery.d.ts +3 -0
- package/dist/react/useQuery.d.ts.map +1 -1
- package/dist/react/useQuery.js +7 -6
- package/dist/react/useQuery.js.map +1 -1
- package/dist/react/useRow.d.ts +33 -0
- package/dist/react/useRow.d.ts.map +1 -0
- package/dist/react/useRow.js +136 -0
- package/dist/react/useRow.js.map +1 -0
- package/dist/react/useTemporaryQuery.d.ts +2 -0
- package/dist/react/useTemporaryQuery.d.ts.map +1 -1
- package/dist/react/useTemporaryQuery.js +28 -11
- package/dist/react/useTemporaryQuery.js.map +1 -1
- package/dist/react/utils/stack-info.d.ts.map +1 -1
- package/dist/react/utils/stack-info.js +3 -3
- package/dist/react/utils/stack-info.js.map +1 -1
- package/dist/react/utils/useStateRefWithReactiveInput.js.map +1 -1
- package/dist/reactive.d.ts +38 -29
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +73 -45
- package/dist/reactive.js.map +1 -1
- package/dist/reactiveQueries/base-class.d.ts +10 -6
- package/dist/reactiveQueries/base-class.d.ts.map +1 -1
- package/dist/reactiveQueries/base-class.js +11 -12
- package/dist/reactiveQueries/base-class.js.map +1 -1
- package/dist/reactiveQueries/graphql.d.ts +2 -2
- package/dist/reactiveQueries/graphql.d.ts.map +1 -1
- package/dist/reactiveQueries/graphql.js +56 -50
- package/dist/reactiveQueries/graphql.js.map +1 -1
- package/dist/reactiveQueries/js.d.ts +7 -3
- package/dist/reactiveQueries/js.d.ts.map +1 -1
- package/dist/reactiveQueries/js.js +25 -15
- package/dist/reactiveQueries/js.js.map +1 -1
- package/dist/reactiveQueries/sql.d.ts +5 -5
- package/dist/reactiveQueries/sql.d.ts.map +1 -1
- package/dist/reactiveQueries/sql.js +39 -34
- package/dist/reactiveQueries/sql.js.map +1 -1
- package/dist/row-query.d.ts +21 -0
- package/dist/row-query.d.ts.map +1 -0
- package/dist/row-query.js +77 -0
- package/dist/row-query.js.map +1 -0
- package/dist/schema/action.d.ts +30 -0
- package/dist/schema/action.d.ts.map +1 -0
- package/dist/schema/action.js +3 -0
- package/dist/schema/action.js.map +1 -0
- package/dist/schema/index.d.ts +28 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +26 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/system-tables.d.ts +24 -0
- package/dist/schema/system-tables.d.ts.map +1 -0
- package/dist/schema/system-tables.js +11 -0
- package/dist/schema/system-tables.js.map +1 -0
- package/dist/schema/table-def.d.ts +161 -0
- package/dist/schema/table-def.d.ts.map +1 -0
- package/dist/schema/table-def.js +53 -0
- package/dist/schema/table-def.js.map +1 -0
- package/dist/storage/in-memory/index.d.ts +1 -1
- package/dist/storage/in-memory/index.d.ts.map +1 -1
- package/dist/storage/in-memory/index.js +6 -7
- package/dist/storage/in-memory/index.js.map +1 -1
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/tauri/index.d.ts +1 -1
- package/dist/storage/tauri/index.d.ts.map +1 -1
- package/dist/storage/tauri/index.js +25 -23
- package/dist/storage/tauri/index.js.map +1 -1
- package/dist/storage/utils/idb.js +3 -1
- package/dist/storage/utils/idb.js.map +1 -1
- package/dist/storage/web-worker/index.d.ts +1 -1
- package/dist/storage/web-worker/index.d.ts.map +1 -1
- package/dist/storage/web-worker/index.js +38 -34
- package/dist/storage/web-worker/index.js.map +1 -1
- package/dist/storage/web-worker/worker.d.ts +1 -1
- package/dist/storage/web-worker/worker.d.ts.map +1 -1
- package/dist/storage/web-worker/worker.js +1 -1
- package/dist/storage/web-worker/worker.js.map +1 -1
- package/dist/store.d.ts +11 -21
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +284 -272
- package/dist/store.js.map +1 -1
- package/dist/utils/bounded-collections.d.ts.map +1 -0
- package/dist/utils/bounded-collections.js +90 -0
- package/dist/utils/bounded-collections.js.map +1 -0
- package/dist/utils/otel.d.ts.map +1 -0
- package/dist/{otel.js → utils/otel.js} +1 -1
- package/dist/utils/otel.js.map +1 -0
- package/dist/utils/util.d.ts.map +1 -0
- package/dist/utils/util.js.map +1 -0
- package/package.json +21 -18
- package/src/QueryCache.ts +4 -4
- package/src/__tests__/react/fixture.tsx +17 -17
- package/src/__tests__/react/useQuery.test.tsx +56 -14
- package/src/__tests__/react/useRow.test.tsx +205 -0
- package/src/__tests__/react/utils/stack-info.test.ts +34 -0
- package/src/__tests__/reactive.test.ts +71 -0
- package/src/__tests__/reactiveQueries/sql.test.ts +6 -13
- package/src/effect/LiveStore.ts +7 -7
- package/src/global-state.ts +26 -0
- package/src/inMemoryDatabase.ts +14 -12
- package/src/index.ts +22 -29
- package/src/migrations.ts +41 -35
- package/src/react/LiveStoreProvider.tsx +2 -2
- package/src/react/index.ts +7 -9
- package/src/react/useQuery.ts +12 -6
- package/src/react/useRow.ts +221 -0
- package/src/react/useTemporaryQuery.ts +43 -11
- package/src/react/utils/stack-info.ts +4 -3
- package/src/reactive.ts +81 -65
- package/src/reactiveQueries/base-class.ts +14 -10
- package/src/reactiveQueries/graphql.ts +4 -3
- package/src/reactiveQueries/js.ts +9 -5
- package/src/reactiveQueries/sql.ts +9 -9
- package/src/row-query.ts +142 -0
- package/src/schema/action.ts +41 -0
- package/src/schema/index.ts +63 -0
- package/src/schema/system-tables.ts +21 -0
- package/src/schema/table-def.ts +199 -0
- package/src/storage/in-memory/index.ts +1 -1
- package/src/storage/index.ts +2 -1
- package/src/storage/tauri/index.ts +2 -2
- package/src/storage/web-worker/index.ts +1 -1
- package/src/storage/web-worker/worker.ts +2 -2
- package/src/store.ts +51 -51
- package/dist/__tests__/react/useComponentState.test.d.ts +0 -2
- package/dist/__tests__/react/useComponentState.test.d.ts.map +0 -1
- package/dist/__tests__/react/useComponentState.test.js +0 -68
- package/dist/__tests__/react/useComponentState.test.js.map +0 -1
- package/dist/__tests__/react/useLQuery.test.d.ts +0 -2
- package/dist/__tests__/react/useLQuery.test.d.ts.map +0 -1
- package/dist/__tests__/react/useLQuery.test.js +0 -38
- package/dist/__tests__/react/useLQuery.test.js.map +0 -1
- package/dist/__tests__/react/useLiveStoreComponent.test.d.ts +0 -2
- package/dist/__tests__/react/useLiveStoreComponent.test.d.ts.map +0 -1
- package/dist/__tests__/react/useLiveStoreComponent.test.js +0 -73
- package/dist/__tests__/react/useLiveStoreComponent.test.js.map +0 -1
- package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.d.ts +0 -2
- package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.d.ts.map +0 -1
- package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.js +0 -38
- package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.js.map +0 -1
- package/dist/bounded-collections.d.ts.map +0 -1
- package/dist/bounded-collections.js +0 -103
- package/dist/bounded-collections.js.map +0 -1
- package/dist/componentKey.d.ts +0 -20
- package/dist/componentKey.d.ts.map +0 -1
- package/dist/componentKey.js +0 -3
- package/dist/componentKey.js.map +0 -1
- package/dist/otel.d.ts.map +0 -1
- package/dist/otel.js.map +0 -1
- package/dist/react/useComponentState.d.ts +0 -50
- package/dist/react/useComponentState.d.ts.map +0 -1
- package/dist/react/useComponentState.js +0 -240
- package/dist/react/useComponentState.js.map +0 -1
- package/dist/react/useGlobalQuery.d.ts +0 -3
- package/dist/react/useGlobalQuery.d.ts.map +0 -1
- package/dist/react/useGlobalQuery.js +0 -26
- package/dist/react/useGlobalQuery.js.map +0 -1
- package/dist/react/useGraphQL.d.ts +0 -13
- package/dist/react/useGraphQL.d.ts.map +0 -1
- package/dist/react/useGraphQL.js +0 -87
- package/dist/react/useGraphQL.js.map +0 -1
- package/dist/react/useLiveStoreComponent.d.ts +0 -75
- package/dist/react/useLiveStoreComponent.d.ts.map +0 -1
- package/dist/react/useLiveStoreComponent.js +0 -361
- package/dist/react/useLiveStoreComponent.js.map +0 -1
- package/dist/react/utils/extractNamesFromStackTrace.d.ts +0 -3
- package/dist/react/utils/extractNamesFromStackTrace.d.ts.map +0 -1
- package/dist/react/utils/extractNamesFromStackTrace.js +0 -40
- package/dist/react/utils/extractNamesFromStackTrace.js.map +0 -1
- package/dist/react/utils/extractStackInfoFromStackTrace.d.ts +0 -7
- package/dist/react/utils/extractStackInfoFromStackTrace.d.ts.map +0 -1
- package/dist/react/utils/extractStackInfoFromStackTrace.js +0 -40
- package/dist/react/utils/extractStackInfoFromStackTrace.js.map +0 -1
- package/dist/reactiveQueries/graph.d.ts +0 -10
- package/dist/reactiveQueries/graph.d.ts.map +0 -1
- package/dist/reactiveQueries/graph.js +0 -6
- package/dist/reactiveQueries/graph.js.map +0 -1
- package/dist/schema.d.ts +0 -81
- package/dist/schema.d.ts.map +0 -1
- package/dist/schema.js +0 -46
- package/dist/schema.js.map +0 -1
- package/dist/util.d.ts.map +0 -1
- package/dist/util.js.map +0 -1
- package/src/__tests__/react/useComponentState.test.tsx +0 -100
- package/src/componentKey.ts +0 -9
- package/src/react/useComponentState.ts +0 -404
- package/src/reactiveQueries/graph.ts +0 -15
- package/src/schema.ts +0 -143
- /package/dist/{bounded-collections.d.ts → utils/bounded-collections.d.ts} +0 -0
- /package/dist/{otel.d.ts → utils/otel.d.ts} +0 -0
- /package/dist/{util.d.ts → utils/util.d.ts} +0 -0
- /package/dist/{util.js → utils/util.js} +0 -0
- /package/src/{bounded-collections.ts → utils/bounded-collections.ts} +0 -0
- /package/src/{otel.ts → utils/otel.ts} +0 -0
- /package/src/{util.ts → utils/util.ts} +0 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { Schema } from '@livestore/utils/effect'
|
|
2
|
+
import * as otel from '@opentelemetry/api'
|
|
3
|
+
import type { SqliteDsl } from 'effect-db-schema'
|
|
4
|
+
import { mapValues } from 'lodash-es'
|
|
5
|
+
import React from 'react'
|
|
6
|
+
|
|
7
|
+
import type { LiveStoreJSQuery } from '../reactiveQueries/js.js'
|
|
8
|
+
import type { RowQueryArgs, RowResult } from '../row-query.js'
|
|
9
|
+
import { rowQuery } from '../row-query.js'
|
|
10
|
+
import type { DefaultSqliteTableDef, TableDef, TableOptions } from '../schema/table-def.js'
|
|
11
|
+
import { useStore } from './LiveStoreContext.js'
|
|
12
|
+
import { useQueryRef } from './useQuery.js'
|
|
13
|
+
|
|
14
|
+
export type UseRowResult<TTableDef extends TableDef> = [
|
|
15
|
+
row: RowResult<TTableDef>,
|
|
16
|
+
setRow: StateSetters<TTableDef>,
|
|
17
|
+
query$: LiveStoreJSQuery<RowResult<TTableDef>>,
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Similar to `React.useState` but returns a tuple of `[row, setRow, query$]` for a given table where ...
|
|
22
|
+
*
|
|
23
|
+
* - `row` is the current value of the row (fully decoded according to the table schema)
|
|
24
|
+
* - `setRow` is a function that can be used to update the row (values will be encoded according to the table schema)
|
|
25
|
+
* - `query$` is a `LiveStoreJSQuery` that e.g. can be used to subscribe to changes to the row
|
|
26
|
+
*
|
|
27
|
+
* If the table is a singleton table, `useRow` can be called without an `id` argument. Otherwise, the `id` argument is required.
|
|
28
|
+
*/
|
|
29
|
+
export const useRow: {
|
|
30
|
+
<TTableDef extends TableDef<DefaultSqliteTableDef, boolean, TableOptions & { isSingleton: true }>>(
|
|
31
|
+
table: TTableDef,
|
|
32
|
+
): UseRowResult<TTableDef>
|
|
33
|
+
<TTableDef extends TableDef<DefaultSqliteTableDef, boolean, TableOptions & { isSingleton: false }>>(
|
|
34
|
+
table: TTableDef,
|
|
35
|
+
// TODO adjust so it works with arbitrary primary keys or unique constraints
|
|
36
|
+
id: string,
|
|
37
|
+
): UseRowResult<TTableDef>
|
|
38
|
+
} = <TTableDef extends TableDef>(table: TTableDef, id?: string): UseRowResult<TTableDef> => {
|
|
39
|
+
const sqliteTableDef = table.schema
|
|
40
|
+
type TComponentState = SqliteDsl.FromColumns.RowDecoded<TTableDef['schema']['columns']>
|
|
41
|
+
|
|
42
|
+
const { store } = useStore()
|
|
43
|
+
|
|
44
|
+
const reactId = React.useId()
|
|
45
|
+
|
|
46
|
+
const { query$, otelContext } = React.useMemo(() => {
|
|
47
|
+
const cachedItem = rcCache.get(table, id ?? 'singleton')
|
|
48
|
+
if (cachedItem !== undefined) {
|
|
49
|
+
cachedItem.reactIds.add(reactId)
|
|
50
|
+
cachedItem.span.addEvent('new-subscriber', { reactId })
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
query$: cachedItem.query$ as LiveStoreJSQuery<RowResult<TTableDef>>,
|
|
54
|
+
otelContext: cachedItem.otelContext,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const span = store.otel.tracer.startSpan(
|
|
59
|
+
`LiveStore:useState:${table.schema.name}${id === undefined ? '' : `:${id}`}`,
|
|
60
|
+
{ attributes: { id } },
|
|
61
|
+
store.otel.queriesSpanContext,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
65
|
+
|
|
66
|
+
const query$ = table.options.isSingleton
|
|
67
|
+
? rowQuery({ table, store, otelContext } as RowQueryArgs<TTableDef>)
|
|
68
|
+
: rowQuery({ table, store, id, otelContext } as RowQueryArgs<TTableDef>)
|
|
69
|
+
|
|
70
|
+
rcCache.set(table, id ?? 'singleton', query$, reactId, otelContext, span)
|
|
71
|
+
|
|
72
|
+
return { query$, otelContext }
|
|
73
|
+
}, [table, id, reactId, store])
|
|
74
|
+
|
|
75
|
+
React.useEffect(
|
|
76
|
+
() => () => {
|
|
77
|
+
const cachedItem = rcCache.get(table, id ?? 'singleton')!
|
|
78
|
+
|
|
79
|
+
cachedItem.reactIds.delete(reactId)
|
|
80
|
+
if (cachedItem.reactIds.size === 0) {
|
|
81
|
+
rcCache.delete(cachedItem.query$)
|
|
82
|
+
cachedItem.query$.destroy()
|
|
83
|
+
cachedItem.span.end()
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
[table, id, reactId],
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
const query$Ref = useQueryRef(query$, otelContext)
|
|
90
|
+
|
|
91
|
+
const setState = React.useMemo<StateSetters<TTableDef>>(() => {
|
|
92
|
+
if (table.isSingleColumn) {
|
|
93
|
+
return (newValue: RowResult<TTableDef>) => {
|
|
94
|
+
if (query$Ref.current === newValue) return
|
|
95
|
+
|
|
96
|
+
const encodedValue = Schema.encodeSync(sqliteTableDef.columns['value']!.type.codec)(newValue)
|
|
97
|
+
|
|
98
|
+
store.applyEvent('livestore.UpdateComponentState', {
|
|
99
|
+
tableName: sqliteTableDef.name,
|
|
100
|
+
columnNames: ['value'],
|
|
101
|
+
id,
|
|
102
|
+
bindValues: { ['value']: encodedValue },
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
const setState = // TODO: do we have a better type for the values that can go in SQLite?
|
|
107
|
+
mapValues(sqliteTableDef.columns, (column, columnName) => (newValue: string | number) => {
|
|
108
|
+
// Don't update the state if it's the same as the value already seen in the component
|
|
109
|
+
// @ts-expect-error TODO fix typing
|
|
110
|
+
if (query$Ref.current[columnName] === newValue) return
|
|
111
|
+
|
|
112
|
+
const encodedValue = Schema.encodeSync(column.type.codec)(newValue)
|
|
113
|
+
|
|
114
|
+
store.applyEvent('livestore.UpdateComponentState', {
|
|
115
|
+
tableName: sqliteTableDef.name,
|
|
116
|
+
columnNames: [columnName],
|
|
117
|
+
id,
|
|
118
|
+
bindValues: { [columnName]: encodedValue },
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
// @ts-expect-error TODO fix typing
|
|
123
|
+
setState.setMany = (columnValues: Partial<TComponentState>) => {
|
|
124
|
+
// TODO use hashing instead
|
|
125
|
+
// Don't update the state if it's the same as the value already seen in the component
|
|
126
|
+
if (
|
|
127
|
+
// @ts-expect-error TODO fix typing
|
|
128
|
+
Object.entries(columnValues).every(([columnName, value]) => query$Ref.current[columnName] === value)
|
|
129
|
+
) {
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const columnNames = Object.keys(columnValues)
|
|
134
|
+
const bindValues = mapValues(columnValues, (value, columnName) =>
|
|
135
|
+
Schema.encodeSync(sqliteTableDef.columns[columnName]!.type.codec)(value),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
store.applyEvent('livestore.UpdateComponentState', {
|
|
139
|
+
tableName: sqliteTableDef.name,
|
|
140
|
+
columnNames,
|
|
141
|
+
id,
|
|
142
|
+
bindValues,
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return setState as any
|
|
147
|
+
}
|
|
148
|
+
}, [table.isSingleColumn, id, sqliteTableDef.columns, sqliteTableDef.name, store, query$Ref])
|
|
149
|
+
|
|
150
|
+
return [query$Ref.current, setState, query$]
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export type Dispatch<A> = (action: A) => void
|
|
154
|
+
export type SetStateAction<S> = S | ((previousValue: S) => S)
|
|
155
|
+
|
|
156
|
+
export type StateSetters<TTableDef extends TableDef> = TTableDef['isSingleColumn'] extends true
|
|
157
|
+
? Dispatch<SetStateAction<RowResult<TTableDef>>>
|
|
158
|
+
: {
|
|
159
|
+
[K in keyof RowResult<TTableDef>]: Dispatch<SetStateAction<RowResult<TTableDef>[K]>>
|
|
160
|
+
} & {
|
|
161
|
+
setMany: Dispatch<SetStateAction<Partial<RowResult<TTableDef>>>>
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Reference counted cache for `query$` and otel context */
|
|
165
|
+
class RCCache {
|
|
166
|
+
private readonly cache = new Map<
|
|
167
|
+
TableDef,
|
|
168
|
+
Map<
|
|
169
|
+
string,
|
|
170
|
+
{
|
|
171
|
+
reactIds: Set<string>
|
|
172
|
+
span: otel.Span
|
|
173
|
+
otelContext: otel.Context
|
|
174
|
+
query$: LiveStoreJSQuery<any>
|
|
175
|
+
}
|
|
176
|
+
>
|
|
177
|
+
>()
|
|
178
|
+
private reverseCache = new Map<LiveStoreJSQuery<any>, [TableDef, string]>()
|
|
179
|
+
|
|
180
|
+
get = (table: TableDef, id: string) => {
|
|
181
|
+
const queries = this.cache.get(table)
|
|
182
|
+
if (queries === undefined) return undefined
|
|
183
|
+
return queries.get(id)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
set = (
|
|
187
|
+
table: TableDef,
|
|
188
|
+
id: string,
|
|
189
|
+
query$: LiveStoreJSQuery<any>,
|
|
190
|
+
reactId: string,
|
|
191
|
+
otelContext: otel.Context,
|
|
192
|
+
span: otel.Span,
|
|
193
|
+
) => {
|
|
194
|
+
let queries = this.cache.get(table)
|
|
195
|
+
if (queries === undefined) {
|
|
196
|
+
queries = new Map()
|
|
197
|
+
this.cache.set(table, queries)
|
|
198
|
+
}
|
|
199
|
+
queries.set(id, { query$, otelContext, span, reactIds: new Set([reactId]) })
|
|
200
|
+
this.reverseCache.set(query$, [table, id])
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
delete = (query$: LiveStoreJSQuery<any>) => {
|
|
204
|
+
const item = this.reverseCache.get(query$)
|
|
205
|
+
if (item === undefined) return
|
|
206
|
+
|
|
207
|
+
const [table, id] = item
|
|
208
|
+
const queries = this.cache.get(table)
|
|
209
|
+
if (queries === undefined) return
|
|
210
|
+
|
|
211
|
+
queries.delete(id)
|
|
212
|
+
|
|
213
|
+
if (queries.size === 0) {
|
|
214
|
+
this.cache.delete(table)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
this.reverseCache.delete(query$)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const rcCache = new RCCache()
|
|
@@ -1,23 +1,55 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
|
|
3
3
|
import type { ILiveStoreQuery } from '../reactiveQueries/base-class.js'
|
|
4
|
-
import {
|
|
4
|
+
import { useQueryRef } from './useQuery.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* This is needed because the `React.useMemo` call below, can sometimes be called multiple times 🤷.
|
|
8
|
+
* The map entry is being removed again in the `React.useEffect` call below.
|
|
9
|
+
*/
|
|
10
|
+
const queryCache = new Map<() => ILiveStoreQuery<any>, { reactIds: Set<string>; query$: ILiveStoreQuery<any> }>()
|
|
5
11
|
|
|
6
12
|
/**
|
|
7
13
|
* Creates a query, subscribes and destroys it when the component unmounts.
|
|
8
14
|
*
|
|
9
15
|
* Make sure `makeQuery` is a memoized function.
|
|
10
16
|
*/
|
|
11
|
-
export const useTemporaryQuery = <TResult>(makeQuery: () => ILiveStoreQuery<TResult>): TResult =>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
export const useTemporaryQuery = <TResult>(makeQuery: () => ILiveStoreQuery<TResult>): TResult =>
|
|
18
|
+
useTemporaryQueryRef(makeQuery).current
|
|
19
|
+
|
|
20
|
+
export const useTemporaryQueryRef = <TResult>(
|
|
21
|
+
makeQuery: () => ILiveStoreQuery<TResult>,
|
|
22
|
+
): React.MutableRefObject<TResult> => {
|
|
23
|
+
const reactId = React.useId()
|
|
24
|
+
|
|
25
|
+
const query$ = React.useMemo(() => {
|
|
26
|
+
const cachedItem = queryCache.get(makeQuery)
|
|
27
|
+
if (cachedItem !== undefined) {
|
|
28
|
+
cachedItem.reactIds.add(reactId)
|
|
29
|
+
|
|
30
|
+
return cachedItem.query$
|
|
19
31
|
}
|
|
20
|
-
}, [query])
|
|
21
32
|
|
|
22
|
-
|
|
33
|
+
const query$ = makeQuery()
|
|
34
|
+
|
|
35
|
+
queryCache.set(makeQuery, { reactIds: new Set([reactId]), query$ })
|
|
36
|
+
|
|
37
|
+
return query$
|
|
38
|
+
}, [reactId, makeQuery])
|
|
39
|
+
|
|
40
|
+
React.useEffect(
|
|
41
|
+
() => () => {
|
|
42
|
+
const cachedItem = queryCache.get(makeQuery)!
|
|
43
|
+
|
|
44
|
+
cachedItem.reactIds.delete(reactId)
|
|
45
|
+
|
|
46
|
+
if (cachedItem.reactIds.size === 0) {
|
|
47
|
+
cachedItem.query$.destroy()
|
|
48
|
+
queryCache.delete(makeQuery)
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
[makeQuery, reactId],
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
return useQueryRef(query$)
|
|
23
55
|
}
|
|
@@ -38,10 +38,12 @@ export const extractStackInfoFromStackTrace = (stackTrace: string): StackInfo =>
|
|
|
38
38
|
|
|
39
39
|
while ((match = namePattern.exec(stackTrace)) !== null) {
|
|
40
40
|
const [, name, filePath] = match as any as [string, string, string]
|
|
41
|
-
|
|
41
|
+
|
|
42
|
+
// NOTE No idea where this `Module.` comes from - possibly a Vite thing?
|
|
43
|
+
if ((name.startsWith('use') || name.startsWith('Module.use')) && name.endsWith('QueryRef') === false) {
|
|
42
44
|
hasReachedStart = true
|
|
43
45
|
|
|
44
|
-
frames.unshift({ name, filePath })
|
|
46
|
+
frames.unshift({ name: name.replace(/^Module\./, ''), filePath })
|
|
45
47
|
} else if (hasReachedStart) {
|
|
46
48
|
// We've reached the end of the `use*` functions, so we're adding the component name and stop
|
|
47
49
|
frames.unshift({ name, filePath })
|
|
@@ -57,7 +59,6 @@ export const useStackInfo = (): StackInfo =>
|
|
|
57
59
|
Error.stackTraceLimit = 10
|
|
58
60
|
// eslint-disable-next-line unicorn/error-message
|
|
59
61
|
const stack = new Error().stack!
|
|
60
|
-
console.log('stack', stack)
|
|
61
62
|
Error.stackTraceLimit = originalStackLimit
|
|
62
63
|
return extractStackInfoFromStackTrace(stack)
|
|
63
64
|
}, [])
|
package/src/reactive.ts
CHANGED
|
@@ -28,7 +28,7 @@ import { pick } from '@livestore/utils'
|
|
|
28
28
|
import type * as otel from '@opentelemetry/api'
|
|
29
29
|
import { isEqual, uniqueId } from 'lodash-es'
|
|
30
30
|
|
|
31
|
-
import { BoundArray } from './bounded-collections.js'
|
|
31
|
+
import { BoundArray } from './utils/bounded-collections.js'
|
|
32
32
|
// import { getDurationMsFromSpan } from './otel.js'
|
|
33
33
|
|
|
34
34
|
export const NOT_REFRESHED_YET = Symbol.for('NOT_REFRESHED_YET')
|
|
@@ -36,7 +36,7 @@ export type NOT_REFRESHED_YET = typeof NOT_REFRESHED_YET
|
|
|
36
36
|
|
|
37
37
|
export type GetAtom = <T>(atom: Atom<T, any, any>, otelContext?: otel.Context) => T
|
|
38
38
|
|
|
39
|
-
export type Ref<T, TContext, TDebugRefreshReason extends
|
|
39
|
+
export type Ref<T, TContext, TDebugRefreshReason extends DebugRefreshReason> = {
|
|
40
40
|
_tag: 'ref'
|
|
41
41
|
id: string
|
|
42
42
|
isDirty: false
|
|
@@ -50,14 +50,11 @@ export type Ref<T, TContext, TDebugRefreshReason extends Taggable> = {
|
|
|
50
50
|
equal: (a: T, b: T) => boolean
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
export type Thunk<TResult, TContext, TDebugRefreshReason extends
|
|
53
|
+
export type Thunk<TResult, TContext, TDebugRefreshReason extends DebugRefreshReason> = {
|
|
54
54
|
_tag: 'thunk'
|
|
55
55
|
id: string
|
|
56
56
|
isDirty: boolean
|
|
57
|
-
computeResult: (
|
|
58
|
-
otelContext?: otel.Context,
|
|
59
|
-
debugRefreshReason?: RefreshReasonWithGenericReasons<TDebugRefreshReason>,
|
|
60
|
-
) => TResult
|
|
57
|
+
computeResult: (otelContext?: otel.Context, debugRefreshReason?: TDebugRefreshReason) => TResult
|
|
61
58
|
previousResult: TResult | NOT_REFRESHED_YET
|
|
62
59
|
sub: Set<Atom<any, TContext, TDebugRefreshReason>>
|
|
63
60
|
super: Set<Atom<any, TContext, TDebugRefreshReason> | Effect>
|
|
@@ -70,7 +67,7 @@ export type Thunk<TResult, TContext, TDebugRefreshReason extends Taggable> = {
|
|
|
70
67
|
__getResult: any
|
|
71
68
|
}
|
|
72
69
|
|
|
73
|
-
export type Atom<T, TContext, TDebugRefreshReason extends
|
|
70
|
+
export type Atom<T, TContext, TDebugRefreshReason extends DebugRefreshReason> =
|
|
74
71
|
| Ref<T, TContext, TDebugRefreshReason>
|
|
75
72
|
| Thunk<T, TContext, TDebugRefreshReason>
|
|
76
73
|
|
|
@@ -82,13 +79,23 @@ export type Effect = {
|
|
|
82
79
|
label?: string
|
|
83
80
|
}
|
|
84
81
|
|
|
85
|
-
export type Taggable<T extends string = string> = { _tag: T }
|
|
86
|
-
|
|
87
82
|
export type DebugThunkInfo<T extends string = string> = {
|
|
88
83
|
_tag: T
|
|
89
84
|
durationMs: number
|
|
90
85
|
}
|
|
91
86
|
|
|
87
|
+
export type DebugRefreshReasonBase =
|
|
88
|
+
/** Usually in response to some `applyEvent`/`applyEvents` with `skipRefresh: true` */
|
|
89
|
+
| {
|
|
90
|
+
_tag: 'runDeferredEffects'
|
|
91
|
+
originalRefreshReasons?: ReadonlyArray<DebugRefreshReasonBase>
|
|
92
|
+
manualRefreshReason?: DebugRefreshReasonBase
|
|
93
|
+
}
|
|
94
|
+
| { _tag: 'makeThunk'; label?: string }
|
|
95
|
+
| { _tag: 'unknown' }
|
|
96
|
+
|
|
97
|
+
export type DebugRefreshReason<T extends string = string> = DebugRefreshReasonBase | { _tag: T }
|
|
98
|
+
|
|
92
99
|
export type ReactiveGraphOptions = {
|
|
93
100
|
effectsWrapper?: (runEffects: () => void) => void
|
|
94
101
|
}
|
|
@@ -100,7 +107,7 @@ export type AtomDebugInfo<TDebugThunkInfo extends DebugThunkInfo> = {
|
|
|
100
107
|
}
|
|
101
108
|
|
|
102
109
|
// TODO possibly find a better name for "refresh"
|
|
103
|
-
export type RefreshDebugInfo<TDebugRefreshReason extends
|
|
110
|
+
export type RefreshDebugInfo<TDebugRefreshReason extends DebugRefreshReason, TDebugThunkInfo extends DebugThunkInfo> = {
|
|
104
111
|
/** Currently only used for easier handling in React (e.g. as key) */
|
|
105
112
|
id: string
|
|
106
113
|
reason: TDebugRefreshReason
|
|
@@ -112,15 +119,7 @@ export type RefreshDebugInfo<TDebugRefreshReason extends Taggable, TDebugThunkIn
|
|
|
112
119
|
graphSnapshot: ReactiveGraphSnapshot
|
|
113
120
|
}
|
|
114
121
|
|
|
115
|
-
|
|
116
|
-
| T
|
|
117
|
-
| {
|
|
118
|
-
_tag: 'makeThunk'
|
|
119
|
-
label?: string
|
|
120
|
-
}
|
|
121
|
-
| { _tag: 'unknown' }
|
|
122
|
-
|
|
123
|
-
export const unknownRefreshReason = () => {
|
|
122
|
+
const unknownRefreshReason = () => {
|
|
124
123
|
// debugger
|
|
125
124
|
return { _tag: 'unknown' as const }
|
|
126
125
|
}
|
|
@@ -128,34 +127,29 @@ export const unknownRefreshReason = () => {
|
|
|
128
127
|
export type SerializedAtom = Readonly<
|
|
129
128
|
PrettifyFlat<
|
|
130
129
|
Pick<Atom<unknown, unknown, any>, '_tag' | 'id' | 'label' | 'meta'> & {
|
|
131
|
-
sub: string
|
|
132
|
-
super: string
|
|
130
|
+
sub: ReadonlyArray<string>
|
|
131
|
+
super: ReadonlyArray<string>
|
|
133
132
|
}
|
|
134
133
|
>
|
|
135
134
|
>
|
|
136
135
|
|
|
137
|
-
export type SerializedEffect = Readonly<PrettifyFlat<Pick<Effect, '_tag' | 'id'>>>
|
|
138
|
-
|
|
139
136
|
type ReactiveGraphSnapshot = {
|
|
140
|
-
readonly atoms: SerializedAtom
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
// readonly dirtyNodes: string[]
|
|
137
|
+
readonly atoms: ReadonlyArray<SerializedAtom>
|
|
138
|
+
/** IDs of deferred effects */
|
|
139
|
+
readonly deferredEffects: ReadonlyArray<string>
|
|
144
140
|
}
|
|
145
141
|
|
|
146
142
|
const uniqueNodeId = () => uniqueId('node-')
|
|
147
143
|
const uniqueRefreshInfoId = () => uniqueId('refresh-info-')
|
|
148
144
|
|
|
149
145
|
const serializeAtom = (atom: Atom<any, unknown, any>): SerializedAtom => ({
|
|
150
|
-
...pick(atom, ['_tag', 'id', 'label', 'meta']),
|
|
146
|
+
...pick(atom, ['_tag', 'id', 'label', 'meta', 'isDirty']),
|
|
151
147
|
sub: Array.from(atom.sub).map((a) => a.id),
|
|
152
148
|
super: Array.from(atom.super).map((a) => a.id),
|
|
153
149
|
})
|
|
154
150
|
|
|
155
|
-
// const serializeEffect = (effect: Effect): SerializedEffect => pick(effect, ['_tag', 'id'])
|
|
156
|
-
|
|
157
151
|
export class ReactiveGraph<
|
|
158
|
-
TDebugRefreshReason extends
|
|
152
|
+
TDebugRefreshReason extends DebugRefreshReason,
|
|
159
153
|
TDebugThunkInfo extends DebugThunkInfo,
|
|
160
154
|
TContext = {},
|
|
161
155
|
> {
|
|
@@ -164,11 +158,13 @@ export class ReactiveGraph<
|
|
|
164
158
|
|
|
165
159
|
context: TContext | undefined
|
|
166
160
|
|
|
167
|
-
debugRefreshInfos: BoundArray<
|
|
168
|
-
|
|
169
|
-
|
|
161
|
+
debugRefreshInfos: BoundArray<RefreshDebugInfo<TDebugRefreshReason, TDebugThunkInfo>> = new BoundArray(5000)
|
|
162
|
+
|
|
163
|
+
private currentDebugRefresh:
|
|
164
|
+
| { refreshedAtoms: AtomDebugInfo<TDebugThunkInfo>[]; startMs: DOMHighResTimeStamp }
|
|
165
|
+
| undefined
|
|
170
166
|
|
|
171
|
-
|
|
167
|
+
private deferredEffects: Map<Effect, Set<TDebugRefreshReason>> = new Map()
|
|
172
168
|
|
|
173
169
|
constructor(options: ReactiveGraphOptions) {
|
|
174
170
|
this.effectsWrapper = options?.effectsWrapper ?? ((runEffects: () => void) => runEffects())
|
|
@@ -208,8 +204,6 @@ export class ReactiveGraph<
|
|
|
208
204
|
label?: string
|
|
209
205
|
meta?: any
|
|
210
206
|
equal?: (a: T, b: T) => boolean
|
|
211
|
-
/** Debug info for initializing the thunk (i.e. running it the first time) */
|
|
212
|
-
// debugRefreshReason?: RefreshReasonWithGenericReasons<TDebugRefreshReason>
|
|
213
207
|
}
|
|
214
208
|
| undefined,
|
|
215
209
|
): Thunk<T, TContext, TDebugRefreshReason> {
|
|
@@ -264,16 +258,15 @@ export class ReactiveGraph<
|
|
|
264
258
|
const durationMs = performance.now() - this.currentDebugRefresh!.startMs
|
|
265
259
|
this.currentDebugRefresh = undefined
|
|
266
260
|
|
|
267
|
-
|
|
261
|
+
this.debugRefreshInfos.push({
|
|
268
262
|
id: uniqueRefreshInfoId(),
|
|
269
|
-
reason: debugRefreshReason ?? { _tag: 'makeThunk', label: options?.label },
|
|
263
|
+
reason: debugRefreshReason ?? ({ _tag: 'makeThunk', label: options?.label } as TDebugRefreshReason),
|
|
270
264
|
skippedRefresh: false,
|
|
271
265
|
refreshedAtoms,
|
|
272
266
|
durationMs,
|
|
273
267
|
completedTimestamp: Date.now(),
|
|
274
268
|
graphSnapshot: this.getSnapshot(),
|
|
275
|
-
}
|
|
276
|
-
this.debugRefreshInfos.push(refreshDebugInfo)
|
|
269
|
+
})
|
|
277
270
|
}
|
|
278
271
|
|
|
279
272
|
return result
|
|
@@ -308,7 +301,9 @@ export class ReactiveGraph<
|
|
|
308
301
|
this.removeEdge(node, subComp)
|
|
309
302
|
}
|
|
310
303
|
|
|
311
|
-
if (node._tag
|
|
304
|
+
if (node._tag === 'effect') {
|
|
305
|
+
this.deferredEffects.delete(node)
|
|
306
|
+
} else {
|
|
312
307
|
this.atoms.delete(node)
|
|
313
308
|
}
|
|
314
309
|
}
|
|
@@ -345,23 +340,20 @@ export class ReactiveGraph<
|
|
|
345
340
|
val: T,
|
|
346
341
|
options?:
|
|
347
342
|
| {
|
|
343
|
+
skipRefresh?: boolean
|
|
348
344
|
debugRefreshReason?: TDebugRefreshReason
|
|
349
345
|
otelContext?: otel.Context
|
|
350
346
|
}
|
|
351
347
|
| undefined,
|
|
352
348
|
) {
|
|
353
|
-
ref
|
|
354
|
-
|
|
355
|
-
const effectsToRefresh = new Set<Effect>()
|
|
356
|
-
markSuperCompDirtyRec(ref, effectsToRefresh)
|
|
357
|
-
|
|
358
|
-
this.runEffects(effectsToRefresh, options)
|
|
349
|
+
this.setRefs([[ref, val]], options)
|
|
359
350
|
}
|
|
360
351
|
|
|
361
352
|
setRefs<T>(
|
|
362
353
|
refs: [Ref<T, TContext, TDebugRefreshReason>, T][],
|
|
363
354
|
options?:
|
|
364
355
|
| {
|
|
356
|
+
skipRefresh?: boolean
|
|
365
357
|
debugRefreshReason?: TDebugRefreshReason
|
|
366
358
|
otelContext?: otel.Context
|
|
367
359
|
}
|
|
@@ -374,17 +366,30 @@ export class ReactiveGraph<
|
|
|
374
366
|
markSuperCompDirtyRec(ref, effectsToRefresh)
|
|
375
367
|
}
|
|
376
368
|
|
|
377
|
-
|
|
369
|
+
if (options?.skipRefresh) {
|
|
370
|
+
for (const effect of effectsToRefresh) {
|
|
371
|
+
if (this.deferredEffects.has(effect) === false) {
|
|
372
|
+
this.deferredEffects.set(effect, new Set())
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (options?.debugRefreshReason !== undefined) {
|
|
376
|
+
this.deferredEffects.get(effect)!.add(options.debugRefreshReason)
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
} else {
|
|
380
|
+
this.runEffects(effectsToRefresh, {
|
|
381
|
+
debugRefreshReason: options?.debugRefreshReason ?? (unknownRefreshReason() as TDebugRefreshReason),
|
|
382
|
+
otelContext: options?.otelContext,
|
|
383
|
+
})
|
|
384
|
+
}
|
|
378
385
|
}
|
|
379
386
|
|
|
380
387
|
private runEffects = (
|
|
381
388
|
effectsToRefresh: Set<Effect>,
|
|
382
|
-
options
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
}
|
|
387
|
-
| undefined,
|
|
389
|
+
options: {
|
|
390
|
+
debugRefreshReason: TDebugRefreshReason
|
|
391
|
+
otelContext?: otel.Context
|
|
392
|
+
},
|
|
388
393
|
) => {
|
|
389
394
|
this.effectsWrapper(() => {
|
|
390
395
|
this.currentDebugRefresh = { refreshedAtoms: [], startMs: performance.now() }
|
|
@@ -399,7 +404,7 @@ export class ReactiveGraph<
|
|
|
399
404
|
|
|
400
405
|
const refreshDebugInfo: RefreshDebugInfo<TDebugRefreshReason, TDebugThunkInfo> = {
|
|
401
406
|
id: uniqueRefreshInfoId(),
|
|
402
|
-
reason: options
|
|
407
|
+
reason: options.debugRefreshReason,
|
|
403
408
|
skippedRefresh: false,
|
|
404
409
|
refreshedAtoms,
|
|
405
410
|
durationMs,
|
|
@@ -410,6 +415,22 @@ export class ReactiveGraph<
|
|
|
410
415
|
})
|
|
411
416
|
}
|
|
412
417
|
|
|
418
|
+
runDeferredEffects = (options?: { debugRefreshReason?: TDebugRefreshReason; otelContext?: otel.Context }) => {
|
|
419
|
+
// TODO improve how refresh reasons are propagated for deferred effect execution
|
|
420
|
+
// TODO also improve "batching" of running deferred effects (i.e. in a single `this.runEffects` call)
|
|
421
|
+
// but need to be careful to not overwhelm the main thread
|
|
422
|
+
for (const [effect, debugRefreshReasons] of this.deferredEffects) {
|
|
423
|
+
this.runEffects(new Set([effect]), {
|
|
424
|
+
debugRefreshReason: {
|
|
425
|
+
_tag: 'runDeferredEffects',
|
|
426
|
+
originalRefreshReasons: Array.from(debugRefreshReasons) as ReadonlyArray<DebugRefreshReasonBase>,
|
|
427
|
+
manualRefreshReason: options?.debugRefreshReason,
|
|
428
|
+
} as TDebugRefreshReason,
|
|
429
|
+
otelContext: options?.otelContext,
|
|
430
|
+
})
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
413
434
|
addEdge(
|
|
414
435
|
superComp: Atom<any, TContext, TDebugRefreshReason> | Effect,
|
|
415
436
|
subComp: Atom<any, TContext, TDebugRefreshReason>,
|
|
@@ -426,17 +447,12 @@ export class ReactiveGraph<
|
|
|
426
447
|
subComp.super.delete(superComp)
|
|
427
448
|
}
|
|
428
449
|
|
|
429
|
-
|
|
450
|
+
getSnapshot = (): ReactiveGraphSnapshot => ({
|
|
430
451
|
atoms: Array.from(this.atoms).map(serializeAtom),
|
|
431
|
-
|
|
432
|
-
// dirtyNodes: Array.from(this.dirtyNodes).map((a) => a.id),
|
|
452
|
+
deferredEffects: Array.from(this.deferredEffects.keys()).map((_) => _.id),
|
|
433
453
|
})
|
|
434
454
|
}
|
|
435
455
|
|
|
436
|
-
// const isAtom = <T, TContext>(a: Atom<T, TContext> | Effect): a is Atom<T, TContext> =>
|
|
437
|
-
// a._tag === 'ref' || a._tag === 'thunk'
|
|
438
|
-
// const isEffect = <T, TContext>(a: Atom<T, TContext> | Effect): a is Effect => a._tag === 'effect'
|
|
439
|
-
|
|
440
456
|
const compute = <T>(atom: Atom<T, unknown, any>, otelContext: otel.Context): T => {
|
|
441
457
|
// const __getResult = atom._tag === 'thunk' ? atom.__getResult.toString() : ''
|
|
442
458
|
if (atom.isDirty) {
|
|
@@ -462,6 +478,6 @@ const markSuperCompDirtyRec = <T>(atom: Atom<T, unknown, any>, effectsToRefresh:
|
|
|
462
478
|
}
|
|
463
479
|
}
|
|
464
480
|
|
|
465
|
-
const throwContextNotSetError = (): never => {
|
|
481
|
+
export const throwContextNotSetError = (): never => {
|
|
466
482
|
throw new Error(`LiveStore Error: \`context\` not set on ReactiveGraph`)
|
|
467
483
|
}
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import type * as otel from '@opentelemetry/api'
|
|
2
2
|
|
|
3
|
+
import { dbGraph } from '../global-state.js'
|
|
3
4
|
import type { StackInfo } from '../react/utils/stack-info.js'
|
|
4
|
-
import type
|
|
5
|
-
import type { RefreshReason } from '../store.js'
|
|
6
|
-
import { type DbContext, dbGraph } from './graph.js'
|
|
5
|
+
import { type Atom, type GetAtom, throwContextNotSetError, type Thunk } from '../reactive.js'
|
|
6
|
+
import type { RefreshReason, Store } from '../store.js'
|
|
7
7
|
import type { LiveStoreJSQuery } from './js.js'
|
|
8
8
|
|
|
9
|
+
export type DbContext = {
|
|
10
|
+
store: Store
|
|
11
|
+
otelTracer: otel.Tracer
|
|
12
|
+
rootOtelContext: otel.Context
|
|
13
|
+
}
|
|
14
|
+
|
|
9
15
|
export type UnsubscribeQuery = () => void
|
|
10
16
|
|
|
11
17
|
let queryIdCounter = 0
|
|
@@ -18,7 +24,7 @@ export interface ILiveStoreQuery<TResult> {
|
|
|
18
24
|
|
|
19
25
|
label: string
|
|
20
26
|
|
|
21
|
-
run: (otelContext?: otel.Context, debugRefreshReason?:
|
|
27
|
+
run: (otelContext?: otel.Context, debugRefreshReason?: RefreshReason) => TResult
|
|
22
28
|
|
|
23
29
|
destroy(): void
|
|
24
30
|
|
|
@@ -41,13 +47,10 @@ export abstract class LiveStoreQueryBase<TResult> implements ILiveStoreQuery<TRe
|
|
|
41
47
|
|
|
42
48
|
abstract destroy: () => void
|
|
43
49
|
|
|
44
|
-
run = (otelContext?: otel.Context, debugRefreshReason?:
|
|
50
|
+
run = (otelContext?: otel.Context, debugRefreshReason?: RefreshReason): TResult =>
|
|
45
51
|
this.results$.computeResult(otelContext, debugRefreshReason)
|
|
46
52
|
|
|
47
|
-
runAndDestroy = (
|
|
48
|
-
otelContext?: otel.Context,
|
|
49
|
-
debugRefreshReason?: RefreshReasonWithGenericReasons<RefreshReason>,
|
|
50
|
-
): TResult => {
|
|
53
|
+
runAndDestroy = (otelContext?: otel.Context, debugRefreshReason?: RefreshReason): TResult => {
|
|
51
54
|
const result = this.run(otelContext, debugRefreshReason)
|
|
52
55
|
this.destroy()
|
|
53
56
|
return result
|
|
@@ -57,7 +60,8 @@ export abstract class LiveStoreQueryBase<TResult> implements ILiveStoreQuery<TRe
|
|
|
57
60
|
onNewValue: (value: TResult) => void,
|
|
58
61
|
onUnsubsubscribe?: () => void,
|
|
59
62
|
options?: { label?: string; otelContext?: otel.Context } | undefined,
|
|
60
|
-
): (() => void) =>
|
|
63
|
+
): (() => void) =>
|
|
64
|
+
dbGraph.context?.store.subscribe(this, onNewValue, onUnsubsubscribe, options) ?? throwContextNotSetError()
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
export type GetAtomResult = <T>(atom: Atom<T, any, RefreshReason> | LiveStoreJSQuery<T>) => T
|