@livestore/react 0.0.0-snapshot-3ea7644e665c5c8292d2309fb7f837b9146af912 → 0.0.0-snapshot-ba25981b6de87a90976fc39e1c2551844d384a05
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/LiveStoreProvider.test.js +2 -2
- package/dist/LiveStoreProvider.test.js.map +1 -1
- package/dist/__tests__/fixture.d.ts +276 -372
- package/dist/__tests__/fixture.d.ts.map +1 -1
- package/dist/useAtom.d.ts +7 -2
- package/dist/useAtom.d.ts.map +1 -1
- package/dist/useAtom.js +8 -9
- package/dist/useAtom.js.map +1 -1
- package/dist/useQuery.d.ts.map +1 -1
- package/dist/useQuery.js +0 -3
- package/dist/useQuery.js.map +1 -1
- package/dist/useQuery.test.js +10 -4
- package/dist/useQuery.test.js.map +1 -1
- package/dist/useRow.d.ts +14 -18
- package/dist/useRow.d.ts.map +1 -1
- package/dist/useRow.js +9 -6
- package/dist/useRow.js.map +1 -1
- package/dist/useRow.test.js +9 -4
- package/dist/useRow.test.js.map +1 -1
- package/dist/useScopedQuery.test.js +2 -3
- package/dist/useScopedQuery.test.js.map +1 -1
- package/dist/useTemporaryQuery.d.ts +22 -0
- package/dist/useTemporaryQuery.d.ts.map +1 -0
- package/dist/useTemporaryQuery.js +75 -0
- package/dist/useTemporaryQuery.js.map +1 -0
- package/package.json +6 -6
- package/src/LiveStoreProvider.test.tsx +2 -2
- package/src/__snapshots__/useRow.test.tsx.snap +33 -33
- package/src/useAtom.ts +17 -14
- package/src/useQuery.test.tsx +10 -10
- package/src/useQuery.ts +0 -4
- package/src/useRow.test.tsx +11 -9
- package/src/useRow.ts +30 -36
- package/src/useScopedQuery.test.tsx +2 -3
- package/src/useTemporaryQuery.ts +131 -0
|
@@ -21,7 +21,7 @@ exports[`useRow > otel > should update the data based on component key > strictM
|
|
|
21
21
|
"_name": "sql-in-memory-select",
|
|
22
22
|
"attributes": {
|
|
23
23
|
"sql.cached": false,
|
|
24
|
-
"sql.query": "
|
|
24
|
+
"sql.query": "select 1 from UserInfo where id = 'u1'",
|
|
25
25
|
"sql.rowsCount": 0,
|
|
26
26
|
},
|
|
27
27
|
},
|
|
@@ -29,7 +29,7 @@ exports[`useRow > otel > should update the data based on component key > strictM
|
|
|
29
29
|
"_name": "sql-in-memory-select",
|
|
30
30
|
"attributes": {
|
|
31
31
|
"sql.cached": false,
|
|
32
|
-
"sql.query": "
|
|
32
|
+
"sql.query": "select 1 from UserInfo where id = 'u2'",
|
|
33
33
|
"sql.rowsCount": 1,
|
|
34
34
|
},
|
|
35
35
|
},
|
|
@@ -75,9 +75,9 @@ exports[`useRow > otel > should update the data based on component key > strictM
|
|
|
75
75
|
"_name": "LiveStore:queries",
|
|
76
76
|
"children": [
|
|
77
77
|
{
|
|
78
|
-
"_name": "
|
|
78
|
+
"_name": "sql:select * from UserInfo where id = 'u1' limit 1",
|
|
79
79
|
"attributes": {
|
|
80
|
-
"sql.query": "
|
|
80
|
+
"sql.query": "select * from UserInfo where id = 'u1' limit 1",
|
|
81
81
|
"sql.rowsCount": 1,
|
|
82
82
|
},
|
|
83
83
|
"children": [
|
|
@@ -85,7 +85,7 @@ exports[`useRow > otel > should update the data based on component key > strictM
|
|
|
85
85
|
"_name": "sql-in-memory-select",
|
|
86
86
|
"attributes": {
|
|
87
87
|
"sql.cached": false,
|
|
88
|
-
"sql.query": "
|
|
88
|
+
"sql.query": "select * from UserInfo where id = 'u1' limit 1",
|
|
89
89
|
"sql.rowsCount": 1,
|
|
90
90
|
},
|
|
91
91
|
},
|
|
@@ -115,16 +115,16 @@ exports[`useRow > otel > should update the data based on component key > strictM
|
|
|
115
115
|
],
|
|
116
116
|
},
|
|
117
117
|
{
|
|
118
|
-
"_name": "LiveStore:useQuery:
|
|
118
|
+
"_name": "LiveStore:useQuery:sql(rowQuery:query:UserInfo:u1)",
|
|
119
119
|
"attributes": {
|
|
120
|
-
"label": "
|
|
120
|
+
"label": "sql(rowQuery:query:UserInfo:u1)",
|
|
121
121
|
"stackInfo": "{"frames":[{"name":"renderHook.wrapper","filePath":"__REPLACED_FOR_SNAPSHOT__"},{"name":"useRow","filePath":"__REPLACED_FOR_SNAPSHOT__"}]}",
|
|
122
122
|
},
|
|
123
123
|
"children": [
|
|
124
124
|
{
|
|
125
|
-
"_name": "
|
|
125
|
+
"_name": "sql:select * from UserInfo where id = 'u1' limit 1",
|
|
126
126
|
"attributes": {
|
|
127
|
-
"sql.query": "
|
|
127
|
+
"sql.query": "select * from UserInfo where id = 'u1' limit 1",
|
|
128
128
|
"sql.rowsCount": 1,
|
|
129
129
|
},
|
|
130
130
|
"children": [
|
|
@@ -132,7 +132,7 @@ exports[`useRow > otel > should update the data based on component key > strictM
|
|
|
132
132
|
"_name": "sql-in-memory-select",
|
|
133
133
|
"attributes": {
|
|
134
134
|
"sql.cached": false,
|
|
135
|
-
"sql.query": "
|
|
135
|
+
"sql.query": "select * from UserInfo where id = 'u1' limit 1",
|
|
136
136
|
"sql.rowsCount": 1,
|
|
137
137
|
},
|
|
138
138
|
},
|
|
@@ -141,8 +141,8 @@ exports[`useRow > otel > should update the data based on component key > strictM
|
|
|
141
141
|
{
|
|
142
142
|
"_name": "LiveStore.subscribe",
|
|
143
143
|
"attributes": {
|
|
144
|
-
"label": "
|
|
145
|
-
"queryLabel": "
|
|
144
|
+
"label": "sql(rowQuery:query:UserInfo:u1)",
|
|
145
|
+
"queryLabel": "sql(rowQuery:query:UserInfo:u1)",
|
|
146
146
|
},
|
|
147
147
|
},
|
|
148
148
|
],
|
|
@@ -156,16 +156,16 @@ exports[`useRow > otel > should update the data based on component key > strictM
|
|
|
156
156
|
},
|
|
157
157
|
"children": [
|
|
158
158
|
{
|
|
159
|
-
"_name": "LiveStore:useQuery:
|
|
159
|
+
"_name": "LiveStore:useQuery:sql(rowQuery:query:UserInfo:u2)",
|
|
160
160
|
"attributes": {
|
|
161
|
-
"label": "
|
|
161
|
+
"label": "sql(rowQuery:query:UserInfo:u2)",
|
|
162
162
|
"stackInfo": "{"frames":[{"name":"renderHook.wrapper","filePath":"__REPLACED_FOR_SNAPSHOT__"},{"name":"useRow","filePath":"__REPLACED_FOR_SNAPSHOT__"}]}",
|
|
163
163
|
},
|
|
164
164
|
"children": [
|
|
165
165
|
{
|
|
166
|
-
"_name": "
|
|
166
|
+
"_name": "sql:select * from UserInfo where id = 'u2' limit 1",
|
|
167
167
|
"attributes": {
|
|
168
|
-
"sql.query": "
|
|
168
|
+
"sql.query": "select * from UserInfo where id = 'u2' limit 1",
|
|
169
169
|
"sql.rowsCount": 1,
|
|
170
170
|
},
|
|
171
171
|
"children": [
|
|
@@ -173,7 +173,7 @@ exports[`useRow > otel > should update the data based on component key > strictM
|
|
|
173
173
|
"_name": "sql-in-memory-select",
|
|
174
174
|
"attributes": {
|
|
175
175
|
"sql.cached": false,
|
|
176
|
-
"sql.query": "
|
|
176
|
+
"sql.query": "select * from UserInfo where id = 'u2' limit 1",
|
|
177
177
|
"sql.rowsCount": 1,
|
|
178
178
|
},
|
|
179
179
|
},
|
|
@@ -182,8 +182,8 @@ exports[`useRow > otel > should update the data based on component key > strictM
|
|
|
182
182
|
{
|
|
183
183
|
"_name": "LiveStore.subscribe",
|
|
184
184
|
"attributes": {
|
|
185
|
-
"label": "
|
|
186
|
-
"queryLabel": "
|
|
185
|
+
"label": "sql(rowQuery:query:UserInfo:u2)",
|
|
186
|
+
"queryLabel": "sql(rowQuery:query:UserInfo:u2)",
|
|
187
187
|
},
|
|
188
188
|
},
|
|
189
189
|
],
|
|
@@ -217,7 +217,7 @@ exports[`useRow > otel > should update the data based on component key > strictM
|
|
|
217
217
|
"_name": "sql-in-memory-select",
|
|
218
218
|
"attributes": {
|
|
219
219
|
"sql.cached": false,
|
|
220
|
-
"sql.query": "
|
|
220
|
+
"sql.query": "select 1 from UserInfo where id = 'u1'",
|
|
221
221
|
"sql.rowsCount": 0,
|
|
222
222
|
},
|
|
223
223
|
},
|
|
@@ -225,7 +225,7 @@ exports[`useRow > otel > should update the data based on component key > strictM
|
|
|
225
225
|
"_name": "sql-in-memory-select",
|
|
226
226
|
"attributes": {
|
|
227
227
|
"sql.cached": false,
|
|
228
|
-
"sql.query": "
|
|
228
|
+
"sql.query": "select 1 from UserInfo where id = 'u2'",
|
|
229
229
|
"sql.rowsCount": 1,
|
|
230
230
|
},
|
|
231
231
|
},
|
|
@@ -271,9 +271,9 @@ exports[`useRow > otel > should update the data based on component key > strictM
|
|
|
271
271
|
"_name": "LiveStore:queries",
|
|
272
272
|
"children": [
|
|
273
273
|
{
|
|
274
|
-
"_name": "
|
|
274
|
+
"_name": "sql:select * from UserInfo where id = 'u1' limit 1",
|
|
275
275
|
"attributes": {
|
|
276
|
-
"sql.query": "
|
|
276
|
+
"sql.query": "select * from UserInfo where id = 'u1' limit 1",
|
|
277
277
|
"sql.rowsCount": 1,
|
|
278
278
|
},
|
|
279
279
|
"children": [
|
|
@@ -281,7 +281,7 @@ exports[`useRow > otel > should update the data based on component key > strictM
|
|
|
281
281
|
"_name": "sql-in-memory-select",
|
|
282
282
|
"attributes": {
|
|
283
283
|
"sql.cached": false,
|
|
284
|
-
"sql.query": "
|
|
284
|
+
"sql.query": "select * from UserInfo where id = 'u1' limit 1",
|
|
285
285
|
"sql.rowsCount": 1,
|
|
286
286
|
},
|
|
287
287
|
},
|
|
@@ -311,16 +311,16 @@ exports[`useRow > otel > should update the data based on component key > strictM
|
|
|
311
311
|
],
|
|
312
312
|
},
|
|
313
313
|
{
|
|
314
|
-
"_name": "LiveStore:useQuery:
|
|
314
|
+
"_name": "LiveStore:useQuery:sql(rowQuery:query:UserInfo:u1)",
|
|
315
315
|
"attributes": {
|
|
316
|
-
"label": "
|
|
316
|
+
"label": "sql(rowQuery:query:UserInfo:u1)",
|
|
317
317
|
"stackInfo": "{"frames":[{"name":"renderHook.wrapper","filePath":"__REPLACED_FOR_SNAPSHOT__"},{"name":"useRow","filePath":"__REPLACED_FOR_SNAPSHOT__"}]}",
|
|
318
318
|
},
|
|
319
319
|
"children": [
|
|
320
320
|
{
|
|
321
|
-
"_name": "
|
|
321
|
+
"_name": "sql:select * from UserInfo where id = 'u1' limit 1",
|
|
322
322
|
"attributes": {
|
|
323
|
-
"sql.query": "
|
|
323
|
+
"sql.query": "select * from UserInfo where id = 'u1' limit 1",
|
|
324
324
|
"sql.rowsCount": 1,
|
|
325
325
|
},
|
|
326
326
|
"children": [
|
|
@@ -328,7 +328,7 @@ exports[`useRow > otel > should update the data based on component key > strictM
|
|
|
328
328
|
"_name": "sql-in-memory-select",
|
|
329
329
|
"attributes": {
|
|
330
330
|
"sql.cached": false,
|
|
331
|
-
"sql.query": "
|
|
331
|
+
"sql.query": "select * from UserInfo where id = 'u1' limit 1",
|
|
332
332
|
"sql.rowsCount": 1,
|
|
333
333
|
},
|
|
334
334
|
},
|
|
@@ -337,15 +337,15 @@ exports[`useRow > otel > should update the data based on component key > strictM
|
|
|
337
337
|
{
|
|
338
338
|
"_name": "LiveStore.subscribe",
|
|
339
339
|
"attributes": {
|
|
340
|
-
"label": "
|
|
341
|
-
"queryLabel": "
|
|
340
|
+
"label": "sql(rowQuery:query:UserInfo:u1)",
|
|
341
|
+
"queryLabel": "sql(rowQuery:query:UserInfo:u1)",
|
|
342
342
|
},
|
|
343
343
|
},
|
|
344
344
|
{
|
|
345
345
|
"_name": "LiveStore.subscribe",
|
|
346
346
|
"attributes": {
|
|
347
|
-
"label": "
|
|
348
|
-
"queryLabel": "
|
|
347
|
+
"label": "sql(rowQuery:query:UserInfo:u1)",
|
|
348
|
+
"queryLabel": "sql(rowQuery:query:UserInfo:u1)",
|
|
349
349
|
},
|
|
350
350
|
},
|
|
351
351
|
],
|
package/src/useAtom.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type QueryInfoCol, type QueryInfoRow } from '@livestore/common'
|
|
2
2
|
import type { DbSchema } from '@livestore/common/schema'
|
|
3
|
-
import type { SqliteDsl } from '@livestore/db-schema'
|
|
4
3
|
import type { LiveQuery } from '@livestore/livestore'
|
|
5
4
|
import React from 'react'
|
|
6
5
|
|
|
@@ -9,8 +8,12 @@ import { useQueryRef } from './useQuery.js'
|
|
|
9
8
|
import type { Dispatch, SetStateAction } from './useRow.js'
|
|
10
9
|
|
|
11
10
|
export const useAtom = <
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
TQuery extends LiveQuery<any, QueryInfoRow<TTableDef> | QueryInfoCol<TTableDef, any>>,
|
|
12
|
+
TTableDef extends DbSchema.TableDef<
|
|
13
|
+
DbSchema.DefaultSqliteTableDefConstrained,
|
|
14
|
+
boolean,
|
|
15
|
+
DbSchema.TableOptions & { deriveMutations: { enabled: true } }
|
|
16
|
+
>,
|
|
14
17
|
>(
|
|
15
18
|
query$: TQuery,
|
|
16
19
|
): [value: TQuery['__result!'], setValue: Dispatch<SetStateAction<Partial<TQuery['__result!']>>>] => {
|
|
@@ -22,23 +25,23 @@ export const useAtom = <
|
|
|
22
25
|
const setValue = React.useMemo<Dispatch<SetStateAction<TQuery['__result!']>>>(() => {
|
|
23
26
|
return (newValueOrFn: any) => {
|
|
24
27
|
const newValue = typeof newValueOrFn === 'function' ? newValueOrFn(query$Ref.current) : newValueOrFn
|
|
25
|
-
const table = query$.queryInfo.table as DbSchema.TableDef &
|
|
26
|
-
DerivedMutationHelperFns<SqliteDsl.Columns, DbSchema.TableOptions>
|
|
27
28
|
|
|
28
29
|
if (query$.queryInfo._tag === 'Row') {
|
|
29
|
-
if (table.options.isSingleton && table.
|
|
30
|
-
store.mutate(table.update(newValue))
|
|
31
|
-
} else if (table.options.isSingleColumn) {
|
|
32
|
-
store.mutate(
|
|
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
|
+
)
|
|
33
36
|
} else {
|
|
34
|
-
store.mutate(table.update({ where: { id: query$.queryInfo.id }, values: newValue }))
|
|
37
|
+
store.mutate(query$.queryInfo.table.update({ where: { id: query$.queryInfo.id }, values: newValue }))
|
|
35
38
|
}
|
|
36
39
|
} else {
|
|
37
|
-
if (table.options.isSingleton && table.
|
|
38
|
-
store.mutate(table.update({ [query$.queryInfo.column]: newValue }))
|
|
40
|
+
if (query$.queryInfo.table.options.isSingleton && query$.queryInfo.table.isSingleColumn) {
|
|
41
|
+
store.mutate(query$.queryInfo.table.update({ [query$.queryInfo.column]: newValue }))
|
|
39
42
|
} else {
|
|
40
43
|
store.mutate(
|
|
41
|
-
table.update({
|
|
44
|
+
query$.queryInfo.table.update({
|
|
42
45
|
where: { id: query$.queryInfo.id },
|
|
43
46
|
values: { [query$.queryInfo.column]: newValue },
|
|
44
47
|
}),
|
package/src/useQuery.test.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { querySQL } from '@livestore/livestore'
|
|
2
2
|
import { Effect, Schema } from '@livestore/utils/effect'
|
|
3
3
|
import { renderHook } from '@testing-library/react'
|
|
4
4
|
import React from 'react'
|
|
@@ -14,7 +14,7 @@ describe('useQuery', () => {
|
|
|
14
14
|
|
|
15
15
|
const renderCount = makeRenderCount()
|
|
16
16
|
|
|
17
|
-
const allTodos$ =
|
|
17
|
+
const allTodos$ = querySQL(`select * from todos`, { schema: Schema.Array(tables.todos.schema) })
|
|
18
18
|
|
|
19
19
|
const { result } = renderHook(
|
|
20
20
|
() => {
|
|
@@ -41,14 +41,14 @@ describe('useQuery', () => {
|
|
|
41
41
|
|
|
42
42
|
const renderCount = makeRenderCount()
|
|
43
43
|
|
|
44
|
-
const todo1$ =
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
)
|
|
48
|
-
const todo2$ =
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
)
|
|
44
|
+
const todo1$ = querySQL(`select * from todos where id = 't1'`, {
|
|
45
|
+
label: 'libraryTracksView1',
|
|
46
|
+
schema: Schema.Array(tables.todos.schema),
|
|
47
|
+
})
|
|
48
|
+
const todo2$ = querySQL(`select * from todos where id = 't2'`, {
|
|
49
|
+
label: 'libraryTracksView2',
|
|
50
|
+
schema: Schema.Array(tables.todos.schema),
|
|
51
|
+
})
|
|
52
52
|
|
|
53
53
|
store.mutate(
|
|
54
54
|
todos.insert({ id: 't1', text: 'buy milk', completed: false }),
|
package/src/useQuery.ts
CHANGED
|
@@ -101,10 +101,6 @@ Stack trace:
|
|
|
101
101
|
React.useEffect(() => {
|
|
102
102
|
query$.activeSubscriptions.add(stackInfo)
|
|
103
103
|
|
|
104
|
-
// Dynamic queries only set their actual label after they've been run the first time,
|
|
105
|
-
// so we're also updating the span name here.
|
|
106
|
-
span.updateName(`LiveStore:useQuery:${query$.label}`)
|
|
107
|
-
|
|
108
104
|
return store.subscribe(
|
|
109
105
|
query$,
|
|
110
106
|
(newValue) => {
|
package/src/useRow.test.tsx
CHANGED
|
@@ -121,10 +121,11 @@ describe('useRow', () => {
|
|
|
121
121
|
useGlobalReactivityGraph: false,
|
|
122
122
|
})
|
|
123
123
|
|
|
124
|
-
const allTodos$ = LiveStore.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
124
|
+
const allTodos$ = LiveStore.querySQL(`select * from todos`, {
|
|
125
|
+
label: 'allTodos',
|
|
126
|
+
schema: Schema.Array(tables.todos.schema),
|
|
127
|
+
reactivityGraph,
|
|
128
|
+
})
|
|
128
129
|
|
|
129
130
|
const appRouterRenderCount = makeRenderCount()
|
|
130
131
|
let globalSetState: LiveStoreReact.StateSetters<typeof AppRouterSchema> | undefined
|
|
@@ -222,12 +223,13 @@ describe('useRow', () => {
|
|
|
222
223
|
const [_row, _setRow, rowState$] = LiveStoreReact.useRow(AppComponentSchema, userId, { reactivityGraph })
|
|
223
224
|
const todos = LiveStoreReact.useScopedQuery(
|
|
224
225
|
() =>
|
|
225
|
-
LiveStore.
|
|
226
|
-
(get) => (
|
|
227
|
-
|
|
226
|
+
LiveStore.querySQL(
|
|
227
|
+
(get) => LiveStore.sql`select * from todos where text like '%${get(rowState$).text}%'`,
|
|
228
|
+
{
|
|
228
229
|
schema: Schema.Array(tables.todos.schema),
|
|
229
|
-
|
|
230
|
-
|
|
230
|
+
reactivityGraph,
|
|
231
|
+
label: 'todosFiltered',
|
|
232
|
+
},
|
|
231
233
|
),
|
|
232
234
|
userId,
|
|
233
235
|
)
|
package/src/useRow.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { QueryInfo
|
|
1
|
+
import type { QueryInfo } from '@livestore/common'
|
|
2
2
|
import { SessionIdSymbol } from '@livestore/common'
|
|
3
3
|
import { DbSchema } from '@livestore/common/schema'
|
|
4
4
|
import type { SqliteDsl } from '@livestore/db-schema'
|
|
5
|
-
import type { LiveQuery, ReactivityGraph } from '@livestore/livestore'
|
|
6
|
-
import {
|
|
5
|
+
import type { LiveQuery, ReactivityGraph, RowResult } from '@livestore/livestore'
|
|
6
|
+
import { rowQuery } from '@livestore/livestore'
|
|
7
7
|
import { shouldNeverHappen } from '@livestore/utils'
|
|
8
8
|
import { ReadonlyRecord } from '@livestore/utils/effect'
|
|
9
9
|
import React from 'react'
|
|
@@ -12,12 +12,16 @@ import { useStore } from './LiveStoreContext.js'
|
|
|
12
12
|
import { useQueryRef } from './useQuery.js'
|
|
13
13
|
import { useMakeScopedQuery } from './useScopedQuery.js'
|
|
14
14
|
|
|
15
|
-
export type UseRowResult<TTableDef extends DbSchema.
|
|
16
|
-
row:
|
|
15
|
+
export type UseRowResult<TTableDef extends DbSchema.TableDef> = [
|
|
16
|
+
row: RowResult<TTableDef>,
|
|
17
17
|
setRow: StateSetters<TTableDef>,
|
|
18
|
-
query$: LiveQuery<
|
|
18
|
+
query$: LiveQuery<RowResult<TTableDef>, QueryInfo>,
|
|
19
19
|
]
|
|
20
20
|
|
|
21
|
+
export type UseRowOptionsDefaulValues<TTableDef extends DbSchema.TableDef> = {
|
|
22
|
+
defaultValues?: Partial<RowResult<TTableDef>>
|
|
23
|
+
}
|
|
24
|
+
|
|
21
25
|
export type UseRowOptionsBase = {
|
|
22
26
|
reactivityGraph?: ReactivityGraph
|
|
23
27
|
}
|
|
@@ -35,6 +39,7 @@ export const useRow: {
|
|
|
35
39
|
<
|
|
36
40
|
TTableDef extends DbSchema.TableDef<
|
|
37
41
|
DbSchema.DefaultSqliteTableDef,
|
|
42
|
+
boolean,
|
|
38
43
|
DbSchema.TableOptions & { isSingleton: true; deriveMutations: { enabled: true } }
|
|
39
44
|
>,
|
|
40
45
|
>(
|
|
@@ -44,44 +49,31 @@ export const useRow: {
|
|
|
44
49
|
<
|
|
45
50
|
TTableDef extends DbSchema.TableDef<
|
|
46
51
|
DbSchema.DefaultSqliteTableDef,
|
|
47
|
-
|
|
48
|
-
isSingleton: false
|
|
49
|
-
requiredInsertColumnNames: 'id'
|
|
50
|
-
deriveMutations: { enabled: true }
|
|
51
|
-
}
|
|
52
|
-
>,
|
|
53
|
-
>(
|
|
54
|
-
table: TTableDef,
|
|
55
|
-
// TODO adjust so it works with arbitrary primary keys or unique constraints
|
|
56
|
-
id: string | SessionIdSymbol,
|
|
57
|
-
options?: UseRowOptionsBase & Partial<RowQuery.RequiredColumnsOptions<TTableDef>>,
|
|
58
|
-
): UseRowResult<TTableDef>
|
|
59
|
-
<
|
|
60
|
-
TTableDef extends DbSchema.TableDef<
|
|
61
|
-
DbSchema.DefaultSqliteTableDef,
|
|
52
|
+
boolean,
|
|
62
53
|
DbSchema.TableOptions & { isSingleton: false; deriveMutations: { enabled: true } }
|
|
63
54
|
>,
|
|
64
55
|
>(
|
|
65
56
|
table: TTableDef,
|
|
66
57
|
// TODO adjust so it works with arbitrary primary keys or unique constraints
|
|
67
58
|
id: string | SessionIdSymbol,
|
|
68
|
-
options
|
|
59
|
+
options?: UseRowOptionsBase & UseRowOptionsDefaulValues<TTableDef>,
|
|
69
60
|
): UseRowResult<TTableDef>
|
|
70
61
|
} = <
|
|
71
62
|
TTableDef extends DbSchema.TableDef<
|
|
72
63
|
DbSchema.DefaultSqliteTableDefConstrained,
|
|
64
|
+
boolean,
|
|
73
65
|
DbSchema.TableOptions & { deriveMutations: { enabled: true } }
|
|
74
66
|
>,
|
|
75
67
|
>(
|
|
76
68
|
table: TTableDef,
|
|
77
69
|
idOrOptions?: string | SessionIdSymbol | UseRowOptionsBase,
|
|
78
|
-
options_?: UseRowOptionsBase &
|
|
70
|
+
options_?: UseRowOptionsBase & UseRowOptionsDefaulValues<TTableDef>,
|
|
79
71
|
): UseRowResult<TTableDef> => {
|
|
80
72
|
const sqliteTableDef = table.sqliteDef
|
|
81
73
|
const id = typeof idOrOptions === 'string' || idOrOptions === SessionIdSymbol ? idOrOptions : undefined
|
|
82
|
-
const options: (UseRowOptionsBase &
|
|
74
|
+
const options: (UseRowOptionsBase & UseRowOptionsDefaulValues<TTableDef>) | undefined =
|
|
83
75
|
typeof idOrOptions === 'string' || idOrOptions === SessionIdSymbol ? options_ : idOrOptions
|
|
84
|
-
const {
|
|
76
|
+
const { defaultValues, reactivityGraph } = options ?? {}
|
|
85
77
|
|
|
86
78
|
type TComponentState = SqliteDsl.FromColumns.RowDecoded<TTableDef['sqliteDef']['columns']>
|
|
87
79
|
|
|
@@ -103,14 +95,16 @@ export const useRow: {
|
|
|
103
95
|
// console.debug('useRow', tableName, id)
|
|
104
96
|
|
|
105
97
|
const idStr = id === SessionIdSymbol ? 'session' : id
|
|
106
|
-
const rowQuery = table.query.row as any
|
|
107
98
|
|
|
108
|
-
type Query$ = LiveQuery<RowQuery.Result<TTableDef>, QueryInfo.Row>
|
|
109
99
|
const { query$, otelContext } = useMakeScopedQuery(
|
|
110
100
|
(otelContext) =>
|
|
111
101
|
DbSchema.tableIsSingleton(table)
|
|
112
|
-
? (
|
|
113
|
-
: (
|
|
102
|
+
? (rowQuery(table, { otelContext, reactivityGraph }) as LiveQuery<RowResult<TTableDef>, QueryInfo>)
|
|
103
|
+
: (rowQuery(table as TTableDef & { options: { isSingleton: false } }, id!, {
|
|
104
|
+
otelContext,
|
|
105
|
+
defaultValues: defaultValues!,
|
|
106
|
+
reactivityGraph,
|
|
107
|
+
}) as any as LiveQuery<RowResult<TTableDef>, QueryInfo>),
|
|
114
108
|
[idStr!, tableName],
|
|
115
109
|
{
|
|
116
110
|
otel: {
|
|
@@ -120,11 +114,11 @@ export const useRow: {
|
|
|
120
114
|
},
|
|
121
115
|
)
|
|
122
116
|
|
|
123
|
-
const query$Ref = useQueryRef(query$, otelContext) as React.MutableRefObject<
|
|
117
|
+
const query$Ref = useQueryRef(query$, otelContext) as React.MutableRefObject<RowResult<TTableDef>>
|
|
124
118
|
|
|
125
119
|
const setState = React.useMemo<StateSetters<TTableDef>>(() => {
|
|
126
|
-
if (table.
|
|
127
|
-
return (newValueOrFn:
|
|
120
|
+
if (table.isSingleColumn) {
|
|
121
|
+
return (newValueOrFn: RowResult<TTableDef>) => {
|
|
128
122
|
const newValue = typeof newValueOrFn === 'function' ? newValueOrFn(query$Ref.current) : newValueOrFn
|
|
129
123
|
if (query$Ref.current === newValue) return
|
|
130
124
|
|
|
@@ -179,10 +173,10 @@ export const useRow: {
|
|
|
179
173
|
export type Dispatch<A> = (action: A) => void
|
|
180
174
|
export type SetStateAction<S> = S | ((previousValue: S) => S)
|
|
181
175
|
|
|
182
|
-
export type StateSetters<TTableDef extends DbSchema.
|
|
183
|
-
? Dispatch<SetStateAction<
|
|
176
|
+
export type StateSetters<TTableDef extends DbSchema.TableDef> = TTableDef['isSingleColumn'] extends true
|
|
177
|
+
? Dispatch<SetStateAction<RowResult<TTableDef>>>
|
|
184
178
|
: {
|
|
185
|
-
[K in keyof
|
|
179
|
+
[K in keyof RowResult<TTableDef>]: Dispatch<SetStateAction<RowResult<TTableDef>[K]>>
|
|
186
180
|
} & {
|
|
187
|
-
setMany: Dispatch<SetStateAction<Partial<
|
|
181
|
+
setMany: Dispatch<SetStateAction<Partial<RowResult<TTableDef>>>>
|
|
188
182
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as LiveStore from '@livestore/livestore'
|
|
2
|
-
import {
|
|
2
|
+
import { querySQL } from '@livestore/livestore'
|
|
3
3
|
import { Effect, Schema } from '@livestore/utils/effect'
|
|
4
4
|
import { render, renderHook } from '@testing-library/react'
|
|
5
5
|
import React from 'react'
|
|
@@ -29,8 +29,7 @@ describe('useScopedQuery', () => {
|
|
|
29
29
|
renderCount.inc()
|
|
30
30
|
|
|
31
31
|
return LiveStoreReact.useScopedQuery(() => {
|
|
32
|
-
const query$ =
|
|
33
|
-
query: `select * from todos where id = '${id}'`,
|
|
32
|
+
const query$ = querySQL(`select * from todos where id = '${id}'`, {
|
|
34
33
|
schema: Schema.Array(tables.todos.schema),
|
|
35
34
|
})
|
|
36
35
|
queryMap.set(id, query$)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { QueryInfo } from '@livestore/common'
|
|
2
|
+
import type { LiveQuery } from '@livestore/livestore'
|
|
3
|
+
import * as otel from '@opentelemetry/api'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
|
|
6
|
+
import { useStore } from './LiveStoreContext.js'
|
|
7
|
+
import { useQueryRef } from './useQuery.js'
|
|
8
|
+
|
|
9
|
+
// NOTE Given `useMemo` will be called multiple times (e.g. when using React Strict mode or Fast Refresh),
|
|
10
|
+
// we are using this cache to avoid starting multiple queries/spans for the same component.
|
|
11
|
+
// This is somewhat against some recommended React best practices, but it should be fine in our case below.
|
|
12
|
+
// Please definitely open an issue if you see or run into any problems with this approach!
|
|
13
|
+
const cache = new Map<
|
|
14
|
+
string,
|
|
15
|
+
| {
|
|
16
|
+
_tag: 'active'
|
|
17
|
+
rc: number
|
|
18
|
+
query$: LiveQuery<any, any>
|
|
19
|
+
span: otel.Span
|
|
20
|
+
otelContext: otel.Context
|
|
21
|
+
}
|
|
22
|
+
| {
|
|
23
|
+
_tag: 'destroyed'
|
|
24
|
+
}
|
|
25
|
+
>()
|
|
26
|
+
|
|
27
|
+
export type DepKey = string | number | ReadonlyArray<string | number>
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Creates a query, subscribes and destroys it when the component unmounts.
|
|
31
|
+
*
|
|
32
|
+
* The `key` is used to determine whether the a new query should be created or if the existing one should be reused.
|
|
33
|
+
*/
|
|
34
|
+
export const useScopedQuery = <TResult>(makeQuery: () => LiveQuery<TResult>, key: DepKey): TResult =>
|
|
35
|
+
useScopedQueryRef(makeQuery, key).current
|
|
36
|
+
|
|
37
|
+
export const useScopedQueryRef = <TResult>(
|
|
38
|
+
makeQuery: () => LiveQuery<TResult>,
|
|
39
|
+
key: DepKey,
|
|
40
|
+
): React.MutableRefObject<TResult> => {
|
|
41
|
+
const { query$ } = useMakeScopedQuery(makeQuery, key)
|
|
42
|
+
|
|
43
|
+
return useQueryRef(query$)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const useMakeScopedQuery = <TResult, TQueryInfo extends QueryInfo>(
|
|
47
|
+
makeQuery: (otelContext: otel.Context) => LiveQuery<TResult, TQueryInfo>,
|
|
48
|
+
key: DepKey,
|
|
49
|
+
options?: {
|
|
50
|
+
otel?: {
|
|
51
|
+
spanName?: string
|
|
52
|
+
attributes?: otel.Attributes
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
): { query$: LiveQuery<TResult, TQueryInfo>; otelContext: otel.Context } => {
|
|
56
|
+
const { store } = useStore()
|
|
57
|
+
const fullKey = React.useMemo(
|
|
58
|
+
// NOTE We're using the `makeQuery` function body string to make sure the key is unique across the app
|
|
59
|
+
// TODO we should figure out whether this could cause some problems and/or if there's a better way to do this
|
|
60
|
+
() => (Array.isArray(key) ? key.join('-') : key) + '-' + store.reactivityGraph.id + '-' + makeQuery.toString(),
|
|
61
|
+
[key, makeQuery, store.reactivityGraph.id],
|
|
62
|
+
)
|
|
63
|
+
const fullKeyRef = React.useRef<string>()
|
|
64
|
+
|
|
65
|
+
const { query$, otelContext } = React.useMemo(() => {
|
|
66
|
+
if (fullKeyRef.current !== undefined && fullKeyRef.current !== fullKey) {
|
|
67
|
+
// console.debug('fullKey changed', 'prev', fullKeyRef.current.split('-')[0]!, '-> new', fullKey.split('-')[0]!)
|
|
68
|
+
|
|
69
|
+
const cachedItem = cache.get(fullKeyRef.current)
|
|
70
|
+
if (cachedItem !== undefined && cachedItem._tag === 'active') {
|
|
71
|
+
cachedItem.rc--
|
|
72
|
+
|
|
73
|
+
if (cachedItem.rc === 0) {
|
|
74
|
+
// console.debug('rc=0-changed', cachedItem.query$.id, cachedItem.query$.label)
|
|
75
|
+
cachedItem.query$.destroy()
|
|
76
|
+
cachedItem.span.end()
|
|
77
|
+
cache.set(fullKeyRef.current, { _tag: 'destroyed' })
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const cachedItem = cache.get(fullKey)
|
|
83
|
+
if (cachedItem !== undefined && cachedItem._tag === 'active') {
|
|
84
|
+
// console.debug('rc++', cachedItem.query$.id, cachedItem.query$.label)
|
|
85
|
+
cachedItem.rc++
|
|
86
|
+
|
|
87
|
+
return cachedItem
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const spanName = options?.otel?.spanName ?? `LiveStore:useScopedQuery:${key}`
|
|
91
|
+
|
|
92
|
+
const span = store.otel.tracer.startSpan(
|
|
93
|
+
spanName,
|
|
94
|
+
{ attributes: options?.otel?.attributes },
|
|
95
|
+
store.otel.queriesSpanContext,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
99
|
+
|
|
100
|
+
const query$ = makeQuery(otelContext)
|
|
101
|
+
|
|
102
|
+
cache.set(fullKey, { _tag: 'active', rc: 1, query$, span, otelContext })
|
|
103
|
+
|
|
104
|
+
return { query$, otelContext }
|
|
105
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
106
|
+
}, [fullKey])
|
|
107
|
+
|
|
108
|
+
fullKeyRef.current = fullKey
|
|
109
|
+
|
|
110
|
+
React.useEffect(() => {
|
|
111
|
+
return () => {
|
|
112
|
+
const fullKey = fullKeyRef.current!
|
|
113
|
+
const cachedItem = cache.get(fullKey)
|
|
114
|
+
// NOTE in case the fullKey changed then the query was already destroyed in the useMemo above
|
|
115
|
+
if (cachedItem === undefined || cachedItem._tag === 'destroyed') return
|
|
116
|
+
|
|
117
|
+
// console.debug('rc--', cachedItem.query$.id, cachedItem.query$.label)
|
|
118
|
+
|
|
119
|
+
cachedItem.rc--
|
|
120
|
+
|
|
121
|
+
if (cachedItem.rc === 0) {
|
|
122
|
+
// console.debug('rc=0', cachedItem.query$.id, cachedItem.query$.label)
|
|
123
|
+
cachedItem.query$.destroy()
|
|
124
|
+
cachedItem.span.end()
|
|
125
|
+
cache.delete(fullKey)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}, [])
|
|
129
|
+
|
|
130
|
+
return { query$, otelContext }
|
|
131
|
+
}
|