@livestore/react 0.4.0-dev.1 → 0.4.0-dev.11
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.d.ts +1 -3
- package/dist/LiveStoreProvider.d.ts.map +1 -1
- package/dist/LiveStoreProvider.js +22 -18
- package/dist/LiveStoreProvider.js.map +1 -1
- package/dist/LiveStoreProvider.test.js +1 -1
- package/dist/LiveStoreProvider.test.js.map +1 -1
- package/dist/__tests__/fixture.d.ts +24 -3
- package/dist/__tests__/fixture.d.ts.map +1 -1
- package/dist/__tests__/fixture.js +10 -3
- package/dist/__tests__/fixture.js.map +1 -1
- package/dist/mod.d.ts +1 -1
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js.map +1 -1
- package/dist/useClientDocument.d.ts +10 -13
- package/dist/useClientDocument.d.ts.map +1 -1
- package/dist/useClientDocument.js +3 -4
- package/dist/useClientDocument.js.map +1 -1
- package/dist/useClientDocument.test.js +20 -2
- package/dist/useClientDocument.test.js.map +1 -1
- package/dist/useQuery.d.ts.map +1 -1
- package/dist/useQuery.js +9 -11
- package/dist/useQuery.js.map +1 -1
- package/package.json +13 -13
- package/src/LiveStoreProvider.test.tsx +1 -1
- package/src/LiveStoreProvider.tsx +24 -19
- package/src/__snapshots__/useClientDocument.test.tsx.snap +208 -100
- package/src/__snapshots__/useQuery.test.tsx.snap +388 -116
- package/src/__tests__/fixture.tsx +17 -9
- package/src/mod.ts +2 -1
- package/src/useClientDocument.test.tsx +31 -2
- package/src/useClientDocument.ts +22 -12
- package/src/useQuery.ts +11 -9
|
@@ -3,6 +3,7 @@ import { provideOtel, type UnexpectedError } from '@livestore/common'
|
|
|
3
3
|
import { Events, makeSchema, State } from '@livestore/common/schema'
|
|
4
4
|
import type { LiveStoreSchema, SqliteDsl, Store } from '@livestore/livestore'
|
|
5
5
|
import { createStore } from '@livestore/livestore'
|
|
6
|
+
import { omitUndefineds } from '@livestore/utils'
|
|
6
7
|
import { Effect, Schema, type Scope } from '@livestore/utils/effect'
|
|
7
8
|
import type * as otel from '@opentelemetry/api'
|
|
8
9
|
import React from 'react'
|
|
@@ -60,6 +61,12 @@ const AppRouterSchema = State.SQLite.clientDocument({
|
|
|
60
61
|
},
|
|
61
62
|
})
|
|
62
63
|
|
|
64
|
+
const kv = State.SQLite.clientDocument({
|
|
65
|
+
name: 'Kv',
|
|
66
|
+
schema: Schema.Any,
|
|
67
|
+
default: { value: null },
|
|
68
|
+
})
|
|
69
|
+
|
|
63
70
|
export const events = {
|
|
64
71
|
todoCreated: Events.synced({
|
|
65
72
|
name: 'todoCreated',
|
|
@@ -75,14 +82,15 @@ export const events = {
|
|
|
75
82
|
}),
|
|
76
83
|
AppRouterSet: AppRouterSchema.set,
|
|
77
84
|
UserInfoSet: userInfo.set,
|
|
85
|
+
KvSet: kv.set,
|
|
78
86
|
}
|
|
79
87
|
|
|
80
88
|
const materializers = State.SQLite.materializers(events, {
|
|
81
89
|
todoCreated: ({ id, text, completed }) => todos.insert({ id, text, completed }),
|
|
82
|
-
todoUpdated: ({ id, text, completed }) => todos.update({ completed, text }).where({ id }),
|
|
90
|
+
todoUpdated: ({ id, text, completed }) => todos.update({ ...omitUndefineds({ completed, text }) }).where({ id }),
|
|
83
91
|
})
|
|
84
92
|
|
|
85
|
-
export const tables = { todos, app, userInfo, AppRouterSchema }
|
|
93
|
+
export const tables = { todos, app, userInfo, AppRouterSchema, kv }
|
|
86
94
|
|
|
87
95
|
const state = State.SQLite.makeState({ tables, materializers })
|
|
88
96
|
export const schema = makeSchema({ state, events })
|
|
@@ -92,9 +100,9 @@ export const makeTodoMvcReact: ({
|
|
|
92
100
|
otelContext,
|
|
93
101
|
strictMode,
|
|
94
102
|
}?: {
|
|
95
|
-
otelTracer?: otel.Tracer
|
|
96
|
-
otelContext?: otel.Context
|
|
97
|
-
strictMode?: boolean
|
|
103
|
+
otelTracer?: otel.Tracer | undefined
|
|
104
|
+
otelContext?: otel.Context | undefined
|
|
105
|
+
strictMode?: boolean | undefined
|
|
98
106
|
}) => Effect.Effect<
|
|
99
107
|
{
|
|
100
108
|
wrapper: ({ children }: any) => React.JSX.Element
|
|
@@ -108,9 +116,9 @@ export const makeTodoMvcReact: ({
|
|
|
108
116
|
otelContext,
|
|
109
117
|
strictMode,
|
|
110
118
|
}: {
|
|
111
|
-
otelTracer?: otel.Tracer
|
|
112
|
-
otelContext?: otel.Context
|
|
113
|
-
strictMode?: boolean
|
|
119
|
+
otelTracer?: otel.Tracer | undefined
|
|
120
|
+
otelContext?: otel.Context | undefined
|
|
121
|
+
strictMode?: boolean | undefined
|
|
114
122
|
} = {}) =>
|
|
115
123
|
Effect.gen(function* () {
|
|
116
124
|
const makeRenderCount = () => {
|
|
@@ -156,4 +164,4 @@ export const makeTodoMvcReact: ({
|
|
|
156
164
|
const renderCount = makeRenderCount()
|
|
157
165
|
|
|
158
166
|
return { wrapper, store: storeWithReactApi, renderCount }
|
|
159
|
-
}).pipe(provideOtel({ parentSpanContext: otelContext, otelTracer }))
|
|
167
|
+
}).pipe(provideOtel(omitUndefineds({ parentSpanContext: otelContext, otelTracer })))
|
package/src/mod.ts
CHANGED
|
@@ -3,8 +3,9 @@ export { LiveStoreProvider } from './LiveStoreProvider.tsx'
|
|
|
3
3
|
export {
|
|
4
4
|
type Dispatch,
|
|
5
5
|
type SetStateAction,
|
|
6
|
+
type SetStateActionPartial,
|
|
6
7
|
type StateSetters,
|
|
7
|
-
type
|
|
8
|
+
type UseClientDocumentResult,
|
|
8
9
|
useClientDocument,
|
|
9
10
|
} from './useClientDocument.ts'
|
|
10
11
|
export { useQuery, useQueryRef } from './useQuery.ts'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** biome-ignore-all lint/a11y/useValidAriaRole: not needed for testing */
|
|
2
2
|
/** biome-ignore-all lint/a11y/noStaticElementInteractions: not needed for testing */
|
|
3
3
|
import * as LiveStore from '@livestore/livestore'
|
|
4
|
-
import { getSimplifiedRootSpan } from '@livestore/livestore/internal/testing-utils'
|
|
4
|
+
import { getAllSimplifiedRootSpans, getSimplifiedRootSpan } from '@livestore/livestore/internal/testing-utils'
|
|
5
5
|
import { Effect, ReadonlyRecord, Schema } from '@livestore/utils/effect'
|
|
6
6
|
import { Vitest } from '@livestore/utils-dev/node-vitest'
|
|
7
7
|
import * as otel from '@opentelemetry/api'
|
|
@@ -224,6 +224,34 @@ Vitest.describe('useClientDocument', () => {
|
|
|
224
224
|
}),
|
|
225
225
|
)
|
|
226
226
|
|
|
227
|
+
Vitest.scopedLive('kv client document overwrites value (Schema.Any, no partial merge)', () =>
|
|
228
|
+
Effect.gen(function* () {
|
|
229
|
+
const { wrapper, store, renderCount } = yield* makeTodoMvcReact({})
|
|
230
|
+
|
|
231
|
+
const { result } = ReactTesting.renderHook(
|
|
232
|
+
(id: string) => {
|
|
233
|
+
renderCount.inc()
|
|
234
|
+
|
|
235
|
+
const [state, setState] = store.useClientDocument(tables.kv, id)
|
|
236
|
+
return { state, setState, id }
|
|
237
|
+
},
|
|
238
|
+
{ wrapper, initialProps: 'k1' },
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
expect(result.current.id).toBe('k1')
|
|
242
|
+
expect(result.current.state).toBe(null)
|
|
243
|
+
expect(renderCount.val).toBe(1)
|
|
244
|
+
|
|
245
|
+
ReactTesting.act(() => result.current.setState(1))
|
|
246
|
+
expect(result.current.state).toEqual(1)
|
|
247
|
+
expect(renderCount.val).toBe(2)
|
|
248
|
+
|
|
249
|
+
ReactTesting.act(() => result.current.setState({ b: 2 }))
|
|
250
|
+
expect(result.current.state).toEqual({ b: 2 })
|
|
251
|
+
expect(renderCount.val).toBe(3)
|
|
252
|
+
}),
|
|
253
|
+
)
|
|
254
|
+
|
|
227
255
|
Vitest.describe('otel', () => {
|
|
228
256
|
it.each([{ strictMode: true }, { strictMode: false }])(
|
|
229
257
|
'should update the data based on component key strictMode=%s',
|
|
@@ -295,7 +323,8 @@ Vitest.describe('useClientDocument', () => {
|
|
|
295
323
|
})
|
|
296
324
|
}
|
|
297
325
|
|
|
298
|
-
expect(getSimplifiedRootSpan(exporter, mapAttributes)).toMatchSnapshot()
|
|
326
|
+
expect(getSimplifiedRootSpan(exporter, 'createStore', mapAttributes)).toMatchSnapshot()
|
|
327
|
+
expect(getAllSimplifiedRootSpans(exporter, 'LiveStore:commit', mapAttributes)).toMatchSnapshot()
|
|
299
328
|
|
|
300
329
|
await provider.shutdown()
|
|
301
330
|
},
|
package/src/useClientDocument.ts
CHANGED
|
@@ -3,13 +3,13 @@ import { SessionIdSymbol } from '@livestore/common'
|
|
|
3
3
|
import { State } from '@livestore/common/schema'
|
|
4
4
|
import type { LiveQuery, LiveQueryDef, Store } from '@livestore/livestore'
|
|
5
5
|
import { queryDb } from '@livestore/livestore'
|
|
6
|
-
import { shouldNeverHappen } from '@livestore/utils'
|
|
6
|
+
import { omitUndefineds, shouldNeverHappen } from '@livestore/utils'
|
|
7
7
|
import React from 'react'
|
|
8
8
|
|
|
9
9
|
import { LiveStoreContext } from './LiveStoreContext.ts'
|
|
10
10
|
import { useQueryRef } from './useQuery.ts'
|
|
11
11
|
|
|
12
|
-
export type
|
|
12
|
+
export type UseClientDocumentResult<TTableDef extends State.SQLite.ClientDocumentTableDef.TraitAny> = [
|
|
13
13
|
row: TTableDef['Value'],
|
|
14
14
|
setRow: StateSetters<TTableDef>,
|
|
15
15
|
id: string,
|
|
@@ -54,13 +54,17 @@ export const useClientDocument: {
|
|
|
54
54
|
any,
|
|
55
55
|
any,
|
|
56
56
|
any,
|
|
57
|
-
{
|
|
57
|
+
{
|
|
58
|
+
partialSet: boolean
|
|
59
|
+
/** Default value to use instead of the default value from the table definition */
|
|
60
|
+
default: any
|
|
61
|
+
}
|
|
58
62
|
>,
|
|
59
63
|
>(
|
|
60
64
|
table: TTableDef,
|
|
61
65
|
id?: State.SQLite.ClientDocumentTableDef.DefaultIdType<TTableDef> | SessionIdSymbol,
|
|
62
66
|
options?: Partial<RowQuery.GetOrCreateOptions<TTableDef>>,
|
|
63
|
-
):
|
|
67
|
+
): UseClientDocumentResult<TTableDef>
|
|
64
68
|
|
|
65
69
|
// case: no default id → id arg is required
|
|
66
70
|
<
|
|
@@ -68,20 +72,24 @@ export const useClientDocument: {
|
|
|
68
72
|
any,
|
|
69
73
|
any,
|
|
70
74
|
any,
|
|
71
|
-
{
|
|
75
|
+
{
|
|
76
|
+
partialSet: boolean
|
|
77
|
+
/** Default value to use instead of the default value from the table definition */
|
|
78
|
+
default: any
|
|
79
|
+
}
|
|
72
80
|
>,
|
|
73
81
|
>(
|
|
74
82
|
table: TTableDef,
|
|
75
83
|
// TODO adjust so it works with arbitrary primary keys or unique constraints
|
|
76
84
|
id: State.SQLite.ClientDocumentTableDef.DefaultIdType<TTableDef> | string | SessionIdSymbol,
|
|
77
85
|
options?: Partial<RowQuery.GetOrCreateOptions<TTableDef>>,
|
|
78
|
-
):
|
|
86
|
+
): UseClientDocumentResult<TTableDef>
|
|
79
87
|
} = <TTableDef extends State.SQLite.ClientDocumentTableDef.Any>(
|
|
80
88
|
table: TTableDef,
|
|
81
89
|
idOrOptions?: string | SessionIdSymbol,
|
|
82
90
|
options_?: Partial<RowQuery.GetOrCreateOptions<TTableDef>>,
|
|
83
91
|
storeArg?: { store?: Store },
|
|
84
|
-
):
|
|
92
|
+
): UseClientDocumentResult<TTableDef> => {
|
|
85
93
|
const id =
|
|
86
94
|
typeof idOrOptions === 'string' || idOrOptions === SessionIdSymbol
|
|
87
95
|
? idOrOptions
|
|
@@ -97,8 +105,7 @@ export const useClientDocument: {
|
|
|
97
105
|
const tableName = table.sqliteDef.name
|
|
98
106
|
|
|
99
107
|
const store =
|
|
100
|
-
storeArg?.store ??
|
|
101
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: store is stable
|
|
108
|
+
storeArg?.store ?? // biome-ignore lint/correctness/useHookAtTopLevel: store is stable
|
|
102
109
|
React.useContext(LiveStoreContext)?.store ??
|
|
103
110
|
shouldNeverHappen(`No store provided to useClientDocument`)
|
|
104
111
|
|
|
@@ -117,7 +124,7 @@ export const useClientDocument: {
|
|
|
117
124
|
|
|
118
125
|
const queryRef = useQueryRef(queryDef, {
|
|
119
126
|
otelSpanName: `LiveStore:useClientDocument:${tableName}:${idStr}`,
|
|
120
|
-
store: storeArg?.store,
|
|
127
|
+
...omitUndefineds({ store: storeArg?.store }),
|
|
121
128
|
})
|
|
122
129
|
|
|
123
130
|
const setState = React.useMemo<StateSetters<TTableDef>>(
|
|
@@ -134,10 +141,13 @@ export const useClientDocument: {
|
|
|
134
141
|
}
|
|
135
142
|
|
|
136
143
|
export type Dispatch<A> = (action: A) => void
|
|
137
|
-
export type
|
|
144
|
+
export type SetStateActionPartial<S> = Partial<S> | ((previousValue: S) => Partial<S>)
|
|
145
|
+
export type SetStateAction<S> = S | ((previousValue: S) => S)
|
|
138
146
|
|
|
139
147
|
export type StateSetters<TTableDef extends State.SQLite.ClientDocumentTableDef.TraitAny> = Dispatch<
|
|
140
|
-
|
|
148
|
+
TTableDef[State.SQLite.ClientDocumentTableDefSymbol]['options']['partialSet'] extends false
|
|
149
|
+
? SetStateAction<TTableDef['Value']>
|
|
150
|
+
: SetStateActionPartial<TTableDef['Value']>
|
|
141
151
|
>
|
|
142
152
|
|
|
143
153
|
const validateTableOptions = (table: State.SQLite.TableDef<any, any>) => {
|
package/src/useQuery.ts
CHANGED
|
@@ -42,8 +42,7 @@ export const useQueryRef = <TQuery extends LiveQueryDef.Any>(
|
|
|
42
42
|
queryRcRef: LiveQueries.RcRef<LiveQuery<LiveQueries.GetResult<TQuery>>>
|
|
43
43
|
} => {
|
|
44
44
|
const store =
|
|
45
|
-
options?.store ??
|
|
46
|
-
// biome-ignore lint/correctness/useHookAtTopLevel: store is stable
|
|
45
|
+
options?.store ?? // biome-ignore lint/correctness/useHookAtTopLevel: store is stable
|
|
47
46
|
React.useContext(LiveStoreContext)?.store ??
|
|
48
47
|
shouldNeverHappen(`No store provided to useQuery`)
|
|
49
48
|
|
|
@@ -133,8 +132,9 @@ Stack trace:
|
|
|
133
132
|
// so we're also updating the span name here.
|
|
134
133
|
span.updateName(options?.otelSpanName ?? `LiveStore:useQuery:${query$.label}`)
|
|
135
134
|
|
|
136
|
-
return store.subscribe(
|
|
137
|
-
|
|
135
|
+
return store.subscribe(
|
|
136
|
+
query$,
|
|
137
|
+
(newValue) => {
|
|
138
138
|
// NOTE: we return a reference to the result object within LiveStore;
|
|
139
139
|
// this implies that app code must not mutate the results, or else
|
|
140
140
|
// there may be weird reactivity bugs.
|
|
@@ -142,12 +142,14 @@ Stack trace:
|
|
|
142
142
|
setValue(newValue)
|
|
143
143
|
}
|
|
144
144
|
},
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
{
|
|
146
|
+
onUnsubsubscribe: () => {
|
|
147
|
+
query$.activeSubscriptions.delete(stackInfo)
|
|
148
|
+
},
|
|
149
|
+
label: query$.label,
|
|
150
|
+
otelContext,
|
|
147
151
|
},
|
|
148
|
-
|
|
149
|
-
otelContext,
|
|
150
|
-
})
|
|
152
|
+
)
|
|
151
153
|
}, [stackInfo, query$, setValue, store, valueRef, otelContext, span, options?.otelSpanName])
|
|
152
154
|
|
|
153
155
|
useRcResource(
|