@livestore/livestore 0.0.0-snapshot-2b8a9de3ec1a701aca891ebc2c98eb328274ae9e → 0.0.0-snapshot-2c861249e50661661613204300b1fc0d902c2e46

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.
Files changed (68) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/SqliteDbWrapper.d.ts +7 -1
  3. package/dist/SqliteDbWrapper.d.ts.map +1 -1
  4. package/dist/SqliteDbWrapper.js +8 -2
  5. package/dist/SqliteDbWrapper.js.map +1 -1
  6. package/dist/live-queries/base-class.d.ts +8 -12
  7. package/dist/live-queries/base-class.d.ts.map +1 -1
  8. package/dist/live-queries/base-class.js.map +1 -1
  9. package/dist/live-queries/computed.d.ts +4 -8
  10. package/dist/live-queries/computed.d.ts.map +1 -1
  11. package/dist/live-queries/computed.js +1 -6
  12. package/dist/live-queries/computed.js.map +1 -1
  13. package/dist/live-queries/db-query.d.ts +13 -18
  14. package/dist/live-queries/db-query.d.ts.map +1 -1
  15. package/dist/live-queries/db-query.js +31 -30
  16. package/dist/live-queries/db-query.js.map +1 -1
  17. package/dist/live-queries/db-query.test.js +7 -7
  18. package/dist/live-queries/db-query.test.js.map +1 -1
  19. package/dist/live-queries/make-ref.d.ts.map +1 -1
  20. package/dist/live-queries/make-ref.js +1 -0
  21. package/dist/live-queries/make-ref.js.map +1 -1
  22. package/dist/live-queries/row-query-utils.d.ts +12 -0
  23. package/dist/live-queries/row-query-utils.d.ts.map +1 -0
  24. package/dist/live-queries/row-query-utils.js +18 -0
  25. package/dist/live-queries/row-query-utils.js.map +1 -0
  26. package/dist/mod.d.ts +0 -1
  27. package/dist/mod.d.ts.map +1 -1
  28. package/dist/mod.js +0 -1
  29. package/dist/mod.js.map +1 -1
  30. package/dist/reactive.d.ts.map +1 -1
  31. package/dist/reactive.js +1 -1
  32. package/dist/reactive.js.map +1 -1
  33. package/dist/store/create-store.d.ts +12 -2
  34. package/dist/store/create-store.d.ts.map +1 -1
  35. package/dist/store/create-store.js +18 -11
  36. package/dist/store/create-store.js.map +1 -1
  37. package/dist/store/store-types.d.ts +11 -10
  38. package/dist/store/store-types.d.ts.map +1 -1
  39. package/dist/store/store.d.ts +29 -30
  40. package/dist/store/store.d.ts.map +1 -1
  41. package/dist/store/store.js +84 -85
  42. package/dist/store/store.js.map +1 -1
  43. package/dist/utils/stack-info.test.js +6 -6
  44. package/dist/utils/tests/fixture.d.ts +54 -207
  45. package/dist/utils/tests/fixture.d.ts.map +1 -1
  46. package/dist/utils/tests/fixture.js +20 -13
  47. package/dist/utils/tests/fixture.js.map +1 -1
  48. package/package.json +4 -4
  49. package/src/SqliteDbWrapper.ts +12 -3
  50. package/src/live-queries/__snapshots__/db-query.test.ts.snap +9 -9
  51. package/src/live-queries/base-class.ts +8 -25
  52. package/src/live-queries/computed.ts +4 -18
  53. package/src/live-queries/db-query.test.ts +7 -7
  54. package/src/live-queries/db-query.ts +57 -63
  55. package/src/live-queries/make-ref.ts +2 -0
  56. package/src/live-queries/row-query-utils.ts +50 -0
  57. package/src/mod.ts +0 -2
  58. package/src/reactive.ts +1 -1
  59. package/src/store/create-store.ts +33 -15
  60. package/src/store/store-types.ts +11 -11
  61. package/src/store/store.ts +120 -122
  62. package/src/utils/stack-info.test.ts +6 -6
  63. package/src/utils/tests/fixture.ts +21 -25
  64. package/dist/row-query-utils.d.ts +0 -17
  65. package/dist/row-query-utils.d.ts.map +0 -1
  66. package/dist/row-query-utils.js +0 -34
  67. package/dist/row-query-utils.js.map +0 -1
  68. package/src/row-query-utils.ts +0 -76
@@ -1,4 +1,3 @@
1
- import type { QueryInfo } from '@livestore/common'
2
1
  import { getDurationMsFromSpan } from '@livestore/common'
3
2
  import * as otel from '@opentelemetry/api'
4
3
 
