@livestore/livestore 0.0.58-dev.8 → 0.1.0-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 +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
|
@@ -1,345 +0,0 @@
|
|
|
1
|
-
import { Effect, ReadonlyRecord, Schema } from '@livestore/utils/effect'
|
|
2
|
-
import * as otel from '@opentelemetry/api'
|
|
3
|
-
import { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
|
|
4
|
-
import { render, renderHook } from '@testing-library/react'
|
|
5
|
-
import React from 'react'
|
|
6
|
-
import { describe, expect, it } from 'vitest'
|
|
7
|
-
|
|
8
|
-
import { makeTodoMvc, tables, todos } from '../__tests__/react/fixture.js'
|
|
9
|
-
import { getSimplifiedRootSpan } from '../__tests__/react/utils/otel.js'
|
|
10
|
-
import * as LiveStore from '../index.js'
|
|
11
|
-
import * as LiveStoreReact from './index.js'
|
|
12
|
-
import type { StackInfo } from './utils/stack-info.js'
|
|
13
|
-
|
|
14
|
-
// NOTE running tests concurrently doesn't work with the default global db graph
|
|
15
|
-
describe('useRow', () => {
|
|
16
|
-
it('should update the data based on component key', () =>
|
|
17
|
-
Effect.gen(function* () {
|
|
18
|
-
const { wrapper, AppComponentSchema, store, reactivityGraph, makeRenderCount } = yield* makeTodoMvc({
|
|
19
|
-
useGlobalReactivityGraph: false,
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
const renderCount = makeRenderCount()
|
|
23
|
-
|
|
24
|
-
const { result, rerender } = renderHook(
|
|
25
|
-
(userId: string) => {
|
|
26
|
-
renderCount.inc()
|
|
27
|
-
|
|
28
|
-
const [state, setState] = LiveStoreReact.useRow(AppComponentSchema, userId, { reactivityGraph })
|
|
29
|
-
return { state, setState }
|
|
30
|
-
},
|
|
31
|
-
{ wrapper, initialProps: 'u1' },
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
expect(result.current.state.id).toBe('u1')
|
|
35
|
-
expect(result.current.state.username).toBe('')
|
|
36
|
-
expect(renderCount.val).toBe(1)
|
|
37
|
-
|
|
38
|
-
React.act(() =>
|
|
39
|
-
store.mutate(
|
|
40
|
-
LiveStore.rawSqlMutation({
|
|
41
|
-
sql: LiveStore.sql`INSERT INTO UserInfo (id, username) VALUES ('u2', 'username_u2')`,
|
|
42
|
-
}),
|
|
43
|
-
),
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
rerender('u2')
|
|
47
|
-
|
|
48
|
-
expect(result.current.state.id).toBe('u2')
|
|
49
|
-
expect(result.current.state.username).toBe('username_u2')
|
|
50
|
-
expect(renderCount.val).toBe(2)
|
|
51
|
-
}).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise))
|
|
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
|
-
|
|
55
|
-
it('should update the data reactively - via setState', () =>
|
|
56
|
-
Effect.gen(function* () {
|
|
57
|
-
const { wrapper, AppComponentSchema, reactivityGraph, makeRenderCount } = yield* makeTodoMvc({
|
|
58
|
-
useGlobalReactivityGraph: false,
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
const renderCount = makeRenderCount()
|
|
62
|
-
|
|
63
|
-
const { result } = renderHook(
|
|
64
|
-
(userId: string) => {
|
|
65
|
-
renderCount.inc()
|
|
66
|
-
|
|
67
|
-
const [state, setState] = LiveStoreReact.useRow(AppComponentSchema, userId, { reactivityGraph })
|
|
68
|
-
return { state, setState }
|
|
69
|
-
},
|
|
70
|
-
{ wrapper, initialProps: 'u1' },
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
expect(result.current.state.id).toBe('u1')
|
|
74
|
-
expect(result.current.state.username).toBe('')
|
|
75
|
-
expect(renderCount.val).toBe(1)
|
|
76
|
-
|
|
77
|
-
React.act(() => result.current.setState.username('username_u1_hello'))
|
|
78
|
-
|
|
79
|
-
expect(result.current.state.id).toBe('u1')
|
|
80
|
-
expect(result.current.state.username).toBe('username_u1_hello')
|
|
81
|
-
expect(renderCount.val).toBe(2)
|
|
82
|
-
}).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise))
|
|
83
|
-
|
|
84
|
-
it('should update the data reactively - via raw store mutation', () =>
|
|
85
|
-
Effect.gen(function* () {
|
|
86
|
-
const { wrapper, AppComponentSchema, store, reactivityGraph, makeRenderCount } = yield* makeTodoMvc({
|
|
87
|
-
useGlobalReactivityGraph: false,
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
const renderCount = makeRenderCount()
|
|
91
|
-
|
|
92
|
-
const { result } = renderHook(
|
|
93
|
-
(userId: string) => {
|
|
94
|
-
renderCount.inc()
|
|
95
|
-
|
|
96
|
-
const [state, setState] = LiveStoreReact.useRow(AppComponentSchema, userId, { reactivityGraph })
|
|
97
|
-
return { state, setState }
|
|
98
|
-
},
|
|
99
|
-
{ wrapper, initialProps: 'u1' },
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
expect(result.current.state.id).toBe('u1')
|
|
103
|
-
expect(result.current.state.username).toBe('')
|
|
104
|
-
expect(renderCount.val).toBe(1)
|
|
105
|
-
|
|
106
|
-
React.act(() =>
|
|
107
|
-
store.mutate(
|
|
108
|
-
LiveStore.rawSqlMutation({
|
|
109
|
-
sql: LiveStore.sql`UPDATE UserInfo SET username = 'username_u1_hello' WHERE id = 'u1';`,
|
|
110
|
-
}),
|
|
111
|
-
),
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
expect(result.current.state.id).toBe('u1')
|
|
115
|
-
expect(result.current.state.username).toBe('username_u1_hello')
|
|
116
|
-
expect(renderCount.val).toBe(2)
|
|
117
|
-
}).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise))
|
|
118
|
-
|
|
119
|
-
it('should work for a larger app', () =>
|
|
120
|
-
Effect.gen(function* () {
|
|
121
|
-
const { wrapper, store, reactivityGraph, makeRenderCount, AppRouterSchema } = yield* makeTodoMvc({
|
|
122
|
-
useGlobalReactivityGraph: false,
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
const allTodos$ = LiveStore.querySQL(`select * from todos`, {
|
|
126
|
-
label: 'allTodos',
|
|
127
|
-
schema: Schema.Array(tables.todos.schema),
|
|
128
|
-
reactivityGraph,
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
const appRouterRenderCount = makeRenderCount()
|
|
132
|
-
let globalSetState: LiveStoreReact.StateSetters<typeof AppRouterSchema> | undefined
|
|
133
|
-
const AppRouter: React.FC = () => {
|
|
134
|
-
appRouterRenderCount.inc()
|
|
135
|
-
|
|
136
|
-
const [state, setState] = LiveStoreReact.useRow(AppRouterSchema, { reactivityGraph })
|
|
137
|
-
|
|
138
|
-
globalSetState = setState
|
|
139
|
-
|
|
140
|
-
return (
|
|
141
|
-
<div>
|
|
142
|
-
<TasksList setTaskId={setState.currentTaskId} />
|
|
143
|
-
<div role="current-id">Current Task Id: {state.currentTaskId ?? '-'}</div>
|
|
144
|
-
{state.currentTaskId ? <TaskDetails id={state.currentTaskId} /> : <div>Click on a task to see details</div>}
|
|
145
|
-
</div>
|
|
146
|
-
)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const TasksList: React.FC<{ setTaskId: (_: string) => void }> = ({ setTaskId }) => {
|
|
150
|
-
const allTodos = LiveStoreReact.useQuery(allTodos$)
|
|
151
|
-
|
|
152
|
-
return (
|
|
153
|
-
<div>
|
|
154
|
-
{allTodos.map((_) => (
|
|
155
|
-
<div key={_.id} onClick={() => setTaskId(_.id)}>
|
|
156
|
-
{_.id}
|
|
157
|
-
</div>
|
|
158
|
-
))}
|
|
159
|
-
</div>
|
|
160
|
-
)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const TaskDetails: React.FC<{ id: string }> = ({ id }) => {
|
|
164
|
-
const [todo] = LiveStoreReact.useRow(todos, id, { reactivityGraph })
|
|
165
|
-
return <div role="content">{JSON.stringify(todo)}</div>
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const renderResult = render(<AppRouter />, { wrapper })
|
|
169
|
-
|
|
170
|
-
expect(appRouterRenderCount.val).toBe(1)
|
|
171
|
-
|
|
172
|
-
React.act(() =>
|
|
173
|
-
store.mutate(
|
|
174
|
-
LiveStore.rawSqlMutation({
|
|
175
|
-
sql: LiveStore.sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)`,
|
|
176
|
-
}),
|
|
177
|
-
),
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
expect(appRouterRenderCount.val).toBe(1)
|
|
181
|
-
expect(renderResult.getByRole('current-id').innerHTML).toMatchInlineSnapshot('"Current Task Id: -"')
|
|
182
|
-
|
|
183
|
-
React.act(() => globalSetState!.currentTaskId('t1'))
|
|
184
|
-
|
|
185
|
-
expect(appRouterRenderCount.val).toBe(2)
|
|
186
|
-
expect(renderResult.getByRole('content').innerHTML).toMatchInlineSnapshot(
|
|
187
|
-
`"{"id":"t1","text":"buy milk","completed":false}"`,
|
|
188
|
-
)
|
|
189
|
-
|
|
190
|
-
expect(renderResult.getByRole('current-id').innerHTML).toMatchInlineSnapshot('"Current Task Id: t1"')
|
|
191
|
-
|
|
192
|
-
React.act(() =>
|
|
193
|
-
store.mutate(
|
|
194
|
-
LiveStore.rawSqlMutation({
|
|
195
|
-
sql: LiveStore.sql`INSERT INTO todos (id, text, completed) VALUES ('t2', 'buy eggs', 0)`,
|
|
196
|
-
}),
|
|
197
|
-
AppRouterSchema.update({ where: { id: 'singleton' }, values: { currentTaskId: 't2' } }),
|
|
198
|
-
LiveStore.rawSqlMutation({
|
|
199
|
-
sql: LiveStore.sql`INSERT INTO todos (id, text, completed) VALUES ('t3', 'buy bread', 0)`,
|
|
200
|
-
}),
|
|
201
|
-
),
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
expect(appRouterRenderCount.val).toBe(3)
|
|
205
|
-
expect(renderResult.getByRole('current-id').innerHTML).toMatchInlineSnapshot('"Current Task Id: t2"')
|
|
206
|
-
}).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise))
|
|
207
|
-
|
|
208
|
-
it('should work for a useRow query chained with a useTemporary query', () =>
|
|
209
|
-
Effect.gen(function* () {
|
|
210
|
-
const { store, wrapper, AppComponentSchema, reactivityGraph, makeRenderCount } = yield* makeTodoMvc({
|
|
211
|
-
useGlobalReactivityGraph: false,
|
|
212
|
-
})
|
|
213
|
-
const renderCount = makeRenderCount()
|
|
214
|
-
|
|
215
|
-
store.mutate(
|
|
216
|
-
todos.insert({ id: 't1', text: 'buy milk', completed: false }),
|
|
217
|
-
todos.insert({ id: 't2', text: 'buy bread', completed: false }),
|
|
218
|
-
)
|
|
219
|
-
|
|
220
|
-
const { result, unmount, rerender } = renderHook(
|
|
221
|
-
(userId: string) => {
|
|
222
|
-
renderCount.inc()
|
|
223
|
-
|
|
224
|
-
const [_row, _setRow, rowState$] = LiveStoreReact.useRow(AppComponentSchema, userId, { reactivityGraph })
|
|
225
|
-
const todos = LiveStoreReact.useTemporaryQuery(
|
|
226
|
-
() =>
|
|
227
|
-
LiveStore.querySQL(
|
|
228
|
-
(get) => LiveStore.sql`select * from todos where text like '%${get(rowState$).text}%'`,
|
|
229
|
-
{
|
|
230
|
-
schema: Schema.Array(tables.todos.schema),
|
|
231
|
-
reactivityGraph,
|
|
232
|
-
label: 'todosFiltered',
|
|
233
|
-
},
|
|
234
|
-
),
|
|
235
|
-
userId,
|
|
236
|
-
)
|
|
237
|
-
|
|
238
|
-
return { todos }
|
|
239
|
-
},
|
|
240
|
-
{ wrapper, initialProps: 'u1' },
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
React.act(() =>
|
|
244
|
-
store.mutate(
|
|
245
|
-
LiveStore.rawSqlMutation({
|
|
246
|
-
sql: LiveStore.sql`INSERT INTO UserInfo (id, username, text) VALUES ('u2', 'username_u2', 'milk')`,
|
|
247
|
-
}),
|
|
248
|
-
),
|
|
249
|
-
)
|
|
250
|
-
|
|
251
|
-
expect(result.current.todos.length).toBe(2)
|
|
252
|
-
// expect(result.current.state.username).toBe('')
|
|
253
|
-
expect(renderCount.val).toBe(1)
|
|
254
|
-
|
|
255
|
-
rerender('u2')
|
|
256
|
-
|
|
257
|
-
expect(result.current.todos.length).toBe(1)
|
|
258
|
-
expect(renderCount.val).toBe(2)
|
|
259
|
-
|
|
260
|
-
unmount()
|
|
261
|
-
}).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise))
|
|
262
|
-
|
|
263
|
-
let cachedProvider: BasicTracerProvider | undefined
|
|
264
|
-
|
|
265
|
-
describe('otel', () => {
|
|
266
|
-
const exporter = new InMemorySpanExporter()
|
|
267
|
-
|
|
268
|
-
const provider = cachedProvider ?? new BasicTracerProvider()
|
|
269
|
-
cachedProvider = provider
|
|
270
|
-
provider.addSpanProcessor(new SimpleSpanProcessor(exporter))
|
|
271
|
-
provider.register()
|
|
272
|
-
|
|
273
|
-
const otelTracer = otel.trace.getTracer('test')
|
|
274
|
-
|
|
275
|
-
const span = otelTracer.startSpan('test')
|
|
276
|
-
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
277
|
-
|
|
278
|
-
it('should update the data based on component key', async () => {
|
|
279
|
-
const { strictMode } = await Effect.gen(function* () {
|
|
280
|
-
const { wrapper, AppComponentSchema, store, reactivityGraph, makeRenderCount, strictMode } = yield* makeTodoMvc(
|
|
281
|
-
{ useGlobalReactivityGraph: false, otelContext, otelTracer },
|
|
282
|
-
)
|
|
283
|
-
|
|
284
|
-
const renderCount = makeRenderCount()
|
|
285
|
-
|
|
286
|
-
const { result, rerender, unmount } = renderHook(
|
|
287
|
-
(userId: string) => {
|
|
288
|
-
renderCount.inc()
|
|
289
|
-
|
|
290
|
-
const [state, setState] = LiveStoreReact.useRow(AppComponentSchema, userId, { reactivityGraph })
|
|
291
|
-
return { state, setState }
|
|
292
|
-
},
|
|
293
|
-
{ wrapper, initialProps: 'u1' },
|
|
294
|
-
)
|
|
295
|
-
|
|
296
|
-
expect(result.current.state.id).toBe('u1')
|
|
297
|
-
expect(result.current.state.username).toBe('')
|
|
298
|
-
expect(renderCount.val).toBe(1)
|
|
299
|
-
|
|
300
|
-
React.act(() =>
|
|
301
|
-
store.mutate(
|
|
302
|
-
LiveStore.rawSqlMutation({
|
|
303
|
-
sql: LiveStore.sql`INSERT INTO UserInfo (id, username) VALUES ('u2', 'username_u2')`,
|
|
304
|
-
}),
|
|
305
|
-
),
|
|
306
|
-
)
|
|
307
|
-
|
|
308
|
-
rerender('u2')
|
|
309
|
-
|
|
310
|
-
expect(result.current.state.id).toBe('u2')
|
|
311
|
-
expect(result.current.state.username).toBe('username_u2')
|
|
312
|
-
expect(renderCount.val).toBe(2)
|
|
313
|
-
|
|
314
|
-
unmount()
|
|
315
|
-
span.end()
|
|
316
|
-
|
|
317
|
-
return { strictMode }
|
|
318
|
-
}).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise)
|
|
319
|
-
|
|
320
|
-
const mapAttributes = (attributes: otel.Attributes) => {
|
|
321
|
-
return ReadonlyRecord.map(attributes, (val, key) => {
|
|
322
|
-
if (key === 'stackInfo') {
|
|
323
|
-
const stackInfo = JSON.parse(val as string) as StackInfo
|
|
324
|
-
// stackInfo.frames.shift() // Removes `renderHook.wrapper` from the stack
|
|
325
|
-
stackInfo.frames.forEach((_) => {
|
|
326
|
-
if (_.name.includes('renderHook.wrapper')) {
|
|
327
|
-
_.name = 'renderHook.wrapper'
|
|
328
|
-
}
|
|
329
|
-
_.filePath = '__REPLACED_FOR_SNAPSHOT__'
|
|
330
|
-
})
|
|
331
|
-
return JSON.stringify(stackInfo)
|
|
332
|
-
}
|
|
333
|
-
return val
|
|
334
|
-
})
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// TODO improve testing setup so "obsolete" warning is avoided
|
|
338
|
-
if (strictMode) {
|
|
339
|
-
expect(getSimplifiedRootSpan(exporter, mapAttributes)).toMatchSnapshot('strictMode=true')
|
|
340
|
-
} else {
|
|
341
|
-
expect(getSimplifiedRootSpan(exporter, mapAttributes)).toMatchSnapshot('strictMode=false')
|
|
342
|
-
}
|
|
343
|
-
})
|
|
344
|
-
})
|
|
345
|
-
})
|
package/src/react/useRow.ts
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import type { QueryInfo } from '@livestore/common'
|
|
2
|
-
import { DbSchema } from '@livestore/common/schema'
|
|
3
|
-
import { shouldNeverHappen } from '@livestore/utils'
|
|
4
|
-
import { ReadonlyRecord } from '@livestore/utils/effect'
|
|
5
|
-
import type { SqliteDsl } from 'effect-db-schema'
|
|
6
|
-
import React from 'react'
|
|
7
|
-
|
|
8
|
-
import type { LiveQuery, ReactivityGraph } from '../index.js'
|
|
9
|
-
import type { RowResult } from '../row-query.js'
|
|
10
|
-
import { rowQuery } from '../row-query.js'
|
|
11
|
-
import { useStore } from './LiveStoreContext.js'
|
|
12
|
-
import { useQueryRef } from './useQuery.js'
|
|
13
|
-
import { useMakeTemporaryQuery } from './useTemporaryQuery.js'
|
|
14
|
-
|
|
15
|
-
export type UseRowResult<TTableDef extends DbSchema.TableDef> = [
|
|
16
|
-
row: RowResult<TTableDef>,
|
|
17
|
-
setRow: StateSetters<TTableDef>,
|
|
18
|
-
query$: LiveQuery<RowResult<TTableDef>, QueryInfo>,
|
|
19
|
-
]
|
|
20
|
-
|
|
21
|
-
export type UseRowOptionsDefaulValues<TTableDef extends DbSchema.TableDef> = {
|
|
22
|
-
defaultValues?: Partial<RowResult<TTableDef>>
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export type UseRowOptionsBase = {
|
|
26
|
-
reactivityGraph?: ReactivityGraph
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Similar to `React.useState` but returns a tuple of `[row, setRow, query$]` for a given table where ...
|
|
31
|
-
*
|
|
32
|
-
* - `row` is the current value of the row (fully decoded according to the table schema)
|
|
33
|
-
* - `setRow` is a function that can be used to update the row (values will be encoded according to the table schema)
|
|
34
|
-
* - `query$` is a `LiveQuery` that e.g. can be used to subscribe to changes to the row
|
|
35
|
-
*
|
|
36
|
-
* If the table is a singleton table, `useRow` can be called without an `id` argument. Otherwise, the `id` argument is required.
|
|
37
|
-
*/
|
|
38
|
-
export const useRow: {
|
|
39
|
-
<
|
|
40
|
-
TTableDef extends DbSchema.TableDef<
|
|
41
|
-
DbSchema.DefaultSqliteTableDef,
|
|
42
|
-
boolean,
|
|
43
|
-
DbSchema.TableOptions & { isSingleton: true; deriveMutations: { enabled: true } }
|
|
44
|
-
>,
|
|
45
|
-
>(
|
|
46
|
-
table: TTableDef,
|
|
47
|
-
options?: UseRowOptionsBase,
|
|
48
|
-
): UseRowResult<TTableDef>
|
|
49
|
-
<
|
|
50
|
-
TTableDef extends DbSchema.TableDef<
|
|
51
|
-
DbSchema.DefaultSqliteTableDef,
|
|
52
|
-
boolean,
|
|
53
|
-
DbSchema.TableOptions & { isSingleton: false; deriveMutations: { enabled: true } }
|
|
54
|
-
>,
|
|
55
|
-
>(
|
|
56
|
-
table: TTableDef,
|
|
57
|
-
// TODO adjust so it works with arbitrary primary keys or unique constraints
|
|
58
|
-
id: string,
|
|
59
|
-
options?: UseRowOptionsBase & UseRowOptionsDefaulValues<TTableDef>,
|
|
60
|
-
): UseRowResult<TTableDef>
|
|
61
|
-
} = <
|
|
62
|
-
TTableDef extends DbSchema.TableDef<
|
|
63
|
-
DbSchema.DefaultSqliteTableDefConstrained,
|
|
64
|
-
boolean,
|
|
65
|
-
DbSchema.TableOptions & { deriveMutations: { enabled: true } }
|
|
66
|
-
>,
|
|
67
|
-
>(
|
|
68
|
-
table: TTableDef,
|
|
69
|
-
idOrOptions?: string | UseRowOptionsBase,
|
|
70
|
-
options_?: UseRowOptionsBase & UseRowOptionsDefaulValues<TTableDef>,
|
|
71
|
-
): UseRowResult<TTableDef> => {
|
|
72
|
-
const sqliteTableDef = table.sqliteDef
|
|
73
|
-
const id = typeof idOrOptions === 'string' ? idOrOptions : undefined
|
|
74
|
-
const options: (UseRowOptionsBase & UseRowOptionsDefaulValues<TTableDef>) | undefined =
|
|
75
|
-
typeof idOrOptions === 'string' ? options_ : idOrOptions
|
|
76
|
-
const { defaultValues, reactivityGraph } = options ?? {}
|
|
77
|
-
|
|
78
|
-
type TComponentState = SqliteDsl.FromColumns.RowDecoded<TTableDef['sqliteDef']['columns']>
|
|
79
|
-
|
|
80
|
-
const tableName = table.sqliteDef.name
|
|
81
|
-
|
|
82
|
-
if (DbSchema.tableHasDerivedMutations(table) === false) {
|
|
83
|
-
shouldNeverHappen(`useRow called on table "${tableName}" which does not have 'deriveMutations: true' set`)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const { store } = useStore()
|
|
87
|
-
|
|
88
|
-
if (
|
|
89
|
-
store.schema.tables.has(table.sqliteDef.name) === false &&
|
|
90
|
-
table.sqliteDef.name.startsWith('__livestore') === false
|
|
91
|
-
) {
|
|
92
|
-
shouldNeverHappen(`Table "${table.sqliteDef.name}" not found in schema`)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// console.debug('useRow', tableName, id)
|
|
96
|
-
|
|
97
|
-
const { query$, otelContext } = useMakeTemporaryQuery(
|
|
98
|
-
(otelContext) =>
|
|
99
|
-
DbSchema.tableIsSingleton(table)
|
|
100
|
-
? (rowQuery(table, { otelContext, reactivityGraph }) as LiveQuery<RowResult<TTableDef>, QueryInfo>)
|
|
101
|
-
: (rowQuery(table as TTableDef & { options: { isSingleton: false } }, id!, {
|
|
102
|
-
otelContext,
|
|
103
|
-
defaultValues: defaultValues!,
|
|
104
|
-
reactivityGraph,
|
|
105
|
-
}) as any as LiveQuery<RowResult<TTableDef>, QueryInfo>),
|
|
106
|
-
[id!, tableName],
|
|
107
|
-
{
|
|
108
|
-
otel: {
|
|
109
|
-
spanName: `LiveStore:useRow:${tableName}${id === undefined ? '' : `:${id}`}`,
|
|
110
|
-
attributes: { id },
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
const query$Ref = useQueryRef(query$, otelContext) as React.MutableRefObject<RowResult<TTableDef>>
|
|
116
|
-
|
|
117
|
-
const setState = React.useMemo<StateSetters<TTableDef>>(() => {
|
|
118
|
-
if (table.isSingleColumn) {
|
|
119
|
-
return (newValueOrFn: RowResult<TTableDef>) => {
|
|
120
|
-
const newValue = typeof newValueOrFn === 'function' ? newValueOrFn(query$Ref.current) : newValueOrFn
|
|
121
|
-
if (query$Ref.current === newValue) return
|
|
122
|
-
|
|
123
|
-
// NOTE we need to account for the short-hand syntax for single-column+singleton tables
|
|
124
|
-
if (table.options.isSingleton) {
|
|
125
|
-
store.mutate(table.update(newValue))
|
|
126
|
-
} else {
|
|
127
|
-
store.mutate(table.update({ where: { id }, values: { value: newValue } }))
|
|
128
|
-
}
|
|
129
|
-
// store.mutate(updateMutationForQueryInfo(query$.queryInfo!, { value: newValue }))
|
|
130
|
-
}
|
|
131
|
-
} else {
|
|
132
|
-
const setState = // TODO: do we have a better type for the values that can go in SQLite?
|
|
133
|
-
ReadonlyRecord.map(sqliteTableDef.columns, (column, columnName) => (newValueOrFn: any) => {
|
|
134
|
-
const newValue =
|
|
135
|
-
// @ts-expect-error TODO fix typing
|
|
136
|
-
typeof newValueOrFn === 'function' ? newValueOrFn(query$Ref.current[columnName]) : newValueOrFn
|
|
137
|
-
|
|
138
|
-
// Don't update the state if it's the same as the value already seen in the component
|
|
139
|
-
// @ts-expect-error TODO fix typing
|
|
140
|
-
if (query$Ref.current[columnName] === newValue) return
|
|
141
|
-
|
|
142
|
-
store.mutate(table.update({ where: { id: id ?? 'singleton' }, values: { [columnName]: newValue } }))
|
|
143
|
-
// store.mutate(updateMutationForQueryInfo(query$.queryInfo!, { [columnName]: newValue }))
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
setState.setMany = (columnValuesOrFn: Partial<TComponentState>) => {
|
|
147
|
-
const columnValues =
|
|
148
|
-
// @ts-expect-error TODO fix typing
|
|
149
|
-
typeof columnValuesOrFn === 'function' ? columnValuesOrFn(query$Ref.current) : columnValuesOrFn
|
|
150
|
-
|
|
151
|
-
// TODO use hashing instead
|
|
152
|
-
// Don't update the state if it's the same as the value already seen in the component
|
|
153
|
-
if (
|
|
154
|
-
// @ts-expect-error TODO fix typing
|
|
155
|
-
Object.entries(columnValues).every(([columnName, value]) => query$Ref.current[columnName] === value)
|
|
156
|
-
) {
|
|
157
|
-
return
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
store.mutate(table.update({ where: { id: id ?? 'singleton' }, values: columnValues }))
|
|
161
|
-
// store.mutate(updateMutationForQueryInfo(query$.queryInfo!, columnValues))
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return setState as any
|
|
165
|
-
}
|
|
166
|
-
}, [id, query$Ref, sqliteTableDef.columns, store, table])
|
|
167
|
-
|
|
168
|
-
return [query$Ref.current, setState, query$]
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export type Dispatch<A> = (action: A) => void
|
|
172
|
-
export type SetStateAction<S> = S | ((previousValue: S) => S)
|
|
173
|
-
|
|
174
|
-
export type StateSetters<TTableDef extends DbSchema.TableDef> = TTableDef['isSingleColumn'] extends true
|
|
175
|
-
? Dispatch<SetStateAction<RowResult<TTableDef>>>
|
|
176
|
-
: {
|
|
177
|
-
[K in keyof RowResult<TTableDef>]: Dispatch<SetStateAction<RowResult<TTableDef>[K]>>
|
|
178
|
-
} & {
|
|
179
|
-
setMany: Dispatch<SetStateAction<Partial<RowResult<TTableDef>>>>
|
|
180
|
-
}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { Effect, Schema } from '@livestore/utils/effect'
|
|
2
|
-
import { render, renderHook } from '@testing-library/react'
|
|
3
|
-
import React from 'react'
|
|
4
|
-
// @ts-expect-error no types
|
|
5
|
-
import * as ReactWindow from 'react-window'
|
|
6
|
-
import { describe, expect, it } from 'vitest'
|
|
7
|
-
|
|
8
|
-
import { makeTodoMvc, tables, todos } from '../__tests__/react/fixture.js'
|
|
9
|
-
import * as LiveStore from '../index.js'
|
|
10
|
-
import { querySQL } from '../reactiveQueries/sql.js'
|
|
11
|
-
import * as LiveStoreReact from './index.js'
|
|
12
|
-
|
|
13
|
-
describe('useTemporaryQuery', () => {
|
|
14
|
-
it('simple', () =>
|
|
15
|
-
Effect.gen(function* () {
|
|
16
|
-
const { wrapper, store, makeRenderCount } = yield* makeTodoMvc()
|
|
17
|
-
|
|
18
|
-
const renderCount = makeRenderCount()
|
|
19
|
-
|
|
20
|
-
store.mutate(
|
|
21
|
-
todos.insert({ id: 't1', text: 'buy milk', completed: false }),
|
|
22
|
-
todos.insert({ id: 't2', text: 'buy bread', completed: false }),
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
const queryMap = new Map<string, LiveStore.LiveQuery<any>>()
|
|
26
|
-
|
|
27
|
-
const { rerender, result, unmount } = renderHook(
|
|
28
|
-
(id: string) => {
|
|
29
|
-
renderCount.inc()
|
|
30
|
-
|
|
31
|
-
return LiveStoreReact.useTemporaryQuery(() => {
|
|
32
|
-
const query$ = querySQL(`select * from todos where id = '${id}'`, {
|
|
33
|
-
schema: Schema.Array(tables.todos.schema),
|
|
34
|
-
})
|
|
35
|
-
queryMap.set(id, query$)
|
|
36
|
-
return query$
|
|
37
|
-
}, id)
|
|
38
|
-
},
|
|
39
|
-
{ wrapper, initialProps: 't1' },
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
expect(result.current.length).toBe(1)
|
|
43
|
-
expect(result.current[0]!.text).toBe('buy milk')
|
|
44
|
-
expect(renderCount.val).toBe(1)
|
|
45
|
-
expect(queryMap.get('t1')!.runs).toBe(1)
|
|
46
|
-
|
|
47
|
-
rerender('t2')
|
|
48
|
-
|
|
49
|
-
expect(result.current.length).toBe(1)
|
|
50
|
-
expect(result.current[0]!.text).toBe('buy bread')
|
|
51
|
-
expect(renderCount.val).toBe(2)
|
|
52
|
-
expect(queryMap.get('t1')!.runs).toBe(1)
|
|
53
|
-
expect(queryMap.get('t2')!.runs).toBe(1)
|
|
54
|
-
|
|
55
|
-
unmount()
|
|
56
|
-
|
|
57
|
-
expect(queryMap.get('t2')!.runs).toBe(1)
|
|
58
|
-
}).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise))
|
|
59
|
-
|
|
60
|
-
// NOTE this test covers some special react lifecyle paths which I couldn't easily reproduce without react-window
|
|
61
|
-
// it basically causes a "query swap" in the `useMemo` and both a `useEffect` cleanup call.
|
|
62
|
-
// To handle this properly we introduced the `_tag: 'destroyed'` state in the `spanAlreadyStartedCache`.
|
|
63
|
-
it('should work for a list with react-window', () =>
|
|
64
|
-
Effect.gen(function* () {
|
|
65
|
-
const { wrapper } = yield* makeTodoMvc()
|
|
66
|
-
|
|
67
|
-
const ListWrapper: React.FC<{ numItems: number }> = ({ numItems }) => {
|
|
68
|
-
return (
|
|
69
|
-
<ReactWindow.FixedSizeList
|
|
70
|
-
height={100}
|
|
71
|
-
width={100}
|
|
72
|
-
itemSize={10}
|
|
73
|
-
itemCount={numItems}
|
|
74
|
-
itemData={Array.from({ length: numItems }, (_, i) => i).reverse()}
|
|
75
|
-
>
|
|
76
|
-
{ListItem}
|
|
77
|
-
</ReactWindow.FixedSizeList>
|
|
78
|
-
)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const ListItem: React.FC<{ data: ReadonlyArray<number>; index: number }> = ({ data: ids, index }) => {
|
|
82
|
-
const id = ids[index]!
|
|
83
|
-
const res = LiveStoreReact.useTemporaryQuery(
|
|
84
|
-
() => LiveStore.computed(() => id, { label: `ListItem.${id}` }),
|
|
85
|
-
id,
|
|
86
|
-
)
|
|
87
|
-
return <div role="listitem">{res}</div>
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const renderResult = render(<ListWrapper numItems={1} />, { wrapper })
|
|
91
|
-
|
|
92
|
-
expect(renderResult.container.textContent).toBe('0')
|
|
93
|
-
|
|
94
|
-
renderResult.rerender(<ListWrapper numItems={2} />)
|
|
95
|
-
|
|
96
|
-
expect(renderResult.container.textContent).toBe('10')
|
|
97
|
-
}).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise))
|
|
98
|
-
})
|