@livestore/livestore 0.0.47-dev.0 → 0.0.48-dev.0
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 +3 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/MainDatabaseWrapper.d.ts +2 -2
- package/dist/MainDatabaseWrapper.d.ts.map +1 -1
- package/dist/MainDatabaseWrapper.js.map +1 -1
- package/dist/__tests__/react/fixture.d.ts +262 -158
- package/dist/__tests__/react/fixture.d.ts.map +1 -1
- package/dist/__tests__/react/fixture.js +14 -12
- package/dist/__tests__/react/fixture.js.map +1 -1
- package/dist/__tests__/react/utils/otel.d.ts +1 -1
- package/dist/__tests__/react/utils/otel.d.ts.map +1 -1
- package/dist/effect/LiveStore.d.ts +5 -5
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +5 -4
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/react/LiveStoreProvider.d.ts +4 -4
- package/dist/react/LiveStoreProvider.d.ts.map +1 -1
- package/dist/react/LiveStoreProvider.js +8 -8
- package/dist/react/LiveStoreProvider.js.map +1 -1
- package/dist/react/LiveStoreProvider.test.js +3 -4
- package/dist/react/LiveStoreProvider.test.js.map +1 -1
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/useAtom.d.ts +5 -2
- package/dist/react/useAtom.d.ts.map +1 -1
- package/dist/react/useAtom.js +20 -4
- package/dist/react/useAtom.js.map +1 -1
- package/dist/react/useLocalId.d.ts +10 -0
- package/dist/react/useLocalId.d.ts.map +1 -0
- package/dist/react/useLocalId.js +15 -0
- package/dist/react/useLocalId.js.map +1 -0
- package/dist/react/useQuery.test.js +7 -7
- package/dist/react/useQuery.test.js.map +1 -1
- package/dist/react/useRow.d.ts +3 -1
- package/dist/react/useRow.d.ts.map +1 -1
- package/dist/react/useRow.js +25 -8
- package/dist/react/useRow.js.map +1 -1
- package/dist/react/useRow.test.js +58 -29
- package/dist/react/useRow.test.js.map +1 -1
- package/dist/react/useTemporaryQuery.d.ts +1 -1
- package/dist/react/useTemporaryQuery.d.ts.map +1 -1
- package/dist/react/useTemporaryQuery.js.map +1 -1
- package/dist/react/useTemporaryQuery.test.js +3 -3
- package/dist/react/useTemporaryQuery.test.js.map +1 -1
- package/dist/react/utils/useStateRefWithReactiveInput.d.ts.map +1 -1
- package/dist/reactive.test.js +1 -1
- package/dist/reactive.test.js.map +1 -1
- package/dist/reactiveQueries/base-class.d.ts +1 -1
- package/dist/reactiveQueries/base-class.d.ts.map +1 -1
- package/dist/reactiveQueries/base-class.js.map +1 -1
- package/dist/reactiveQueries/graphql.d.ts +4 -4
- package/dist/reactiveQueries/graphql.d.ts.map +1 -1
- package/dist/reactiveQueries/graphql.js +2 -1
- package/dist/reactiveQueries/graphql.js.map +1 -1
- package/dist/reactiveQueries/js.d.ts +1 -1
- package/dist/reactiveQueries/js.d.ts.map +1 -1
- package/dist/reactiveQueries/js.js.map +1 -1
- package/dist/reactiveQueries/sql.d.ts +1 -1
- package/dist/reactiveQueries/sql.d.ts.map +1 -1
- package/dist/reactiveQueries/sql.js +2 -2
- package/dist/reactiveQueries/sql.js.map +1 -1
- package/dist/row-query.d.ts +3 -3
- package/dist/row-query.d.ts.map +1 -1
- package/dist/row-query.js +47 -34
- package/dist/row-query.js.map +1 -1
- package/dist/store.d.ts +20 -19
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +67 -41
- package/dist/store.js.map +1 -1
- package/dist/utils/bounded-collections.d.ts +1 -1
- package/dist/utils/bounded-collections.d.ts.map +1 -1
- package/dist/utils/util.d.ts +0 -1
- package/dist/utils/util.d.ts.map +1 -1
- package/dist/utils/util.js.map +1 -1
- package/package.json +16 -16
- package/src/MainDatabaseWrapper.ts +3 -3
- package/src/__tests__/react/fixture.tsx +32 -18
- package/src/effect/LiveStore.ts +9 -8
- package/src/index.ts +7 -5
- package/src/react/LiveStoreProvider.test.tsx +5 -5
- package/src/react/LiveStoreProvider.tsx +11 -11
- package/src/react/index.ts +1 -0
- package/src/react/useAtom.ts +31 -5
- package/src/react/useLocalId.ts +25 -0
- package/src/react/useQuery.test.tsx +8 -8
- package/src/react/useRow.test.tsx +60 -35
- package/src/react/useRow.ts +43 -12
- package/src/react/useTemporaryQuery.test.tsx +4 -4
- package/src/react/useTemporaryQuery.ts +1 -1
- package/src/reactive.test.ts +1 -1
- package/src/reactiveQueries/base-class.ts +1 -1
- package/src/reactiveQueries/graphql.ts +3 -2
- package/src/reactiveQueries/js.ts +1 -1
- package/src/reactiveQueries/sql.ts +3 -3
- package/src/row-query.ts +67 -55
- package/src/store.ts +89 -59
- package/src/utils/util.ts +0 -2
- package/dist/cud.d.ts +0 -28
- package/dist/cud.d.ts.map +0 -1
- package/dist/cud.js +0 -47
- package/dist/cud.js.map +0 -1
- package/dist/cud.test.d.ts +0 -2
- package/dist/cud.test.d.ts.map +0 -1
- package/dist/cud.test.js +0 -47
- package/dist/cud.test.js.map +0 -1
- package/dist/migrations.d.ts +0 -16
- package/dist/migrations.d.ts.map +0 -1
- package/dist/migrations.js +0 -98
- package/dist/migrations.js.map +0 -1
- package/dist/query-info.d.ts +0 -48
- package/dist/query-info.d.ts.map +0 -1
- package/dist/query-info.js +0 -39
- package/dist/query-info.js.map +0 -1
- package/src/cud.test.ts +0 -52
- package/src/cud.ts +0 -88
- package/src/migrations.ts +0 -151
- package/src/query-info.ts +0 -104
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { Store, createStore } from './store.js'
|
|
2
|
-
export type { BaseGraphQLContext, QueryDebugInfo, RefreshReason
|
|
2
|
+
export type { BaseGraphQLContext, QueryDebugInfo, RefreshReason } from './store.js'
|
|
3
3
|
|
|
4
4
|
export type { QueryDefinition, LiveStoreCreateStoreOptions, LiveStoreContext } from './effect/LiveStore.js'
|
|
5
5
|
|
|
@@ -26,14 +26,16 @@ export { globalDbGraph, dynamicallyRegisteredTables } from './global-state.js'
|
|
|
26
26
|
|
|
27
27
|
export { type RowResult, type RowResultEncoded, rowQuery, deriveColQuery } from './row-query.js'
|
|
28
28
|
|
|
29
|
-
export * from './cud.js'
|
|
30
|
-
|
|
31
29
|
export * from '@livestore/common/schema'
|
|
32
|
-
export { sql } from '@livestore/common'
|
|
30
|
+
export { sql, type BootDb, type InMemoryDatabase } from '@livestore/common'
|
|
33
31
|
|
|
34
32
|
export { SqliteAst, SqliteDsl } from 'effect-db-schema'
|
|
35
33
|
|
|
36
34
|
export { prepareBindValues, type Bindable, type PreparedBindValues } from './utils/util.js'
|
|
37
35
|
export { isEqual } from 'lodash-es'
|
|
38
36
|
|
|
39
|
-
export type {
|
|
37
|
+
export type {
|
|
38
|
+
StoreAdapter as DatabaseImpl,
|
|
39
|
+
StoreAdapterFactory as DatabaseFactory,
|
|
40
|
+
PreparedStatement,
|
|
41
|
+
} from '@livestore/common'
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
import type { BootDb } from '@livestore/common'
|
|
1
2
|
import { sql } from '@livestore/common'
|
|
2
|
-
import {
|
|
3
|
-
import { InMemoryStorage } from '@livestore/web/storage/in-memory'
|
|
3
|
+
import { makeInMemoryAdapter } from '@livestore/web'
|
|
4
4
|
import { render, screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react'
|
|
5
5
|
import React from 'react'
|
|
6
6
|
import { describe, expect, it } from 'vitest'
|
|
7
7
|
|
|
8
8
|
import { parseTodos, schema } from '../__tests__/react/fixture.js'
|
|
9
9
|
import { querySQL } from '../reactiveQueries/sql.js'
|
|
10
|
-
import type {
|
|
10
|
+
import type { Store } from '../store.js'
|
|
11
11
|
import * as LiveStoreReact from './index.js'
|
|
12
12
|
import { LiveStoreProvider } from './LiveStoreProvider.js'
|
|
13
13
|
|
|
@@ -35,9 +35,9 @@ describe('LiveStoreProvider', () => {
|
|
|
35
35
|
[],
|
|
36
36
|
)
|
|
37
37
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
38
|
-
const
|
|
38
|
+
const adapterMemo = React.useMemo(() => makeInMemoryAdapter(), [forceUpdate])
|
|
39
39
|
return (
|
|
40
|
-
<LiveStoreProvider schema={schema} fallback={<div>Loading LiveStore</div>}
|
|
40
|
+
<LiveStoreProvider schema={schema} fallback={<div>Loading LiveStore</div>} adapter={adapterMemo} boot={bootCb}>
|
|
41
41
|
<App />
|
|
42
42
|
</LiveStoreProvider>
|
|
43
43
|
)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { BootDb, StoreAdapterFactory } from '@livestore/common'
|
|
2
2
|
import type { LiveStoreSchema } from '@livestore/common/schema'
|
|
3
3
|
import { shouldNeverHappen } from '@livestore/utils'
|
|
4
4
|
import type * as otel from '@opentelemetry/api'
|
|
@@ -7,7 +7,7 @@ import React from 'react'
|
|
|
7
7
|
|
|
8
8
|
// TODO refactor so the `react` module doesn't depend on `effect` module
|
|
9
9
|
import type { LiveStoreContext as StoreContext_, LiveStoreCreateStoreOptions } from '../effect/LiveStore.js'
|
|
10
|
-
import type { BaseGraphQLContext,
|
|
10
|
+
import type { BaseGraphQLContext, GraphQLOptions, Store } from '../store.js'
|
|
11
11
|
import { createStore } from '../store.js'
|
|
12
12
|
import { LiveStoreContext } from './LiveStoreContext.js'
|
|
13
13
|
|
|
@@ -18,7 +18,7 @@ interface LiveStoreProviderProps<GraphQLContext> {
|
|
|
18
18
|
otelTracer?: otel.Tracer
|
|
19
19
|
otelRootSpanContext?: otel.Context
|
|
20
20
|
fallback: ReactElement
|
|
21
|
-
|
|
21
|
+
adapter: StoreAdapterFactory
|
|
22
22
|
batchUpdates?: (run: () => void) => void
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -30,7 +30,7 @@ export const LiveStoreProvider = <GraphQLContext extends BaseGraphQLContext>({
|
|
|
30
30
|
children,
|
|
31
31
|
schema,
|
|
32
32
|
boot,
|
|
33
|
-
|
|
33
|
+
adapter,
|
|
34
34
|
batchUpdates,
|
|
35
35
|
}: LiveStoreProviderProps<GraphQLContext> & { children?: ReactNode }): JSX.Element => {
|
|
36
36
|
const storeCtx = useCreateStore({
|
|
@@ -39,7 +39,7 @@ export const LiveStoreProvider = <GraphQLContext extends BaseGraphQLContext>({
|
|
|
39
39
|
otelTracer,
|
|
40
40
|
otelRootSpanContext,
|
|
41
41
|
boot,
|
|
42
|
-
|
|
42
|
+
adapter,
|
|
43
43
|
batchUpdates,
|
|
44
44
|
})
|
|
45
45
|
|
|
@@ -58,7 +58,7 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
|
|
|
58
58
|
otelTracer,
|
|
59
59
|
otelRootSpanContext,
|
|
60
60
|
boot,
|
|
61
|
-
|
|
61
|
+
adapter,
|
|
62
62
|
batchUpdates,
|
|
63
63
|
}: LiveStoreCreateStoreOptions<GraphQLContext>) => {
|
|
64
64
|
const [_, rerender] = React.useState(0)
|
|
@@ -69,7 +69,7 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
|
|
|
69
69
|
otelTracer,
|
|
70
70
|
otelRootSpanContext,
|
|
71
71
|
boot,
|
|
72
|
-
|
|
72
|
+
adapter,
|
|
73
73
|
batchUpdates,
|
|
74
74
|
})
|
|
75
75
|
const oldStoreAlreadyDestroyedRef = React.useRef(false)
|
|
@@ -80,7 +80,7 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
|
|
|
80
80
|
inputPropsCacheRef.current.otelTracer !== otelTracer ||
|
|
81
81
|
inputPropsCacheRef.current.otelRootSpanContext !== otelRootSpanContext ||
|
|
82
82
|
inputPropsCacheRef.current.boot !== boot ||
|
|
83
|
-
inputPropsCacheRef.current.
|
|
83
|
+
inputPropsCacheRef.current.adapter !== adapter ||
|
|
84
84
|
inputPropsCacheRef.current.batchUpdates !== batchUpdates
|
|
85
85
|
) {
|
|
86
86
|
inputPropsCacheRef.current = {
|
|
@@ -89,7 +89,7 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
|
|
|
89
89
|
otelTracer,
|
|
90
90
|
otelRootSpanContext,
|
|
91
91
|
boot,
|
|
92
|
-
|
|
92
|
+
adapter,
|
|
93
93
|
batchUpdates,
|
|
94
94
|
}
|
|
95
95
|
ctxValueRef.current?.store.destroy()
|
|
@@ -108,7 +108,7 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
|
|
|
108
108
|
otelTracer,
|
|
109
109
|
otelRootSpanContext,
|
|
110
110
|
boot,
|
|
111
|
-
|
|
111
|
+
adapter,
|
|
112
112
|
batchUpdates,
|
|
113
113
|
})
|
|
114
114
|
ctxValueRef.current = { store }
|
|
@@ -124,7 +124,7 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
|
|
|
124
124
|
store?.destroy()
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
|
-
}, [schema, graphQLOptions, otelTracer, otelRootSpanContext, boot,
|
|
127
|
+
}, [schema, graphQLOptions, otelTracer, otelRootSpanContext, boot, adapter, batchUpdates])
|
|
128
128
|
|
|
129
129
|
return ctxValueRef.current
|
|
130
130
|
}
|
package/src/react/index.ts
CHANGED
package/src/react/useAtom.ts
CHANGED
|
@@ -1,26 +1,52 @@
|
|
|
1
|
+
import { type QueryInfoCol, type QueryInfoRow } from '@livestore/common'
|
|
2
|
+
import type { DbSchema } from '@livestore/common/schema'
|
|
1
3
|
import React from 'react'
|
|
2
4
|
|
|
3
|
-
import { mutationForQueryInfo, type QueryInfoCol, type QueryInfoRow } from '../query-info.js'
|
|
4
5
|
import type { LiveQuery } from '../reactiveQueries/base-class.js'
|
|
5
6
|
import { useStore } from './LiveStoreContext.js'
|
|
6
7
|
import { useQueryRef } from './useQuery.js'
|
|
7
8
|
import type { Dispatch, SetStateAction } from './useRow.js'
|
|
8
9
|
|
|
9
|
-
export const useAtom = <
|
|
10
|
+
export const useAtom = <
|
|
11
|
+
TQuery extends LiveQuery<any, QueryInfoRow<TTableDef> | QueryInfoCol<TTableDef, any>>,
|
|
12
|
+
TTableDef extends DbSchema.TableDef<
|
|
13
|
+
DbSchema.DefaultSqliteTableDefConstrained,
|
|
14
|
+
boolean,
|
|
15
|
+
DbSchema.TableOptions & { deriveMutations: true }
|
|
16
|
+
>,
|
|
17
|
+
>(
|
|
10
18
|
query$: TQuery,
|
|
11
19
|
): [value: TQuery['__result!'], setValue: Dispatch<SetStateAction<Partial<TQuery['__result!']>>>] => {
|
|
12
20
|
const query$Ref = useQueryRef(query$)
|
|
13
21
|
|
|
14
22
|
const { store } = useStore()
|
|
15
23
|
|
|
24
|
+
// TODO make API equivalent to useRow
|
|
16
25
|
const setValue = React.useMemo<Dispatch<SetStateAction<TQuery['__result!']>>>(() => {
|
|
17
26
|
return (newValueOrFn: any) => {
|
|
18
27
|
const newValue = typeof newValueOrFn === 'function' ? newValueOrFn(query$Ref.current) : newValueOrFn
|
|
19
28
|
|
|
20
|
-
if (query$.queryInfo._tag === 'Row'
|
|
21
|
-
|
|
29
|
+
if (query$.queryInfo._tag === 'Row') {
|
|
30
|
+
if (query$.queryInfo.table.options.isSingleton && query$.queryInfo.table.isSingleColumn) {
|
|
31
|
+
store.mutate(query$.queryInfo.table.update(newValue))
|
|
32
|
+
} else if (query$.queryInfo.table.options.isSingleColumn) {
|
|
33
|
+
store.mutate(
|
|
34
|
+
query$.queryInfo.table.update({ where: { id: query$.queryInfo.id }, values: { value: newValue } }),
|
|
35
|
+
)
|
|
36
|
+
} else {
|
|
37
|
+
store.mutate(query$.queryInfo.table.update({ where: { id: query$.queryInfo.id }, values: newValue }))
|
|
38
|
+
}
|
|
22
39
|
} else {
|
|
23
|
-
|
|
40
|
+
if (query$.queryInfo.table.options.isSingleton && query$.queryInfo.table.isSingleColumn) {
|
|
41
|
+
store.mutate(query$.queryInfo.table.update({ [query$.queryInfo.column]: newValue }))
|
|
42
|
+
} else {
|
|
43
|
+
store.mutate(
|
|
44
|
+
query$.queryInfo.table.update({
|
|
45
|
+
where: { id: query$.queryInfo.id },
|
|
46
|
+
values: { [query$.queryInfo.column]: newValue },
|
|
47
|
+
}),
|
|
48
|
+
)
|
|
49
|
+
}
|
|
24
50
|
}
|
|
25
51
|
}
|
|
26
52
|
}, [query$.queryInfo, query$Ref, store])
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { cuid } from '@livestore/utils/cuid'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
type LocalIdOptions = {
|
|
5
|
+
key: string
|
|
6
|
+
storageType: 'session' | 'local'
|
|
7
|
+
storageKeyPrefix: string
|
|
8
|
+
makeId: () => string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const useLocalId = (opts?: Partial<LocalIdOptions>) => React.useMemo(() => getLocalId(opts), [opts])
|
|
12
|
+
|
|
13
|
+
export const getLocalId = (opts?: Partial<LocalIdOptions>) => {
|
|
14
|
+
const { key = '', storageType = 'session', storageKeyPrefix = 'livestore:localid:', makeId = cuid } = opts ?? {}
|
|
15
|
+
|
|
16
|
+
const storage = storageType === 'session' ? window.sessionStorage : window.localStorage
|
|
17
|
+
const fullKey = `${storageKeyPrefix}:${key}`
|
|
18
|
+
const storedKey = storage.getItem(fullKey)
|
|
19
|
+
|
|
20
|
+
if (storedKey) return storedKey
|
|
21
|
+
|
|
22
|
+
const newKey = makeId()
|
|
23
|
+
storage.setItem(fullKey, newKey)
|
|
24
|
+
return newKey
|
|
25
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { renderHook } from '@testing-library/react'
|
|
2
2
|
import React from 'react'
|
|
3
3
|
import { describe, expect, it } from 'vitest'
|
|
4
4
|
|
|
5
|
-
import { makeTodoMvc, parseTodos } from '../__tests__/react/fixture.js'
|
|
5
|
+
import { makeTodoMvc, parseTodos, todos } from '../__tests__/react/fixture.js'
|
|
6
6
|
import { querySQL } from '../reactiveQueries/sql.js'
|
|
7
7
|
import * as LiveStoreReact from './index.js'
|
|
8
8
|
|
|
9
9
|
describe('useQuery', () => {
|
|
10
10
|
it('simple', async () => {
|
|
11
|
-
const { wrapper, store,
|
|
11
|
+
const { wrapper, store, makeRenderCount } = await makeTodoMvc()
|
|
12
12
|
|
|
13
13
|
const renderCount = makeRenderCount()
|
|
14
14
|
|
|
@@ -26,7 +26,7 @@ describe('useQuery', () => {
|
|
|
26
26
|
expect(result.current.length).toBe(0)
|
|
27
27
|
expect(renderCount.val).toBe(1)
|
|
28
28
|
|
|
29
|
-
act(() => store.mutate(
|
|
29
|
+
React.act(() => store.mutate(todos.insert({ id: 't1', text: 'buy milk', completed: false })))
|
|
30
30
|
|
|
31
31
|
expect(result.current.length).toBe(1)
|
|
32
32
|
expect(result.current[0]!.text).toBe('buy milk')
|
|
@@ -34,7 +34,7 @@ describe('useQuery', () => {
|
|
|
34
34
|
})
|
|
35
35
|
|
|
36
36
|
it('same `useQuery` hook invoked with different queries', async () => {
|
|
37
|
-
const { wrapper, store,
|
|
37
|
+
const { wrapper, store, makeRenderCount } = await makeTodoMvc()
|
|
38
38
|
|
|
39
39
|
const renderCount = makeRenderCount()
|
|
40
40
|
|
|
@@ -42,8 +42,8 @@ describe('useQuery', () => {
|
|
|
42
42
|
const todo2$ = querySQL(`select * from todos where id = 't2'`, { label: 'libraryTracksView2', map: parseTodos })
|
|
43
43
|
|
|
44
44
|
store.mutate(
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
todos.insert({ id: 't1', text: 'buy milk', completed: false }),
|
|
46
|
+
todos.insert({ id: 't2', text: 'buy eggs', completed: false }),
|
|
47
47
|
)
|
|
48
48
|
|
|
49
49
|
const { result, rerender } = renderHook(
|
|
@@ -60,7 +60,7 @@ describe('useQuery', () => {
|
|
|
60
60
|
expect(result.current).toBe('buy milk')
|
|
61
61
|
expect(renderCount.val).toBe(1)
|
|
62
62
|
|
|
63
|
-
act(() => store.mutate(
|
|
63
|
+
React.act(() => store.mutate(todos.update({ where: { id: 't1' }, values: { text: 'buy soy milk' } })))
|
|
64
64
|
|
|
65
65
|
expect(result.current).toBe('buy soy milk')
|
|
66
66
|
expect(renderCount.val).toBe(2)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ReadonlyRecord } from '@livestore/utils/effect'
|
|
2
2
|
import * as otel from '@opentelemetry/api'
|
|
3
3
|
import { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
|
|
4
|
-
import {
|
|
4
|
+
import { render, renderHook } from '@testing-library/react'
|
|
5
5
|
import React from 'react'
|
|
6
6
|
import { describe, expect, it } from 'vitest'
|
|
7
7
|
|
|
@@ -9,7 +9,6 @@ import type { Todo } from '../__tests__/react/fixture.js'
|
|
|
9
9
|
import { makeTodoMvc, todos } from '../__tests__/react/fixture.js'
|
|
10
10
|
import { getSimplifiedRootSpan } from '../__tests__/react/utils/otel.js'
|
|
11
11
|
import * as LiveStore from '../index.js'
|
|
12
|
-
import { mutationForQueryInfo } from '../query-info.js'
|
|
13
12
|
import * as LiveStoreReact from './index.js'
|
|
14
13
|
import type { StackInfo } from './utils/stack-info.js'
|
|
15
14
|
|
|
@@ -36,7 +35,7 @@ describe.concurrent('useRow', () => {
|
|
|
36
35
|
expect(result.current.state.username).toBe('')
|
|
37
36
|
expect(renderCount.val).toBe(1)
|
|
38
37
|
|
|
39
|
-
act(() =>
|
|
38
|
+
React.act(() =>
|
|
40
39
|
store.mutate(
|
|
41
40
|
LiveStore.rawSqlMutation({
|
|
42
41
|
sql: LiveStore.sql`INSERT INTO UserInfo (id, username) VALUES ('u2', 'username_u2')`,
|
|
@@ -51,6 +50,8 @@ describe.concurrent('useRow', () => {
|
|
|
51
50
|
expect(renderCount.val).toBe(2)
|
|
52
51
|
})
|
|
53
52
|
|
|
53
|
+
// TODO add a test that makes sure React doesn't re-render when a setter is used to set the same value
|
|
54
|
+
|
|
54
55
|
it('should update the data reactively - via setState', async () => {
|
|
55
56
|
using inputs = await makeTodoMvc({ useGlobalDbGraph: false })
|
|
56
57
|
|
|
@@ -72,7 +73,7 @@ describe.concurrent('useRow', () => {
|
|
|
72
73
|
expect(result.current.state.username).toBe('')
|
|
73
74
|
expect(renderCount.val).toBe(1)
|
|
74
75
|
|
|
75
|
-
act(() => result.current.setState.username('username_u1_hello'))
|
|
76
|
+
React.act(() => result.current.setState.username('username_u1_hello'))
|
|
76
77
|
|
|
77
78
|
expect(result.current.state.id).toBe('u1')
|
|
78
79
|
expect(result.current.state.username).toBe('username_u1_hello')
|
|
@@ -100,7 +101,7 @@ describe.concurrent('useRow', () => {
|
|
|
100
101
|
expect(result.current.state.username).toBe('')
|
|
101
102
|
expect(renderCount.val).toBe(1)
|
|
102
103
|
|
|
103
|
-
act(() =>
|
|
104
|
+
React.act(() =>
|
|
104
105
|
store.mutate(
|
|
105
106
|
LiveStore.rawSqlMutation({
|
|
106
107
|
sql: LiveStore.sql`UPDATE UserInfo SET username = 'username_u1_hello' WHERE id = 'u1';`,
|
|
@@ -115,18 +116,10 @@ describe.concurrent('useRow', () => {
|
|
|
115
116
|
|
|
116
117
|
it('should work for a larger app', async () => {
|
|
117
118
|
using inputs = await makeTodoMvc({ useGlobalDbGraph: false })
|
|
118
|
-
const { wrapper, store, dbGraph, makeRenderCount } = inputs
|
|
119
|
+
const { wrapper, store, dbGraph, makeRenderCount, AppRouterSchema } = inputs
|
|
119
120
|
|
|
120
121
|
const allTodos$ = LiveStore.querySQL<Todo[]>(`select * from todos`, { label: 'allTodos', dbGraph })
|
|
121
122
|
|
|
122
|
-
const AppRouterSchema = LiveStore.DbSchema.table(
|
|
123
|
-
'AppRouter',
|
|
124
|
-
{
|
|
125
|
-
currentTaskId: LiveStore.DbSchema.text({ default: null, nullable: true }),
|
|
126
|
-
},
|
|
127
|
-
{ isSingleton: true },
|
|
128
|
-
)
|
|
129
|
-
|
|
130
123
|
const appRouterRenderCount = makeRenderCount()
|
|
131
124
|
let globalSetState: LiveStoreReact.StateSetters<typeof AppRouterSchema> | undefined
|
|
132
125
|
const AppRouter: React.FC = () => {
|
|
@@ -168,7 +161,7 @@ describe.concurrent('useRow', () => {
|
|
|
168
161
|
|
|
169
162
|
expect(appRouterRenderCount.val).toBe(1)
|
|
170
163
|
|
|
171
|
-
act(() =>
|
|
164
|
+
React.act(() =>
|
|
172
165
|
store.mutate(
|
|
173
166
|
LiveStore.rawSqlMutation({
|
|
174
167
|
sql: LiveStore.sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)`,
|
|
@@ -179,7 +172,7 @@ describe.concurrent('useRow', () => {
|
|
|
179
172
|
expect(appRouterRenderCount.val).toBe(1)
|
|
180
173
|
expect(renderResult.getByRole('current-id').innerHTML).toMatchInlineSnapshot('"Current Task Id: -"')
|
|
181
174
|
|
|
182
|
-
act(() => globalSetState!.currentTaskId('t1'))
|
|
175
|
+
React.act(() => globalSetState!.currentTaskId('t1'))
|
|
183
176
|
|
|
184
177
|
expect(appRouterRenderCount.val).toBe(2)
|
|
185
178
|
expect(renderResult.getByRole('content').innerHTML).toMatchInlineSnapshot(
|
|
@@ -188,12 +181,12 @@ describe.concurrent('useRow', () => {
|
|
|
188
181
|
|
|
189
182
|
expect(renderResult.getByRole('current-id').innerHTML).toMatchInlineSnapshot('"Current Task Id: t1"')
|
|
190
183
|
|
|
191
|
-
act(() =>
|
|
184
|
+
React.act(() =>
|
|
192
185
|
store.mutate(
|
|
193
186
|
LiveStore.rawSqlMutation({
|
|
194
187
|
sql: LiveStore.sql`INSERT INTO todos (id, text, completed) VALUES ('t2', 'buy eggs', 0)`,
|
|
195
188
|
}),
|
|
196
|
-
|
|
189
|
+
AppRouterSchema.update({ where: { id: 'singleton' }, values: { currentTaskId: 't2' } }),
|
|
197
190
|
LiveStore.rawSqlMutation({
|
|
198
191
|
sql: LiveStore.sql`INSERT INTO todos (id, text, completed) VALUES ('t3', 'buy bread', 0)`,
|
|
199
192
|
}),
|
|
@@ -206,12 +199,12 @@ describe.concurrent('useRow', () => {
|
|
|
206
199
|
|
|
207
200
|
it('should work for a useRow query chained with a useTemporary query', async () => {
|
|
208
201
|
using inputs = await makeTodoMvc({ useGlobalDbGraph: false })
|
|
209
|
-
const { store, wrapper, AppComponentSchema, dbGraph, makeRenderCount
|
|
202
|
+
const { store, wrapper, AppComponentSchema, dbGraph, makeRenderCount } = inputs
|
|
210
203
|
const renderCount = makeRenderCount()
|
|
211
204
|
|
|
212
205
|
store.mutate(
|
|
213
|
-
|
|
214
|
-
|
|
206
|
+
todos.insert({ id: 't1', text: 'buy milk', completed: false }),
|
|
207
|
+
todos.insert({ id: 't2', text: 'buy bread', completed: false }),
|
|
215
208
|
)
|
|
216
209
|
|
|
217
210
|
const { result, unmount, rerender } = renderHook(
|
|
@@ -233,7 +226,7 @@ describe.concurrent('useRow', () => {
|
|
|
233
226
|
{ wrapper, initialProps: 'u1' },
|
|
234
227
|
)
|
|
235
228
|
|
|
236
|
-
act(() =>
|
|
229
|
+
React.act(() =>
|
|
237
230
|
store.mutate(
|
|
238
231
|
LiveStore.rawSqlMutation({
|
|
239
232
|
sql: LiveStore.sql`INSERT INTO UserInfo (id, username, text) VALUES ('u2', 'username_u2', 'milk')`,
|
|
@@ -289,7 +282,7 @@ describe.concurrent('useRow', () => {
|
|
|
289
282
|
expect(result.current.state.username).toBe('')
|
|
290
283
|
expect(renderCount.val).toBe(1)
|
|
291
284
|
|
|
292
|
-
act(() =>
|
|
285
|
+
React.act(() =>
|
|
293
286
|
store.mutate(
|
|
294
287
|
LiveStore.rawSqlMutation({
|
|
295
288
|
sql: LiveStore.sql`INSERT INTO UserInfo (id, username) VALUES ('u2', 'username_u2')`,
|
|
@@ -346,10 +339,18 @@ describe.concurrent('useRow', () => {
|
|
|
346
339
|
"_name": "sql-in-memory-select",
|
|
347
340
|
"attributes": {
|
|
348
341
|
"sql.cached": false,
|
|
349
|
-
"sql.query": "
|
|
342
|
+
"sql.query": "select 1 from UserInfo where id = 'u1'",
|
|
350
343
|
"sql.rowsCount": 0,
|
|
351
344
|
},
|
|
352
345
|
},
|
|
346
|
+
{
|
|
347
|
+
"_name": "sql-in-memory-select",
|
|
348
|
+
"attributes": {
|
|
349
|
+
"sql.cached": false,
|
|
350
|
+
"sql.query": "select 1 from UserInfo where id = 'u2'",
|
|
351
|
+
"sql.rowsCount": 1,
|
|
352
|
+
},
|
|
353
|
+
},
|
|
353
354
|
{
|
|
354
355
|
"_name": "LiveStore:mutations",
|
|
355
356
|
"children": [
|
|
@@ -415,10 +416,21 @@ describe.concurrent('useRow', () => {
|
|
|
415
416
|
},
|
|
416
417
|
"children": [
|
|
417
418
|
{
|
|
418
|
-
"_name": "
|
|
419
|
+
"_name": "LiveStore:mutatetWithoutRefresh",
|
|
419
420
|
"attributes": {
|
|
420
|
-
"
|
|
421
|
+
"livestore.args": "{
|
|
422
|
+
"id": "u1"
|
|
423
|
+
}",
|
|
424
|
+
"livestore.mutation": "_Derived_Create_UserInfo",
|
|
421
425
|
},
|
|
426
|
+
"children": [
|
|
427
|
+
{
|
|
428
|
+
"_name": "livestore.in-memory-db:execute",
|
|
429
|
+
"attributes": {
|
|
430
|
+
"sql.query": "INSERT INTO UserInfo (username, text, id) VALUES ($username, $text, $id)",
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
],
|
|
422
434
|
},
|
|
423
435
|
{
|
|
424
436
|
"_name": "LiveStore:useQuery:sql(rowQuery:query:UserInfo:u1)",
|
|
@@ -490,10 +502,18 @@ describe.concurrent('useRow', () => {
|
|
|
490
502
|
"_name": "sql-in-memory-select",
|
|
491
503
|
"attributes": {
|
|
492
504
|
"sql.cached": false,
|
|
493
|
-
"sql.query": "
|
|
505
|
+
"sql.query": "select 1 from UserInfo where id = 'u1'",
|
|
494
506
|
"sql.rowsCount": 0,
|
|
495
507
|
},
|
|
496
508
|
},
|
|
509
|
+
{
|
|
510
|
+
"_name": "sql-in-memory-select",
|
|
511
|
+
"attributes": {
|
|
512
|
+
"sql.cached": false,
|
|
513
|
+
"sql.query": "select 1 from UserInfo where id = 'u2'",
|
|
514
|
+
"sql.rowsCount": 1,
|
|
515
|
+
},
|
|
516
|
+
},
|
|
497
517
|
{
|
|
498
518
|
"_name": "LiveStore:mutations",
|
|
499
519
|
"children": [
|
|
@@ -559,10 +579,21 @@ describe.concurrent('useRow', () => {
|
|
|
559
579
|
},
|
|
560
580
|
"children": [
|
|
561
581
|
{
|
|
562
|
-
"_name": "
|
|
582
|
+
"_name": "LiveStore:mutatetWithoutRefresh",
|
|
563
583
|
"attributes": {
|
|
564
|
-
"
|
|
584
|
+
"livestore.args": "{
|
|
585
|
+
"id": "u1"
|
|
586
|
+
}",
|
|
587
|
+
"livestore.mutation": "_Derived_Create_UserInfo",
|
|
565
588
|
},
|
|
589
|
+
"children": [
|
|
590
|
+
{
|
|
591
|
+
"_name": "livestore.in-memory-db:execute",
|
|
592
|
+
"attributes": {
|
|
593
|
+
"sql.query": "INSERT INTO UserInfo (username, text, id) VALUES ($username, $text, $id)",
|
|
594
|
+
},
|
|
595
|
+
},
|
|
596
|
+
],
|
|
566
597
|
},
|
|
567
598
|
{
|
|
568
599
|
"_name": "LiveStore:useQuery:sql(rowQuery:query:UserInfo:u1)",
|
|
@@ -605,12 +636,6 @@ describe.concurrent('useRow', () => {
|
|
|
605
636
|
"id": "u2",
|
|
606
637
|
},
|
|
607
638
|
"children": [
|
|
608
|
-
{
|
|
609
|
-
"_name": "livestore.in-memory-db:execute",
|
|
610
|
-
"attributes": {
|
|
611
|
-
"sql.query": "insert into UserInfo (username, text, id) select $username, $text, $id where not exists(select 1 from UserInfo where id = 'u2')",
|
|
612
|
-
},
|
|
613
|
-
},
|
|
614
639
|
{
|
|
615
640
|
"_name": "LiveStore:useQuery:sql(rowQuery:query:UserInfo:u2)",
|
|
616
641
|
"attributes": {
|
package/src/react/useRow.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import type { QueryInfo } from '@livestore/common'
|
|
2
|
+
import {} from // deriveCreateMutationDef as deriveCreateMutationDef_,
|
|
3
|
+
// updateMutationForQueryInfo as updateMutationForQueryInfo_,
|
|
4
|
+
'@livestore/common'
|
|
1
5
|
import { DbSchema } from '@livestore/common/schema'
|
|
6
|
+
import { shouldNeverHappen } from '@livestore/utils'
|
|
2
7
|
import type { SqliteDsl } from 'effect-db-schema'
|
|
3
8
|
import { mapValues } from 'lodash-es'
|
|
4
9
|
import React from 'react'
|
|
5
10
|
|
|
6
11
|
import type { DbGraph, LiveQuery } from '../index.js'
|
|
7
|
-
import type { QueryInfo } from '../query-info.js'
|
|
8
|
-
import { mutationForQueryInfo } from '../query-info.js'
|
|
9
12
|
import type { RowResult } from '../row-query.js'
|
|
10
13
|
import { rowQuery } from '../row-query.js'
|
|
11
14
|
import { useStore } from './LiveStoreContext.js'
|
|
@@ -40,7 +43,7 @@ export const useRow: {
|
|
|
40
43
|
TTableDef extends DbSchema.TableDef<
|
|
41
44
|
DbSchema.DefaultSqliteTableDef,
|
|
42
45
|
boolean,
|
|
43
|
-
DbSchema.TableOptions & { isSingleton: true }
|
|
46
|
+
DbSchema.TableOptions & { isSingleton: true; deriveMutations: true }
|
|
44
47
|
>,
|
|
45
48
|
>(
|
|
46
49
|
table: TTableDef,
|
|
@@ -50,7 +53,7 @@ export const useRow: {
|
|
|
50
53
|
TTableDef extends DbSchema.TableDef<
|
|
51
54
|
DbSchema.DefaultSqliteTableDef,
|
|
52
55
|
boolean,
|
|
53
|
-
DbSchema.TableOptions & { isSingleton: false }
|
|
56
|
+
DbSchema.TableOptions & { isSingleton: false; deriveMutations: true }
|
|
54
57
|
>,
|
|
55
58
|
>(
|
|
56
59
|
table: TTableDef,
|
|
@@ -58,7 +61,13 @@ export const useRow: {
|
|
|
58
61
|
id: string,
|
|
59
62
|
options?: UseRowOptionsBase & UseRowOptionsDefaulValues<TTableDef>,
|
|
60
63
|
): UseRowResult<TTableDef>
|
|
61
|
-
} = <
|
|
64
|
+
} = <
|
|
65
|
+
TTableDef extends DbSchema.TableDef<
|
|
66
|
+
DbSchema.DefaultSqliteTableDefConstrained,
|
|
67
|
+
boolean,
|
|
68
|
+
DbSchema.TableOptions & { deriveMutations: true }
|
|
69
|
+
>,
|
|
70
|
+
>(
|
|
62
71
|
table: TTableDef,
|
|
63
72
|
idOrOptions?: string | UseRowOptionsBase,
|
|
64
73
|
options_?: UseRowOptionsBase & UseRowOptionsDefaulValues<TTableDef>,
|
|
@@ -68,11 +77,25 @@ export const useRow: {
|
|
|
68
77
|
const options: (UseRowOptionsBase & UseRowOptionsDefaulValues<TTableDef>) | undefined =
|
|
69
78
|
typeof idOrOptions === 'string' ? options_ : idOrOptions
|
|
70
79
|
const { defaultValues, dbGraph } = options ?? {}
|
|
80
|
+
|
|
71
81
|
type TComponentState = SqliteDsl.FromColumns.RowDecoded<TTableDef['sqliteDef']['columns']>
|
|
72
82
|
|
|
83
|
+
const tableName = table.sqliteDef.name
|
|
84
|
+
|
|
85
|
+
if (DbSchema.tableHasDerivedMutations(table) === false) {
|
|
86
|
+
shouldNeverHappen(`useRow called on table "${tableName}" which does not have 'deriveMutations: true' set`)
|
|
87
|
+
}
|
|
88
|
+
|
|
73
89
|
const { store } = useStore()
|
|
74
90
|
|
|
75
|
-
|
|
91
|
+
if (
|
|
92
|
+
store.schema.tables.has(table.sqliteDef.name) === false &&
|
|
93
|
+
table.sqliteDef.name.startsWith('__livestore') === false
|
|
94
|
+
) {
|
|
95
|
+
shouldNeverHappen(`Table "${table.sqliteDef.name}" not found in schema`)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// console.debug('useRow', tableName, id)
|
|
76
99
|
|
|
77
100
|
const { query$, otelContext } = useMakeTemporaryQuery(
|
|
78
101
|
(otelContext) =>
|
|
@@ -83,10 +106,10 @@ export const useRow: {
|
|
|
83
106
|
defaultValues: defaultValues!,
|
|
84
107
|
dbGraph,
|
|
85
108
|
}) as any as LiveQuery<RowResult<TTableDef>, QueryInfo>),
|
|
86
|
-
[id!,
|
|
109
|
+
[id!, tableName],
|
|
87
110
|
{
|
|
88
111
|
otel: {
|
|
89
|
-
spanName: `LiveStore:useRow:${
|
|
112
|
+
spanName: `LiveStore:useRow:${tableName}${id === undefined ? '' : `:${id}`}`,
|
|
90
113
|
attributes: { id },
|
|
91
114
|
},
|
|
92
115
|
},
|
|
@@ -100,7 +123,13 @@ export const useRow: {
|
|
|
100
123
|
const newValue = typeof newValueOrFn === 'function' ? newValueOrFn(query$Ref.current) : newValueOrFn
|
|
101
124
|
if (query$Ref.current === newValue) return
|
|
102
125
|
|
|
103
|
-
|
|
126
|
+
// NOTE we need to account for the short-hand syntax for single-column+singleton tables
|
|
127
|
+
if (table.options.isSingleton) {
|
|
128
|
+
store.mutate(table.update(newValue))
|
|
129
|
+
} else {
|
|
130
|
+
store.mutate(table.update({ where: { id }, values: { value: newValue } }))
|
|
131
|
+
}
|
|
132
|
+
// store.mutate(updateMutationForQueryInfo(query$.queryInfo!, { value: newValue }))
|
|
104
133
|
}
|
|
105
134
|
} else {
|
|
106
135
|
const setState = // TODO: do we have a better type for the values that can go in SQLite?
|
|
@@ -113,7 +142,8 @@ export const useRow: {
|
|
|
113
142
|
// @ts-expect-error TODO fix typing
|
|
114
143
|
if (query$Ref.current[columnName] === newValue) return
|
|
115
144
|
|
|
116
|
-
store.mutate(
|
|
145
|
+
store.mutate(table.update({ where: { id: id ?? 'singleton' }, values: { [columnName]: newValue } }))
|
|
146
|
+
// store.mutate(updateMutationForQueryInfo(query$.queryInfo!, { [columnName]: newValue }))
|
|
117
147
|
})
|
|
118
148
|
|
|
119
149
|
setState.setMany = (columnValuesOrFn: Partial<TComponentState>) => {
|
|
@@ -130,12 +160,13 @@ export const useRow: {
|
|
|
130
160
|
return
|
|
131
161
|
}
|
|
132
162
|
|
|
133
|
-
store.mutate(
|
|
163
|
+
store.mutate(table.update({ where: { id: id ?? 'singleton' }, values: columnValues }))
|
|
164
|
+
// store.mutate(updateMutationForQueryInfo(query$.queryInfo!, columnValues))
|
|
134
165
|
}
|
|
135
166
|
|
|
136
167
|
return setState as any
|
|
137
168
|
}
|
|
138
|
-
}, [
|
|
169
|
+
}, [id, query$Ref, sqliteTableDef.columns, store, table])
|
|
139
170
|
|
|
140
171
|
return [query$Ref.current, setState, query$]
|
|
141
172
|
}
|