@@ -8,29 +7,25 @@ import { isValidFunctionString } from '../utils/function-string.js'
8
7
  import type { DepKey, GetAtomResult, LiveQueryDef, ReactivityGraph, ReactivityGraphContext } from './base-class.js'
9
8
  import { depsToString, LiveStoreQueryBase, makeGetAtomResult, withRCMap } from './base-class.js'
10
9
 
11
- export const computed = <TResult, TQueryInfo extends QueryInfo = QueryInfo.None>(
10
+ export const computed = <TResult>(
12
11
  fn: (get: GetAtomResult) => TResult,
13
12
  options?: {
14
13
  label?: string
15
- queryInfo?: TQueryInfo
16
14
  deps?: DepKey
17
15
  },
18
- ): LiveQueryDef<TResult, TQueryInfo> => {
16
+ ): LiveQueryDef<TResult> => {
19
17
  const hash = options?.deps ? depsToString(options.deps) : fn.toString()
20
18
  if (isValidFunctionString(hash)._tag === 'invalid') {
21
19
  throw new Error(`On Expo/React Native, computed queries must provide a \`deps\` option`)
22
20
  }
23
21
 
24
- const queryInfo = options?.queryInfo ?? ({ _tag: 'None' } as TQueryInfo)
25
-
26
22
  return {
27
23
  _tag: 'def',
28
24
  make: withRCMap(hash, (ctx, _otelContext) => {
29
25
  // TODO onDestroy
30
- return new LiveStoreComputedQuery<TResult, TQueryInfo>({
26
+ return new LiveStoreComputedQuery<TResult>({
31
27
  fn,
32
28
  label: options?.label ?? fn.toString(),
33
- queryInfo: options?.queryInfo,
34
29
  reactivityGraph: ctx.reactivityGraph.deref()!,
35
30
  })
36
31
  }),
@@ -39,14 +34,10 @@ export const computed = <TResult, TQueryInfo extends QueryInfo = QueryInfo.None>
39
34
  // TODO we should figure out whether this could cause some problems and/or if there's a better way to do this
40
35
  // NOTE `fn.toString()` doesn't work in Expo as it always produces `[native code]`
41
36
  hash,
42
- queryInfo,
43
37
  }
44
38
  }
45
39
 
46
- export class LiveStoreComputedQuery<TResult, TQueryInfo extends QueryInfo = QueryInfo.None> extends LiveStoreQueryBase<
47
- TResult,
48
- TQueryInfo
49
- > {
40
+ export class LiveStoreComputedQuery<TResult> extends LiveStoreQueryBase<TResult> {
50
41
  _tag: 'computed' = 'computed'
51
42
 
52
43
  /** A reactive thunk representing the query results */
@@ -56,24 +47,19 @@ export class LiveStoreComputedQuery<TResult, TQueryInfo extends QueryInfo = Quer
56
47
 
57
48
  reactivityGraph: ReactivityGraph
58
49
 
59
- queryInfo: TQueryInfo
60
-
61
50
  constructor({
62
51
  fn,
63
52
  label,
64
53
  reactivityGraph,
65
- queryInfo,
66
54
  }: {
67
55
  label: string
68
56
  fn: (get: GetAtomResult) => TResult
69
57
  reactivityGraph: ReactivityGraph
70
- queryInfo?: TQueryInfo
71
58
  }) {
72
59
  super()
73
60
 
74
61
  this.label = label
75
62
  this.reactivityGraph = reactivityGraph
76
- this.queryInfo = queryInfo ?? ({ _tag: 'None' } as TQueryInfo)
77
63
 
78
64
  const queryLabel = `${label}:results`
79
65
 
@@ -1,5 +1,5 @@
1
1
  import { sql } from '@livestore/common'
2
- import { rawSqlMutation } from '@livestore/common/schema'
2
+ import { rawSqlEvent } from '@livestore/common/schema'
3
3
  import { Effect, ReadonlyRecord, Schema } from '@livestore/utils/effect'
4
4
  import { Vitest } from '@livestore/utils/node-vitest'
5
5
  import * as otel from '@opentelemetry/api'
@@ -64,12 +64,12 @@ Vitest.describe('otel', () => {
64
64
 
65
65
  const query$ = queryDb({
66
66
  query: `select * from todos`,
67
- schema: Schema.Array(tables.todos.schema),
67
+ schema: Schema.Array(tables.todos.rowSchema),
68
68
  queriedTables: new Set(['todos']),
69
69
  })
70
70
  expect(store.query(query$)).toMatchInlineSnapshot('[]')
71
71
 
72
- store.commit(rawSqlMutation({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
72
+ store.commit(rawSqlEvent({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
73
73
 
74
74
  expect(store.query(query$)).toMatchInlineSnapshot(`
75
75
  [
@@ -100,7 +100,7 @@ Vitest.describe('otel', () => {
100
100
  const query$ = queryDb(
101
101
  (get) => ({
102
102
  query: `select * from todos ${get(filter)}`,
103
- schema: Schema.Array(tables.todos.schema).pipe(Schema.headOrElse(() => defaultTodo)),
103
+ schema: Schema.Array(tables.todos.rowSchema).pipe(Schema.headOrElse(() => defaultTodo)),
104
104
  }),
105
105
  { label: 'all todos' },
106
106
  )
@@ -117,7 +117,7 @@ Vitest.describe('otel', () => {
117
117
 
118
118
  expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
119
119
 
120
- store.commit(rawSqlMutation({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
120
+ store.commit(rawSqlEvent({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
121
121
 
122
122
  expect(store.reactivityGraph.getSnapshot({ includeResults: true })).toMatchSnapshot()
123
123
 
@@ -147,7 +147,7 @@ Vitest.describe('otel', () => {
147
147
  const defaultTodo = { id: '', text: '', completed: false }
148
148
 
149
149
  const filter = computed(() => ({ completed: false }))
150
- const query$ = queryDb((get) => tables.todos.query.where(get(filter)).first({ fallback: () => defaultTodo }))
150
+ const query$ = queryDb((get) => tables.todos.where(get(filter)).first({ fallback: () => defaultTodo }))
151
151
 
152
152
  expect(store.query(query$)).toMatchInlineSnapshot(`
153
153
  {
@@ -157,7 +157,7 @@ Vitest.describe('otel', () => {
157
157
  }
158
158
  `)
159
159
 
160
- store.commit(rawSqlMutation({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
160
+ store.commit(rawSqlEvent({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
161
161
 
162
162
  expect(store.query(query$)).toMatchInlineSnapshot(`
163
163
  {
@@ -1,4 +1,4 @@
1
- import type { Bindable, QueryBuilder, QueryInfo } from '@livestore/common'
1
+ import type { Bindable, QueryBuilder } from '@livestore/common'
2
2
  import {
3
3
  getDurationMsFromSpan,
4
4
  getResultSchema,
@@ -6,6 +6,7 @@ import {
6
6
  prepareBindValues,
7
7
  QueryBuilderAstSymbol,
8
8
  replaceSessionIdSymbol,
9
+ SessionIdSymbol,
9
10
  UnexpectedError,
10
11
  } from '@livestore/common'
11
12
  import { deepEqual, shouldNeverHappen } from '@livestore/utils'
@@ -14,13 +15,13 @@ import * as otel from '@opentelemetry/api'
14
15
 
15
16
  import type { Thunk } from '../reactive.js'
16
17
  import { isThunk, NOT_REFRESHED_YET } from '../reactive.js'
17
- import { makeExecBeforeFirstRun, rowQueryLabel } from '../row-query-utils.js'
18
18
  import type { RefreshReason } from '../store/store-types.js'
19
19
  import { isValidFunctionString } from '../utils/function-string.js'
20
20
  import type { DepKey, GetAtomResult, LiveQueryDef, ReactivityGraph, ReactivityGraphContext } from './base-class.js'
21
21
  import { depsToString, LiveStoreQueryBase, makeGetAtomResult, withRCMap } from './base-class.js'
22
+ import { makeExecBeforeFirstRun, rowQueryLabel } from './row-query-utils.js'
22
23
 
23
- export type QueryInputRaw<TDecoded, TEncoded, TQueryInfo extends QueryInfo> = {
24
+ export type QueryInputRaw<TDecoded, TEncoded> = {
24
25
  query: string
25
26
  schema: Schema.Schema<TDecoded, TEncoded>
26
27
  bindValues?: Bindable
@@ -30,25 +31,20 @@ export type QueryInputRaw<TDecoded, TEncoded, TQueryInfo extends QueryInfo> = {
30
31
  * NOTE In the future we want to do this automatically at build time
31
32
  */
32
33
  queriedTables?: Set<string>
33
- queryInfo?: TQueryInfo
34
34
  execBeforeFirstRun?: (ctx: ReactivityGraphContext) => void
35
35
  }
36
36
 
37
- export const isQueryInputRaw = (value: unknown): value is QueryInputRaw<any, any, any> =>
37
+ export const isQueryInputRaw = (value: unknown): value is QueryInputRaw<any, any> =>
38
38
  Predicate.hasProperty(value, 'query') && Predicate.hasProperty(value, 'schema')
39
39
 
40
- export type QueryInput<TDecoded, TEncoded, TQueryInfo extends QueryInfo> =
41
- | QueryInputRaw<TDecoded, TEncoded, TQueryInfo>
42
- | QueryBuilder<TDecoded, any, any, TQueryInfo>
40
+ export type QueryInput<TDecoded, TEncoded> = QueryInputRaw<TDecoded, TEncoded> | QueryBuilder<TDecoded, any, any>
43
41
 
44
42
  /**
45
- * NOTE `query` is only supposed to read data. Don't use it to insert/update/delete data but use mutations instead.
43
+ * NOTE `queryDb` is only supposed to read data. Don't use it to insert/update/delete data but use events instead.
46
44
  */
47
45
  export const queryDb: {
48
- <TResultSchema, TResult = TResultSchema, TQueryInfo extends QueryInfo = QueryInfo.None>(
49
- queryInput:
50
- | QueryInputRaw<TResultSchema, ReadonlyArray<any>, TQueryInfo>
51
- | QueryBuilder<TResultSchema, any, any, TQueryInfo>,
46
+ <TResultSchema, TResult = TResultSchema>(
47
+ queryInput: QueryInputRaw<TResultSchema, ReadonlyArray<any>> | QueryBuilder<TResultSchema, any, any>,
52
48
  options?: {
53
49
  map?: (rows: TResultSchema) => TResult
54
50
  /**
@@ -56,16 +52,15 @@ export const queryDb: {
56
52
  */
57
53
  label?: string
58
54
  deps?: DepKey
59
- queryInfo?: TQueryInfo
60
55
  },
61
- ): LiveQueryDef<TResult, TQueryInfo>
56
+ ): LiveQueryDef<TResult>
62
57
  // NOTE in this "thunk case", we can't directly derive label/queryInfo from the queryInput,
63
58
  // so the caller needs to provide them explicitly otherwise queryInfo will be set to `None`,
64
59
  // and label will be set during the query execution
65
- <TResultSchema, TResult = TResultSchema, TQueryInfo extends QueryInfo = QueryInfo.None>(
60
+ <TResultSchema, TResult = TResultSchema>(
66
61
  queryInput:
67
- | ((get: GetAtomResult) => QueryInputRaw<TResultSchema, ReadonlyArray<any>, TQueryInfo>)
68
- | ((get: GetAtomResult) => QueryBuilder<TResultSchema, any, any, TQueryInfo>),
62
+ | ((get: GetAtomResult) => QueryInputRaw<TResultSchema, ReadonlyArray<any>>)
63
+ | ((get: GetAtomResult) => QueryBuilder<TResultSchema, any, any>),
69
64
  options?: {
70
65
  map?: (rows: TResultSchema) => TResult
71
66
  /**
@@ -73,23 +68,21 @@ export const queryDb: {
73
68
  */
74
69
  label?: string
75
70
  deps?: DepKey
76
- queryInfo?: TQueryInfo
77
71
  },
78
- ): LiveQueryDef<TResult, TQueryInfo>
72
+ ): LiveQueryDef<TResult>
79
73
  } = (queryInput, options) => {
80
- const queryString = isQueryBuilder(queryInput)
81
- ? queryInput.toString()
82
- : isQueryInputRaw(queryInput)
83
- ? queryInput.query
84
- : typeof queryInput === 'function'
85
- ? queryInput.toString()
86
- : shouldNeverHappen(`Invalid query input: ${queryInput}`)
87
-
88
- const hash = options?.deps ? queryString + '-' + depsToString(options.deps) : queryString
74
+ const { queryString, extraDeps } = getQueryStringAndExtraDeps(queryInput)
75
+
76
+ const hash =
77
+ (options?.deps ? queryString + '-' + depsToString(options.deps) : queryString) + '-' + depsToString(extraDeps)
89
78
  if (isValidFunctionString(hash)._tag === 'invalid') {
90
79
  throw new Error(`On Expo/React Native, db queries must provide a \`deps\` option`)
91
80
  }
92
81
 
82
+ if (hash.trim() === '') {
83
+ return shouldNeverHappen(`Invalid query hash for query: ${queryInput}`)
84
+ }
85
+
93
86
  const label = options?.label ?? queryString
94
87
 
95
88
  return {
@@ -101,36 +94,55 @@ export const queryDb: {
101
94
  queryInput,
102
95
  label,
103
96
  map: options?.map,
104
- // We're not falling back to `None` here as the queryInfo will be set dynamically
105
- queryInfo: options?.queryInfo,
106
97
  otelContext,
107
98
  })
108
99
  }),
109
100
  label,
110
101
  hash,
111
- queryInfo:
112
- options?.queryInfo ?? (isQueryBuilder(queryInput) ? queryInfoFromQueryBuilder(queryInput) : { _tag: 'None' }),
113
102
  }
114
103
  }
115
104
 
105
+ const bindValuesToDepKey = (bindValues: Bindable | undefined): DepKey => {
106
+ if (bindValues === undefined) {
107
+ return []
108
+ }
109
+
110
+ return Object.entries(bindValues)
111
+ .map(([key, value]: [string, any]) => `${key}:${value === SessionIdSymbol ? 'SessionIdSymbol' : value}`)
112
+ .join(',')
113
+ }
114
+
115
+ const getQueryStringAndExtraDeps = (
116
+ queryInput: QueryInput<any, any> | ((get: GetAtomResult) => QueryInput<any, any>),
117
+ ): { queryString: string; extraDeps: DepKey } => {
118
+ if (isQueryBuilder(queryInput)) {
119
+ const { query, bindValues } = queryInput.asSql()
120
+ return { queryString: query, extraDeps: bindValuesToDepKey(bindValues) }
121
+ }
122
+
123
+ if (isQueryInputRaw(queryInput)) {
124
+ return { queryString: queryInput.query, extraDeps: bindValuesToDepKey(queryInput.bindValues) }
125
+ }
126
+
127
+ if (typeof queryInput === 'function') {
128
+ return { queryString: queryInput.toString(), extraDeps: [] }
129
+ }
130
+
131
+ return shouldNeverHappen(`Invalid query input: ${queryInput}`)
132
+ }
133
+
116
134
  /* An object encapsulating a reactive SQL query */
117
- export class LiveStoreDbQuery<
118
- TResultSchema,
119
- TResult = TResultSchema,
120
- TQueryInfo extends QueryInfo = QueryInfo.None,
121
- > extends LiveStoreQueryBase<TResult, TQueryInfo> {
135
+ export class LiveStoreDbQuery<TResultSchema, TResult = TResultSchema> extends LiveStoreQueryBase<TResult> {
122
136
  _tag: 'db' = 'db'
123
137
 
124
138
  /** A reactive thunk representing the query text */
125
- queryInput$: Thunk<QueryInputRaw<any, any, QueryInfo>, ReactivityGraphContext, RefreshReason> | undefined
139
+ queryInput$: Thunk<QueryInputRaw<any, any>, ReactivityGraphContext, RefreshReason> | undefined
126
140
 
127
141
  /** A reactive thunk representing the query results */
128
142
  results$: Thunk<TResult, ReactivityGraphContext, RefreshReason>
129
143
 
130
144
  label: string
131
145
 
132
- queryInfo: TQueryInfo
133
-
134
146
  readonly reactivityGraph
135
147
 
136
148
  private mapResult: (rows: TResultSchema) => TResult
@@ -140,23 +152,20 @@ export class LiveStoreDbQuery<
140
152
  label: inputLabel,
141
153
  reactivityGraph,
142
154
  map,
143
- queryInfo: inputQueryInfo,
144
155
  otelContext,
145
156
  }: {
146
157
  label?: string
147
158
  queryInput:
148
- | QueryInput<TResultSchema, ReadonlyArray<any>, TQueryInfo>
149
- | ((get: GetAtomResult, ctx: ReactivityGraphContext) => QueryInput<TResultSchema, ReadonlyArray<any>, TQueryInfo>)
159
+ | QueryInput<TResultSchema, ReadonlyArray<any>>
160
+ | ((get: GetAtomResult, ctx: ReactivityGraphContext) => QueryInput<TResultSchema, ReadonlyArray<any>>)
150
161
  reactivityGraph: ReactivityGraph
151
162
  map?: (rows: TResultSchema) => TResult
152
- queryInfo?: TQueryInfo
153
163
  /** Only used for the initial query execution */
154
164
  otelContext?: otel.Context
155
165
  }) {
156
166
  super()
157
167
 
158
168
  let label = inputLabel ?? 'db(unknown)'
159
- let queryInfo = inputQueryInfo ?? ({ _tag: 'None' } as TQueryInfo)
160
169
  this.reactivityGraph = reactivityGraph
161
170
 
162
171
  this.mapResult = map === undefined ? (rows: any) => rows as TResult : map
@@ -172,7 +181,7 @@ export class LiveStoreDbQuery<
172
181
  current: undefined,
173
182
  }
174
183
 
175
- type TQueryInputRaw = QueryInputRaw<any, any, QueryInfo>
184
+ type TQueryInputRaw = QueryInputRaw<any, any>
176
185
 
177
186
  let queryInputRaw$OrQueryInputRaw: TQueryInputRaw | Thunk<TQueryInputRaw, ReactivityGraphContext, RefreshReason>
178
187
 
@@ -188,14 +197,13 @@ export class LiveStoreDbQuery<
188
197
  schema,
189
198
  bindValues: qbRes.bindValues,
190
199
  queriedTables: new Set([ast.tableDef.sqliteDef.name]),
191
- queryInfo: queryInfoFromQueryBuilder(qb),
192
200
  } satisfies TQueryInputRaw,
193
201
  label: ast._tag === 'RowQuery' ? rowQueryLabel(ast.tableDef, ast.id) : qb.toString(),
194
202
  execBeforeFirstRun:
195
203
  ast._tag === 'RowQuery'
196
204
  ? makeExecBeforeFirstRun({
197
205
  table: ast.tableDef,
198
- insertValues: ast.insertValues,
206
+ explicitDefaultValues: ast.explicitDefaultValues,
199
207
  id: ast.id,
200
208
  otelContext,
201
209
  })
@@ -232,10 +240,6 @@ export class LiveStoreDbQuery<
232
240
 
233
241
  schemaRef.current = queryInputRaw.schema
234
242
 
235
- if (inputQueryInfo === undefined && queryInputRaw.queryInfo !== undefined) {
236
- queryInfo = queryInputRaw.queryInfo as TQueryInfo
237
- }
238
-
239
243
  return queryInputRaw
240
244
  },
241
245
  {
@@ -268,10 +272,6 @@ export class LiveStoreDbQuery<
268
272
  label = `db(${rowQueryLabel(ast.tableDef, ast.id)})`
269
273
  }
270
274
  }
271
-
272
- if (inputQueryInfo === undefined && queryInputRaw.queryInfo !== undefined) {
273
- queryInfo = queryInputRaw.queryInfo as TQueryInfo
274
- }
275
275
  }
276
276
 
277
277
  const queriedTablesRef: { current: Set<string> | undefined } = { current: undefined }
@@ -388,7 +388,6 @@ Result:`,
388
388
  this.results$ = results$
389
389
 
390
390
  this.label = label
391
- this.queryInfo = queryInfo
392
391
  }
393
392
 
394
393
  destroy = () => {
@@ -405,8 +404,3 @@ Result:`,
405
404
  }
406
405
  }
407
406
  }
408
-
409
- const queryInfoFromQueryBuilder = (qb: QueryBuilder.Any): QueryInfo.Row | QueryInfo.None => {
410
- const ast = qb[QueryBuilderAstSymbol]
411
- return ast._tag === 'RowQuery' ? { _tag: 'Row', table: ast.tableDef, id: ast.id } : { _tag: 'None' }
412
- }
@@ -5,6 +5,8 @@ import type { RefreshReason } from '../store/store-types.js'
5
5
  import type { ILiveQueryRef, ILiveQueryRefDef, ReactivityGraph, ReactivityGraphContext } from './base-class.js'
6
6
  import { withRCMap } from './base-class.js'
7
7
 
8
+ // TODO rename to `signal`
9
+
8
10
  export const makeRef = <T>(
9
11
  defaultValue: T,
10
12
  options?: {
@@ -0,0 +1,50 @@
1
+ import type { PreparedBindValues } from '@livestore/common'
2
+ import { SessionIdSymbol } from '@livestore/common'
3
+ import { State } from '@livestore/common/schema'
4
+ import { shouldNeverHappen } from '@livestore/utils'
5
+ import type * as otel from '@opentelemetry/api'
6
+
7
+ import type { ReactivityGraphContext } from './base-class.js'
8
+
9
+ export const rowQueryLabel = (table: State.SQLite.TableDefBase, id: string | SessionIdSymbol | number | undefined) =>
10
+ `row:${table.sqliteDef.name}${id === undefined ? '' : id === SessionIdSymbol ? `:sessionId` : `:${id}`}`
11
+
12
+ export const makeExecBeforeFirstRun =
13
+ ({
14
+ id,
15
+ explicitDefaultValues,
16
+ table,
17
+ otelContext: otelContext_,
18
+ }: {
19
+ id?: string | SessionIdSymbol | number
20
+ explicitDefaultValues?: any
21
+ table: State.SQLite.TableDefBase
22
+ otelContext: otel.Context | undefined
23
+ }) =>
24
+ ({ store }: ReactivityGraphContext) => {
25
+ if (State.SQLite.tableIsClientDocumentTable(table) === false) {
26
+ return shouldNeverHappen(
27
+ `Cannot insert row for table "${table.sqliteDef.name}" which does not have 'deriveEvents: true' set`,
28
+ )
29
+ }
30
+
31
+ const otelContext = otelContext_ ?? store.otel.queriesSpanContext
32
+
33
+ const idVal = id === SessionIdSymbol ? store.sessionId : id!
34
+ const rowExists =
35
+ store.sqliteDbWrapper.select(
36
+ `SELECT 1 FROM '${table.sqliteDef.name}' WHERE id = ?`,
37
+ [idVal] as any as PreparedBindValues,
38
+ { otelContext },
39
+ ).length === 1
40
+
41
+ if (rowExists) return
42
+
43
+ // It's important that we only commit and don't refresh here, as this function might be called during a render
44
+ // and otherwise we might end up in a "reactive loop"
45
+
46
+ store.commit(
47
+ { otelContext, skipRefresh: true, label: `rowQuery:${table.sqliteDef.name}:${idVal}` },
48
+ table.set(explicitDefaultValues, idVal as TODO),
49
+ )
50
+ }
package/src/mod.ts CHANGED
@@ -13,8 +13,6 @@ export {
13
13
 
14
14
  export { SqliteDbWrapper, emptyDebugInfo } from './SqliteDbWrapper.js'
15
15
 
16
- export { deriveColQuery } from './row-query-utils.js'
17
-
18
16
  export { queryDb, computed, makeRef, type LiveQuery, type LiveQueryDef } from './live-queries/mod.js'
19
17
 
20
18
  export * from '@livestore/common/schema'
package/src/reactive.ts CHANGED
@@ -203,7 +203,7 @@ export class ReactiveGraph<
203
203
 
204
204
  context: TContext | undefined
205
205
 
206
- debugRefreshInfos: BoundArray<RefreshDebugInfo<TDebugRefreshReason, TDebugThunkInfo>> = new BoundArray(5000)
206
+ debugRefreshInfos: BoundArray<RefreshDebugInfo<TDebugRefreshReason, TDebugThunkInfo>> = new BoundArray(200)
207
207
 
208
208
  private currentDebugRefresh:
209
209
  | { refreshedAtoms: AtomDebugInfo<TDebugThunkInfo>[]; startMs: DOMHighResTimeStamp }
@@ -7,8 +7,8 @@ import type {
7
7
  MigrationsReport,
8
8
  } from '@livestore/common'
9
9
  import { provideOtel, UnexpectedError } from '@livestore/common'
10
- import type { EventId, LiveStoreSchema, MutationEvent } from '@livestore/common/schema'
11
- import { LS_DEV } from '@livestore/utils'
10
+ import type { LiveStoreSchema } from '@livestore/common/schema'
11
+ import { isDevEnv, LS_DEV } from '@livestore/utils'
12
12
  import type { Cause, Schema } from '@livestore/utils/effect'
13
13
  import {
14
14
  Context,
@@ -19,7 +19,6 @@ import {
19
19
  Layer,
20
20
  Logger,
21
21
  LogLevel,
22
- MutableHashMap,
23
22
  OtelTracer,
24
23
  Queue,
25
24
  Runtime,
@@ -74,7 +73,12 @@ export type LiveStoreContextProps<TSchema extends LiveStoreSchema, TContext = {}
74
73
  store: Store<TSchema, TContext>,
75
74
  ) => Effect.Effect<void, unknown, OtelTracer.OtelTracer | LiveStoreContextRunning>
76
75
  adapter: Adapter
77
- disableDevtools?: boolean
76
+ /**
77
+ * Whether to disable devtools.
78
+ *
79
+ * @default 'auto'
80
+ */
81
+ disableDevtools?: boolean | 'auto'
78
82
  onBootStatus?: (status: BootStatus) => void
79
83
  batchUpdates: (run: () => void) => void
80
84
  }
@@ -92,7 +96,12 @@ export interface CreateStoreOptions<TSchema extends LiveStoreSchema, TContext =
92
96
  },
93
97
  ) => void | Promise<void> | Effect.Effect<void, unknown, OtelTracer.OtelTracer | LiveStoreContextRunning>
94
98
  batchUpdates?: (run: () => void) => void
95
- disableDevtools?: boolean
99
+ /**
100
+ * Whether to disable devtools.
101
+ *
102
+ * @default 'auto'
103
+ */
104
+ disableDevtools?: boolean | 'auto'
96
105
  onBootStatus?: (status: BootStatus) => void
97
106
  shutdownDeferred?: ShutdownDeferred
98
107
  /**
@@ -229,7 +238,7 @@ export const createStore = <TSchema extends LiveStoreSchema = LiveStoreSchema, T
229
238
  const clientSession: ClientSession = yield* adapter({
230
239
  schema,
231
240
  storeId,
232
- devtoolsEnabled: disableDevtools !== true,
241
+ devtoolsEnabled: getDevtoolsEnabled(disableDevtools),
233
242
  bootStatusQueue,
234
243
  shutdown,
235
244
  connectDevtoolsToStore: connectDevtoolsToStore_,
@@ -248,20 +257,17 @@ export const createStore = <TSchema extends LiveStoreSchema = LiveStoreSchema, T
248
257
  )
249
258
  }
250
259
 
251
- // TODO fill up with unsynced mutation events from the client session
252
- const unsyncedMutationEvents = MutableHashMap.empty<EventId.EventId, MutationEvent.ForSchema<TSchema>>()
253
-
254
260
  const store = new Store<TSchema, TContext>({
255
261
  clientSession,
256
262
  schema,
257
263
  context,
258
264
  otelOptions: { tracer: otelTracer, rootSpanContext: otelRootSpanContext },
259
- disableDevtools,
260
- unsyncedMutationEvents,
261
- lifetimeScope,
262
- runtime,
265
+ effectContext: { lifetimeScope, runtime },
266
+ // TODO find a better way to detect if we're running LiveStore in the LiveStore devtools
267
+ // But for now this is a good enough approximation with little downsides
268
+ __runningInDevtools: getDevtoolsEnabled(disableDevtools) === false,
263
269
  confirmUnsavedChanges,
264
- // NOTE during boot we're not yet executing mutations in a batched context
270
+ // NOTE during boot we're not yet executing events in a batched context
265
271
  // but only set the provided `batchUpdates` function after boot
266
272
  batchUpdates: (run) => run(),
267
273
  storeId,
@@ -270,7 +276,7 @@ export const createStore = <TSchema extends LiveStoreSchema = LiveStoreSchema, T
270
276
  },
271
277
  })
272
278
 
273
- // Starts background fibers (syncing, mutation processing, etc) for store
279
+ // Starts background fibers (syncing, event processing, etc) for store
274
280
  yield* store.boot
275
281
 
276
282
  if (boot !== undefined) {
@@ -314,3 +320,15 @@ const validateStoreId = (storeId: string) =>
314
320
  })
315
321
  }
316
322
  })
323
+
324
+ const getDevtoolsEnabled = (disableDevtools: boolean | 'auto' | undefined) => {
325
+ if (disableDevtools === true) {
326
+ return false
327
+ }
328
+
329
+ if (isDevEnv() === true) {
330
+ return true
331
+ }
332
+
333
+ return false
334
+ }
@@ -1,6 +1,6 @@
1
1
  import type { ClientSession, IntentionalShutdownCause, StoreInterrupted, UnexpectedError } from '@livestore/common'
2
- import type { EventId, LiveStoreSchema, MutationEvent } from '@livestore/common/schema'
3
- import type { Effect, MutableHashMap, Runtime, Scope } from '@livestore/utils/effect'
2
+ import type { LiveStoreEvent, LiveStoreSchema } from '@livestore/common/schema'
3
+ import type { Effect, Runtime, Scope } from '@livestore/utils/effect'
4
4
  import { Deferred } from '@livestore/utils/effect'
5
5
  import type * as otel from '@opentelemetry/api'
6
6
 
@@ -41,24 +41,24 @@ export type StoreOptions<TSchema extends LiveStoreSchema = LiveStoreSchema, TCon
41
41
  storeId: string
42
42
  context: TContext
43
43
  otelOptions: OtelOptions
44
- disableDevtools?: boolean
45
- lifetimeScope: Scope.Scope
46
- runtime: Runtime.Runtime<Scope.Scope>
44
+ effectContext: {
45
+ runtime: Runtime.Runtime<Scope.Scope>
46
+ lifetimeScope: Scope.Scope
47
+ }
47
48
  confirmUnsavedChanges: boolean
48
49
  batchUpdates: (runUpdates: () => void) => void
49
- // TODO validate whether we still need this
50
- unsyncedMutationEvents: MutableHashMap.MutableHashMap<EventId.EventId, MutationEvent.ForSchema<TSchema>>
51
50
  params: {
52
51
  leaderPushBatchSize: number
53
52
  }
53
+ __runningInDevtools: boolean
54
54
  }
55
55
 
56
56
  export type RefreshReason =
57
57
  | DebugRefreshReasonBase
58
58
  | {
59
59
  _tag: 'commit'
60
- /** The mutations that were applied */
61
- mutations: ReadonlyArray<MutationEvent.AnyDecoded | MutationEvent.PartialAnyDecoded>
60
+ /** The events that were applied */
61
+ events: ReadonlyArray<LiveStoreEvent.AnyDecoded | LiveStoreEvent.PartialAnyDecoded>
62
62
 
63
63
  /** The tables that were written to by the event */
64
64
  writeTables: ReadonlyArray<string>
@@ -83,11 +83,11 @@ export type QueryDebugInfo = {
83
83
 
84
84
  export type StoreOtel = {
85
85
  tracer: otel.Tracer
86
- mutationsSpanContext: otel.Context
86
+ commitsSpanContext: otel.Context
87
87
  queriesSpanContext: otel.Context
88
88
  }
89
89
 
90
- export type StoreMutateOptions = {
90
+ export type StoreCommitOptions = {
91
91
  label?: string
92
92
  skipRefresh?: boolean
93
93
  spanLinks?: otel.Link[]