@livestore/livestore 0.3.0-dev.50 → 0.3.0-dev.52

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.
@@ -1,4 +1,5 @@
1
1
  import { isNotNil } from '@livestore/utils'
2
+ import { Predicate } from '@livestore/utils/effect'
2
3
  import type * as otel from '@opentelemetry/api'
3
4
 
4
5
  import * as RG from '../reactive.js'
@@ -22,18 +23,26 @@ export type ReactivityGraphContext = {
22
23
  effectsWrapper: (run: () => void) => void
23
24
  }
24
25
 
25
- export type GetResult<TQuery extends LiveQueryDef.Any | LiveQuery.Any> =
26
- TQuery extends LiveQuery<infer TResult> ? TResult : TQuery extends LiveQueryDef<infer TResult> ? TResult : unknown
26
+ export type GetResult<TQuery extends LiveQueryDef.Any | LiveQuery.Any | SignalDef<any>> =
27
+ TQuery extends LiveQuery<infer TResult>
28
+ ? TResult
29
+ : TQuery extends LiveQueryDef<infer TResult>
30
+ ? TResult
31
+ : TQuery extends SignalDef<infer TResult>
32
+ ? TResult
33
+ : unknown
27
34
 
28
35
  let queryIdCounter = 0
29
36
 
30
- export interface SignalDef<T> {
37
+ export interface SignalDef<T> extends LiveQueryDef<T, 'signal-def'> {
31
38
  _tag: 'signal-def'
32
39
  defaultValue: T
40
+ hash: string
41
+ label: string
33
42
  make: (ctx: ReactivityGraphContext) => RcRef<ISignal<T>>
34
43
  }
35
44
 
36
- export interface ISignal<T> {
45
+ export interface ISignal<T> extends LiveQuery<T> {
37
46
  _tag: 'signal'
38
47
  reactivityGraph: ReactivityGraph
39
48
  ref: RG.Ref<T, ReactivityGraphContext, RefreshReason>
@@ -60,16 +69,17 @@ export const depsToString = (deps: DepKey): string => {
60
69
  return deps.filter(isNotNil).join(',')
61
70
  }
62
71
 
63
- export interface LiveQueryDef<TResult> {
64
- _tag: 'def'
72
+ // TODO we should refactor/clean up how LiveQueryDef / SignalDef / LiveQuery / ISignal are defined (particularly on the type-level)
73
+ export interface LiveQueryDef<TResult, TTag extends string = 'def'> {
74
+ _tag: TTag
65
75
  /** Creates a new LiveQuery instance bound to a specific store/reactivityGraph */
66
- make: (ctx: ReactivityGraphContext, otelContext?: otel.Context) => RcRef<LiveQuery<TResult>>
76
+ make: (ctx: ReactivityGraphContext, otelContext?: otel.Context) => RcRef<LiveQuery<TResult> | ISignal<TResult>>
67
77
  label: string
68
78
  hash: string
69
79
  }
70
80
 
71
81
  export namespace LiveQueryDef {
72
- export type Any = LiveQueryDef<any>
82
+ export type Any = LiveQueryDef<any, 'def' | 'signal-def'>
73
83
  }
74
84
 
75
85
  /**
@@ -77,7 +87,7 @@ export namespace LiveQueryDef {
77
87
  */
78
88
  export interface LiveQuery<TResult> {
79
89
  id: number
80
- _tag: 'computed' | 'db' | 'graphql'
90
+ _tag: 'computed' | 'db' | 'graphql' | 'signal'
81
91
  [TypeId]: TypeId
82
92
 
83
93
  // reactivityGraph: ReactivityGraph
@@ -86,7 +96,7 @@ export interface LiveQuery<TResult> {
86
96
  '__result!': TResult
87
97
 
88
98
  /** A reactive thunk representing the query results */
89
- results$: RG.Thunk<TResult, ReactivityGraphContext, RefreshReason>
99
+ results$: RG.Atom<TResult, ReactivityGraphContext, RefreshReason>
90
100
 
91
101
  label: string
92
102
 
@@ -106,7 +116,7 @@ export interface LiveQuery<TResult> {
106
116
  runs: number
107
117
 
108
118
  executionTimes: number[]
109
- def: LiveQueryDef<TResult>
119
+ def: LiveQueryDef<TResult> | SignalDef<TResult>
110
120
  }
111
121
 
112
122
  export namespace LiveQuery {
@@ -117,21 +127,24 @@ export abstract class LiveStoreQueryBase<TResult> implements LiveQuery<TResult>
117
127
  '__result!'!: TResult
118
128
  id = queryIdCounter++;
119
129
  [TypeId]: TypeId = TypeId
120
- abstract _tag: 'computed' | 'db' | 'graphql'
130
+ abstract _tag: 'computed' | 'db' | 'graphql' | 'signal'
121
131
 
122
132
  /** Human-readable label for the query for debugging */
123
133
  abstract label: string
124
134
 
125
- abstract def: LiveQueryDef<TResult>
135
+ abstract def: LiveQueryDef<TResult> | SignalDef<TResult>
126
136
 
127
- abstract results$: RG.Thunk<TResult, ReactivityGraphContext, RefreshReason>
137
+ abstract results$: RG.Atom<TResult, ReactivityGraphContext, RefreshReason>
128
138
 
129
139
  activeSubscriptions: Set<StackInfo> = new Set()
130
140
 
131
141
  abstract readonly reactivityGraph: ReactivityGraph
132
142
 
133
143
  get runs() {
134
- return this.results$.recomputations
144
+ if (this.results$._tag === 'thunk') {
145
+ return this.results$.recomputations
146
+ }
147
+ return 0
135
148
  }
136
149
 
137
150
  executionTimes: number[] = []
@@ -183,7 +196,9 @@ export const makeGetAtomResult = (
183
196
  }
184
197
 
185
198
  // Signal case
186
- if (atom._tag === 'signal') return get(atom.ref, otelContext, debugRefreshReason)
199
+ if (atom._tag === 'signal' && Predicate.hasProperty(atom, 'ref')) {
200
+ return get(atom.ref, otelContext, debugRefreshReason)
201
+ }
187
202
 
188
203
  // LiveQuery case
189
204
  return get(atom.results$, otelContext, debugRefreshReason)
@@ -19,7 +19,7 @@ export const computed = <TResult>(
19
19
  throw new Error(`On Expo/React Native, computed queries must provide a \`deps\` option`)
20
20
  }
21
21
 
22
- const def: LiveQueryDef.Any = {
22
+ const def: LiveQueryDef<any> = {
23
23
  _tag: 'def',
24
24
  make: withRCMap(hash, (ctx, _otelContext) => {
25
25
  // TODO onDestroy
@@ -41,7 +41,7 @@ export const computed = <TResult>(
41
41
  }
42
42
 
43
43
  export class LiveStoreComputedQuery<TResult> extends LiveStoreQueryBase<TResult> {
44
- _tag: 'computed' = 'computed'
44
+ _tag = 'computed' as const
45
45
 
46
46
  /** A reactive thunk representing the query results */
47
47
  results$: Thunk<TResult, ReactivityGraphContext, RefreshReason>
@@ -102,8 +102,10 @@ export const queryDb: {
102
102
  } = (queryInput, options) => {
103
103
  const { queryString, extraDeps } = getQueryStringAndExtraDeps(queryInput)
104
104
 
105
- const hash =
106
- (options?.deps ? queryString + '-' + depsToString(options.deps) : queryString) + '-' + depsToString(extraDeps)
105
+ const hash = [queryString, options?.deps ? depsToString(options.deps) : undefined, depsToString(extraDeps)]
106
+ .filter(Boolean)
107
+ .join('-')
108
+
107
109
  if (isValidFunctionString(hash)._tag === 'invalid') {
108
110
  throw new Error(`On Expo/React Native, db queries must provide a \`deps\` option`)
109
111
  }
@@ -114,7 +116,7 @@ export const queryDb: {
114
116
 
115
117
  const label = options?.label ?? queryString
116
118
 
117
- const def: LiveQueryDef.Any = {
119
+ const def: LiveQueryDef<any> = {
118
120
  _tag: 'def',
119
121
  make: withRCMap(hash, (ctx, otelContext) => {
120
122
  // TODO onDestroy
@@ -165,7 +167,7 @@ const getQueryStringAndExtraDeps = (
165
167
 
166
168
  /* An object encapsulating a reactive SQL query */
167
169
  export class LiveStoreDbQuery<TResultSchema, TResult = TResultSchema> extends LiveStoreQueryBase<TResult> {
168
- _tag: 'db' = 'db'
170
+ _tag = 'db' as const
169
171
 
170
172
  /** A reactive thunk representing the query text */
171
173
  queryInput$: Thunk<QueryInputRaw<any, any>, ReactivityGraphContext, RefreshReason> | undefined
@@ -3,7 +3,7 @@ import { nanoid } from '@livestore/utils/nanoid'
3
3
  import type * as RG from '../reactive.js'
4
4
  import type { RefreshReason } from '../store/store-types.js'
5
5
  import type { ISignal, ReactivityGraph, ReactivityGraphContext, SignalDef } from './base-class.js'
6
- import { withRCMap } from './base-class.js'
6
+ import { LiveStoreQueryBase, withRCMap } from './base-class.js'
7
7
 
8
8
  export const signal = <T>(
9
9
  defaultValue: T,
@@ -12,25 +12,59 @@ export const signal = <T>(
12
12
  },
13
13
  ): SignalDef<T> => {
14
14
  const id = nanoid()
15
- return {
15
+ const def: SignalDef<T> = {
16
16
  _tag: 'signal-def',
17
17
  defaultValue,
18
- make: withRCMap(id, (ctx) => new Signal(defaultValue, ctx.reactivityGraph.deref()!, options)),
18
+ hash: id,
19
+ label: options?.label ?? 'Signal',
20
+ make: withRCMap(
21
+ id,
22
+ (ctx) =>
23
+ new Signal({
24
+ defaultValue,
25
+ reactivityGraph: ctx.reactivityGraph.deref()!,
26
+ label: options?.label ?? 'Signal',
27
+ def,
28
+ }),
29
+ ),
19
30
  }
31
+
32
+ return def
20
33
  }
21
34
 
22
- export class Signal<T> implements ISignal<T> {
35
+ export class Signal<T> extends LiveStoreQueryBase<T> implements ISignal<T> {
23
36
  _tag = 'signal' as const
24
37
  readonly ref: RG.Ref<T, ReactivityGraphContext, RefreshReason>
25
-
38
+ label: string
39
+ reactivityGraph: ReactivityGraph
40
+ results$: RG.Ref<T, ReactivityGraphContext, RefreshReason>
41
+ def: SignalDef<T>
26
42
  constructor(
27
- private defaultValue: T,
28
- readonly reactivityGraph: ReactivityGraph,
29
- private options?: {
30
- label?: string
43
+ // private defaultValue: T,
44
+ // readonly reactivityGraph: ReactivityGraph,
45
+ // private options?: {
46
+ // label?: string
47
+ // },
48
+ {
49
+ defaultValue,
50
+ reactivityGraph,
51
+ label,
52
+ def,
53
+ }: {
54
+ defaultValue: T
55
+ reactivityGraph: ReactivityGraph
56
+ label: string
57
+ def: SignalDef<T>
31
58
  },
32
59
  ) {
33
- this.ref = reactivityGraph.makeRef(defaultValue, { label: options?.label })
60
+ super()
61
+
62
+ this.ref = reactivityGraph.makeRef(defaultValue, { label })
63
+ this.label = label
64
+ this.reactivityGraph = reactivityGraph
65
+ this.def = def
66
+
67
+ this.results$ = this.ref
34
68
  }
35
69
 
36
70
  set = (value: T) => {
package/src/mod.ts CHANGED
@@ -13,7 +13,16 @@ export {
13
13
 
14
14
  export { SqliteDbWrapper, emptyDebugInfo } from './SqliteDbWrapper.js'
15
15
 
16
- export { queryDb, computed, signal, type LiveQuery, type LiveQueryDef } from './live-queries/mod.js'
16
+ export {
17
+ queryDb,
18
+ computed,
19
+ signal,
20
+ type LiveQuery,
21
+ type LiveQueryDef,
22
+ type Signal,
23
+ type SignalDef,
24
+ type RcRef,
25
+ } from './live-queries/mod.js'
17
26
 
18
27
  export * from '@livestore/common/schema'
19
28
  export {
@@ -135,7 +135,9 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
135
135
  this.sqliteDbWrapper.execute(statementSql, bindValues, { otelContext, writeTables })
136
136
 
137
137
  // durationMsTotal += durationMs
138
- writeTables.forEach((table) => writeTablesForEvent.add(table))
138
+ for (const table of writeTables) {
139
+ writeTablesForEvent.add(table)
140
+ }
139
141
  }
140
142
  }
141
143
 
@@ -262,7 +264,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
262
264
  * ```
263
265
  */
264
266
  subscribe = <TResult>(
265
- query: LiveQueryDef<TResult> | LiveQuery<TResult>,
267
+ query: LiveQueryDef<TResult, 'def' | 'signal-def'> | LiveQuery<TResult>,
266
268
  options: {
267
269
  /** Called when the query result has changed */
268
270
  onUpdate: (value: TResult) => void
@@ -289,10 +291,10 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
289
291
  const otelContext = otel.trace.setSpan(otel.context.active(), span)
290
292
 
291
293
  const queryRcRef =
292
- query._tag === 'def'
294
+ query._tag === 'def' || query._tag === 'signal-def'
293
295
  ? query.make(this.reactivityGraph.context!)
294
296
  : {
295
- value: query,
297
+ value: query as LiveQuery<TResult>,
296
298
  deref: () => {},
297
299
  }
298
300
  const query$ = queryRcRef.value