@livestore/livestore 0.0.39-dev.3 → 0.0.40

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 (215) hide show
  1. package/README.md +15 -24
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/react/fixture.d.ts +210 -35
  4. package/dist/__tests__/react/fixture.d.ts.map +1 -1
  5. package/dist/__tests__/react/fixture.js +10 -29
  6. package/dist/__tests__/react/fixture.js.map +1 -1
  7. package/dist/cud.d.ts +28 -0
  8. package/dist/cud.d.ts.map +1 -0
  9. package/dist/cud.js +50 -0
  10. package/dist/cud.js.map +1 -0
  11. package/dist/cud.test.d.ts +2 -0
  12. package/dist/cud.test.d.ts.map +1 -0
  13. package/dist/cud.test.js +47 -0
  14. package/dist/cud.test.js.map +1 -0
  15. package/dist/inMemoryDatabase.d.ts +1 -1
  16. package/dist/inMemoryDatabase.d.ts.map +1 -1
  17. package/dist/inMemoryDatabase.js +1 -4
  18. package/dist/inMemoryDatabase.js.map +1 -1
  19. package/dist/index.d.ts +4 -4
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +3 -3
  22. package/dist/index.js.map +1 -1
  23. package/dist/migrations.d.ts.map +1 -1
  24. package/dist/migrations.js +11 -7
  25. package/dist/migrations.js.map +1 -1
  26. package/dist/query-info.d.ts +49 -0
  27. package/dist/query-info.d.ts.map +1 -0
  28. package/dist/{update-path.js → query-info.js} +4 -3
  29. package/dist/query-info.js.map +1 -0
  30. package/dist/react/LiveStoreContext.d.ts +0 -6
  31. package/dist/react/LiveStoreContext.d.ts.map +1 -1
  32. package/dist/react/LiveStoreContext.js.map +1 -1
  33. package/dist/react/useAtom.d.ts +2 -2
  34. package/dist/react/useAtom.d.ts.map +1 -1
  35. package/dist/react/useAtom.js +3 -3
  36. package/dist/react/useAtom.js.map +1 -1
  37. package/dist/react/useQuery.d.ts.map +1 -1
  38. package/dist/react/useQuery.test.d.ts.map +1 -0
  39. package/dist/{__tests__/react → react}/useQuery.test.js +8 -11
  40. package/dist/react/useQuery.test.js.map +1 -0
  41. package/dist/react/useRow.d.ts +2 -2
  42. package/dist/react/useRow.d.ts.map +1 -1
  43. package/dist/react/useRow.js +5 -5
  44. package/dist/react/useRow.js.map +1 -1
  45. package/dist/react/useRow.test.d.ts.map +1 -0
  46. package/dist/{__tests__/react → react}/useRow.test.js +14 -38
  47. package/dist/react/useRow.test.js.map +1 -0
  48. package/dist/react/useTemporaryQuery.d.ts +2 -2
  49. package/dist/react/useTemporaryQuery.d.ts.map +1 -1
  50. package/dist/reactive.d.ts +2 -2
  51. package/dist/reactive.d.ts.map +1 -1
  52. package/dist/reactive.js +50 -15
  53. package/dist/reactive.js.map +1 -1
  54. package/dist/reactive.test.d.ts.map +1 -0
  55. package/dist/{__tests__/reactive.test.js → reactive.test.js} +1 -1
  56. package/dist/reactive.test.js.map +1 -0
  57. package/dist/reactiveQueries/base-class.d.ts +16 -8
  58. package/dist/reactiveQueries/base-class.d.ts.map +1 -1
  59. package/dist/reactiveQueries/base-class.js +2 -1
  60. package/dist/reactiveQueries/base-class.js.map +1 -1
  61. package/dist/reactiveQueries/graphql.d.ts +15 -9
  62. package/dist/reactiveQueries/graphql.d.ts.map +1 -1
  63. package/dist/reactiveQueries/graphql.js +29 -8
  64. package/dist/reactiveQueries/graphql.js.map +1 -1
  65. package/dist/reactiveQueries/js.d.ts +8 -8
  66. package/dist/reactiveQueries/js.d.ts.map +1 -1
  67. package/dist/reactiveQueries/js.js +5 -4
  68. package/dist/reactiveQueries/js.js.map +1 -1
  69. package/dist/reactiveQueries/sql.d.ts +11 -11
  70. package/dist/reactiveQueries/sql.d.ts.map +1 -1
  71. package/dist/reactiveQueries/sql.js +7 -6
  72. package/dist/reactiveQueries/sql.js.map +1 -1
  73. package/dist/reactiveQueries/sql.test.d.ts.map +1 -0
  74. package/dist/{__tests__/reactiveQueries → reactiveQueries}/sql.test.js +44 -34
  75. package/dist/reactiveQueries/sql.test.js.map +1 -0
  76. package/dist/row-query.d.ts +5 -5
  77. package/dist/row-query.d.ts.map +1 -1
  78. package/dist/row-query.js +11 -9
  79. package/dist/row-query.js.map +1 -1
  80. package/dist/schema/index.d.ts +20 -7
  81. package/dist/schema/index.d.ts.map +1 -1
  82. package/dist/schema/index.js +18 -3
  83. package/dist/schema/index.js.map +1 -1
  84. package/dist/schema/mutations.d.ts +81 -0
  85. package/dist/schema/mutations.d.ts.map +1 -0
  86. package/dist/schema/mutations.js +29 -0
  87. package/dist/schema/mutations.js.map +1 -0
  88. package/dist/schema/parse-utils.d.ts +3 -6
  89. package/dist/schema/parse-utils.d.ts.map +1 -1
  90. package/dist/schema/parse-utils.js +2 -27
  91. package/dist/schema/parse-utils.js.map +1 -1
  92. package/dist/schema/system-tables.d.ts +8 -8
  93. package/dist/schema/table-def.d.ts +6 -6
  94. package/dist/schema/table-def.d.ts.map +1 -1
  95. package/dist/schema/table-def.js +2 -2
  96. package/dist/schema/table-def.js.map +1 -1
  97. package/dist/storage/in-memory/index.d.ts +4 -0
  98. package/dist/storage/in-memory/index.d.ts.map +1 -1
  99. package/dist/storage/in-memory/index.js +3 -0
  100. package/dist/storage/in-memory/index.js.map +1 -1
  101. package/dist/storage/index.d.ts +4 -0
  102. package/dist/storage/index.d.ts.map +1 -1
  103. package/dist/storage/tauri/index.d.ts +4 -0
  104. package/dist/storage/tauri/index.d.ts.map +1 -1
  105. package/dist/storage/tauri/index.js +6 -0
  106. package/dist/storage/tauri/index.js.map +1 -1
  107. package/dist/storage/utils/idb.d.ts +1 -0
  108. package/dist/storage/utils/idb.d.ts.map +1 -1
  109. package/dist/storage/utils/idb.js +11 -0
  110. package/dist/storage/utils/idb.js.map +1 -1
  111. package/dist/storage/web-worker/common.d.ts +11 -0
  112. package/dist/storage/web-worker/common.d.ts.map +1 -0
  113. package/dist/storage/web-worker/common.js +2 -0
  114. package/dist/storage/web-worker/common.js.map +1 -0
  115. package/dist/storage/web-worker/index.d.ts +14 -7
  116. package/dist/storage/web-worker/index.d.ts.map +1 -1
  117. package/dist/storage/web-worker/index.js +70 -14
  118. package/dist/storage/web-worker/index.js.map +1 -1
  119. package/dist/storage/web-worker/make-worker.d.ts +20 -0
  120. package/dist/storage/web-worker/make-worker.d.ts.map +1 -0
  121. package/dist/storage/web-worker/make-worker.js +155 -0
  122. package/dist/storage/web-worker/make-worker.js.map +1 -0
  123. package/dist/storage/web-worker/vite-dev-polyfill.d.ts +2 -0
  124. package/dist/storage/web-worker/vite-dev-polyfill.d.ts.map +1 -0
  125. package/dist/storage/web-worker/vite-dev-polyfill.js +35 -0
  126. package/dist/storage/web-worker/vite-dev-polyfill.js.map +1 -0
  127. package/dist/store.d.ts +32 -42
  128. package/dist/store.d.ts.map +1 -1
  129. package/dist/store.js +82 -131
  130. package/dist/store.js.map +1 -1
  131. package/dist/utils/dev.d.ts +3 -0
  132. package/dist/utils/dev.d.ts.map +1 -0
  133. package/dist/utils/dev.js +16 -0
  134. package/dist/utils/dev.js.map +1 -0
  135. package/dist/utils/util.d.ts +2 -0
  136. package/dist/utils/util.d.ts.map +1 -1
  137. package/dist/utils/util.js +2 -0
  138. package/dist/utils/util.js.map +1 -1
  139. package/package.json +26 -14
  140. package/src/__tests__/react/fixture.tsx +12 -30
  141. package/src/cud.test.ts +52 -0
  142. package/src/cud.ts +92 -0
  143. package/src/inMemoryDatabase.ts +2 -7
  144. package/src/index.ts +14 -8
  145. package/src/migrations.ts +10 -7
  146. package/src/{update-path.ts → query-info.ts} +18 -21
  147. package/src/react/LiveStoreContext.ts +0 -9
  148. package/src/react/useAtom.ts +6 -6
  149. package/src/{__tests__/react → react}/useQuery.test.tsx +11 -11
  150. package/src/react/useQuery.ts +1 -1
  151. package/src/{__tests__/react → react}/useRow.test.tsx +21 -39
  152. package/src/react/useRow.ts +10 -10
  153. package/src/{__tests__/reactive.test.ts → reactive.test.ts} +1 -1
  154. package/src/reactive.ts +60 -19
  155. package/src/reactiveQueries/base-class.ts +23 -9
  156. package/src/reactiveQueries/graphql.ts +49 -13
  157. package/src/reactiveQueries/js.ts +15 -13
  158. package/src/{__tests__/reactiveQueries → reactiveQueries}/sql.test.ts +44 -34
  159. package/src/reactiveQueries/sql.ts +24 -22
  160. package/src/row-query.ts +24 -22
  161. package/src/schema/index.ts +47 -11
  162. package/src/schema/mutations.ts +129 -0
  163. package/src/schema/parse-utils.ts +3 -45
  164. package/src/schema/table-def.ts +9 -2
  165. package/src/storage/in-memory/index.ts +7 -0
  166. package/src/storage/index.ts +8 -0
  167. package/src/storage/tauri/index.ts +10 -0
  168. package/src/storage/utils/idb.ts +14 -0
  169. package/src/storage/web-worker/common.ts +6 -0
  170. package/src/storage/web-worker/index.ts +86 -17
  171. package/src/storage/web-worker/make-worker.ts +214 -0
  172. package/src/storage/web-worker/vite-dev-polyfill.ts +33 -0
  173. package/src/store.ts +142 -212
  174. package/src/utils/dev.ts +23 -0
  175. package/src/utils/util.ts +4 -0
  176. package/dist/__tests__/mutations.test.d.ts +0 -2
  177. package/dist/__tests__/mutations.test.d.ts.map +0 -1
  178. package/dist/__tests__/mutations.test.js +0 -40
  179. package/dist/__tests__/mutations.test.js.map +0 -1
  180. package/dist/__tests__/react/useQuery.test.d.ts.map +0 -1
  181. package/dist/__tests__/react/useQuery.test.js.map +0 -1
  182. package/dist/__tests__/react/useRow.test.d.ts.map +0 -1
  183. package/dist/__tests__/react/useRow.test.js.map +0 -1
  184. package/dist/__tests__/reactive.test.d.ts.map +0 -1
  185. package/dist/__tests__/reactive.test.js.map +0 -1
  186. package/dist/__tests__/reactiveQueries/sql.test.d.ts.map +0 -1
  187. package/dist/__tests__/reactiveQueries/sql.test.js.map +0 -1
  188. package/dist/events.d.ts +0 -7
  189. package/dist/events.d.ts.map +0 -1
  190. package/dist/events.js +0 -2
  191. package/dist/events.js.map +0 -1
  192. package/dist/mutations.d.ts +0 -33
  193. package/dist/mutations.d.ts.map +0 -1
  194. package/dist/mutations.js +0 -38
  195. package/dist/mutations.js.map +0 -1
  196. package/dist/schema/action.d.ts +0 -30
  197. package/dist/schema/action.d.ts.map +0 -1
  198. package/dist/schema/action.js +0 -3
  199. package/dist/schema/action.js.map +0 -1
  200. package/dist/storage/web-worker/worker.d.ts +0 -13
  201. package/dist/storage/web-worker/worker.d.ts.map +0 -1
  202. package/dist/storage/web-worker/worker.js +0 -110
  203. package/dist/storage/web-worker/worker.js.map +0 -1
  204. package/dist/update-path.d.ts +0 -52
  205. package/dist/update-path.d.ts.map +0 -1
  206. package/dist/update-path.js.map +0 -1
  207. package/src/__tests__/mutations.test.ts +0 -43
  208. package/src/events.ts +0 -8
  209. package/src/mutations.ts +0 -79
  210. package/src/schema/action.ts +0 -41
  211. package/src/storage/web-worker/worker.ts +0 -141
  212. /package/dist/{__tests__/react → react}/useQuery.test.d.ts +0 -0
  213. /package/dist/{__tests__/react → react}/useRow.test.d.ts +0 -0
  214. /package/dist/{__tests__/reactive.test.d.ts → reactive.test.d.ts} +0 -0
  215. /package/dist/{__tests__/reactiveQueries → reactiveQueries}/sql.test.d.ts +0 -0
@@ -3,9 +3,9 @@ import { Schema, TreeFormatter } from '@livestore/utils/effect'
3
3
  import * as otel from '@opentelemetry/api'
4
4
 
5
5
  import { globalDbGraph } from '../global-state.js'
6
+ import type { QueryInfo, QueryInfoNone } from '../query-info.js'
6
7
  import type { Thunk } from '../reactive.js'
7
8
  import type { RefreshReason } from '../store.js'
8
- import type { UpdatePathDesc, UpdatePathDescNone } from '../update-path.js'
9
9
  import { getDurationMsFromSpan } from '../utils/otel.js'
10
10
  import type { Bindable } from '../utils/util.js'
11
11
  import { prepareBindValues } from '../utils/util.js'
@@ -14,12 +14,12 @@ import { LiveStoreQueryBase, makeGetAtomResult } from './base-class.js'
14
14
 
15
15
  export type MapRows<TResult, TRaw = any> =
16
16
  | ((rows: ReadonlyArray<TRaw>) => TResult)
17
- | Schema.Schema<ReadonlyArray<TRaw>, TResult>
17
+ | Schema.Schema<never, ReadonlyArray<TRaw>, TResult>
18
18
 
19
- export const querySQL = <Result, TRaw = any>(
19
+ export const querySQL = <TResult, TRaw = any>(
20
20
  query: string | ((get: GetAtomResult) => string),
21
21
  options?: {
22
- map?: MapRows<Result, TRaw>
22
+ map?: MapRows<TResult, TRaw>
23
23
  /**
24
24
  * Can be provided explicitly to slightly speed up initial query performance
25
25
  *
@@ -30,29 +30,29 @@ export const querySQL = <Result, TRaw = any>(
30
30
  label?: string
31
31
  dbGraph?: DbGraph
32
32
  },
33
- ): LiveQuery<Result, UpdatePathDescNone> =>
34
- new LiveStoreSQLQuery<Result, UpdatePathDescNone>({
33
+ ): LiveQuery<TResult, QueryInfoNone> =>
34
+ new LiveStoreSQLQuery<TResult, QueryInfoNone>({
35
35
  label: options?.label,
36
36
  genQueryString: query,
37
37
  queriedTables: options?.queriedTables,
38
38
  bindValues: options?.bindValues,
39
39
  dbGraph: options?.dbGraph,
40
40
  map: options?.map,
41
- updatePathDesc: { _tag: 'None' },
41
+ queryInfo: { _tag: 'None' },
42
42
  })
43
43
 
44
44
  /* An object encapsulating a reactive SQL query */
45
- export class LiveStoreSQLQuery<
46
- Result,
47
- TUpdatePath extends UpdatePathDesc = UpdatePathDescNone,
48
- > extends LiveStoreQueryBase<Result, TUpdatePath> {
45
+ export class LiveStoreSQLQuery<TResult, TQueryInfo extends QueryInfo = QueryInfoNone> extends LiveStoreQueryBase<
46
+ TResult,
47
+ TQueryInfo
48
+ > {
49
49
  _tag: 'sql' = 'sql'
50
50
 
51
51
  /** A reactive thunk representing the query text */
52
52
  queryString$: Thunk<string, DbContext, RefreshReason>
53
53
 
54
54
  /** A reactive thunk representing the query results */
55
- results$: Thunk<Result, DbContext, RefreshReason>
55
+ results$: Thunk<TResult, DbContext, RefreshReason>
56
56
 
57
57
  label: string
58
58
 
@@ -63,7 +63,7 @@ export class LiveStoreSQLQuery<
63
63
 
64
64
  private mapRows
65
65
 
66
- updatePathDesc: TUpdatePath
66
+ queryInfo: TQueryInfo
67
67
 
68
68
  constructor({
69
69
  genQueryString,
@@ -73,16 +73,16 @@ export class LiveStoreSQLQuery<
73
73
  dbGraph,
74
74
  map,
75
75
  execBeforeFirstRun,
76
- updatePathDesc,
76
+ queryInfo,
77
77
  }: {
78
78
  label?: string
79
79
  genQueryString: string | ((get: GetAtomResult) => string)
80
80
  queriedTables?: Set<string>
81
81
  bindValues?: Bindable
82
82
  dbGraph?: DbGraph
83
- map?: MapRows<Result>
83
+ map?: MapRows<TResult>
84
84
  execBeforeFirstRun?: (ctx: DbContext) => void
85
- updatePathDesc?: TUpdatePath
85
+ queryInfo?: TQueryInfo
86
86
  }) {
87
87
  super()
88
88
 
@@ -90,18 +90,18 @@ export class LiveStoreSQLQuery<
90
90
  this.label = `sql(${label})`
91
91
  this.dbGraph = dbGraph ?? globalDbGraph
92
92
  this.execBeforeFirstRun = execBeforeFirstRun
93
- this.updatePathDesc = updatePathDesc ?? ({ _tag: 'None' } as TUpdatePath)
93
+ this.queryInfo = queryInfo ?? ({ _tag: 'None' } as TQueryInfo)
94
94
  this.mapRows =
95
95
  map === undefined
96
- ? (rows: any) => rows as Result
96
+ ? (rows: any) => rows as TResult
97
97
  : Schema.isSchema(map)
98
98
  ? (rows: any) => {
99
- const parseResult = Schema.parseEither(map)(rows)
99
+ const parseResult = Schema.decodeEither(map as Schema.Schema<never, ReadonlyArray<any>, TResult>)(rows)
100
100
  if (parseResult._tag === 'Left') {
101
101
  console.error(`Error parsing SQL query result: ${TreeFormatter.formatError(parseResult.left)}`)
102
102
  return shouldNeverHappen(`Error parsing SQL query result: ${parseResult.left}`)
103
103
  } else {
104
- return parseResult.right as Result
104
+ return parseResult.right as TResult
105
105
  }
106
106
  }
107
107
  : typeof map === 'function'
@@ -130,7 +130,7 @@ export class LiveStoreSQLQuery<
130
130
 
131
131
  const queriedTablesRef = { current: queriedTables }
132
132
 
133
- const results$ = this.dbGraph.makeThunk<Result>(
133
+ const results$ = this.dbGraph.makeThunk<TResult>(
134
134
  (get, setDebugInfo, { store, otelTracer, rootOtelContext }, otelContext) =>
135
135
  otelTracer.startActiveSpan(
136
136
  'sql:...', // NOTE span name will be overridden further down
@@ -173,6 +173,8 @@ export class LiveStoreSQLQuery<
173
173
 
174
174
  const durationMs = getDurationMsFromSpan(span)
175
175
 
176
+ this.executionTimes.push(durationMs)
177
+
176
178
  setDebugInfo({ _tag: 'sql', label, query: sqlString, durationMs })
177
179
 
178
180
  return result
@@ -197,7 +199,7 @@ export class LiveStoreSQLQuery<
197
199
  // label: `${this.label}:js`,
198
200
  // onDestroy: () => this.destroy(),
199
201
  // dbGraph: this.dbGraph,
200
- // updatePathDesc: undefined,
202
+ // queryInfo: undefined,
201
203
  // })
202
204
 
203
205
  /** Returns a reactive query */
package/src/row-query.ts CHANGED
@@ -6,8 +6,9 @@ import { SqliteAst, SqliteDsl } from 'effect-db-schema'
6
6
  import { computed } from './index.js'
7
7
  import type { InMemoryDatabase } from './inMemoryDatabase.js'
8
8
  import { migrateTable } from './migrations.js'
9
+ import type { QueryInfoCol, QueryInfoNone, QueryInfoRow } from './query-info.js'
9
10
  import type { Ref } from './reactive.js'
10
- import type { DbContext, DbGraph, GetResult, LiveQuery, LiveQueryAny } from './reactiveQueries/base-class.js'
11
+ import type { DbContext, DbGraph, LiveQuery, LiveQueryAny } from './reactiveQueries/base-class.js'
11
12
  // import type { LiveStoreJSQuery } from './reactiveQueries/js.js'
12
13
  import { LiveStoreSQLQuery } from './reactiveQueries/sql.js'
13
14
  import { SCHEMA_META_TABLE } from './schema/index.js'
@@ -18,7 +19,6 @@ import {
18
19
  type TableOptions,
19
20
  } from './schema/table-def.js'
20
21
  import type { RefreshReason } from './store.js'
21
- import type { UpdatePathDesc, UpdatePathDescCol, UpdatePathDescNone, UpdatePathDescRow } from './update-path.js'
22
22
  import type { GetValForKey } from './utils/util.js'
23
23
  import { prepareBindValues, sql } from './utils/util.js'
24
24
 
@@ -36,13 +36,13 @@ export type MakeRowQuery = {
36
36
  <TTableDef extends TableDef<DefaultSqliteTableDef, boolean, TableOptions & { isSingleton: true }>>(
37
37
  table: TTableDef,
38
38
  options?: RowQueryOptions,
39
- ): LiveQuery<RowResult<TTableDef>, UpdatePathDescRow<TTableDef>>
39
+ ): LiveQuery<RowResult<TTableDef>, QueryInfoRow<TTableDef>>
40
40
  <TTableDef extends TableDef<DefaultSqliteTableDef, boolean, TableOptions & { isSingleton: false }>>(
41
41
  table: TTableDef,
42
42
  // TODO adjust so it works with arbitrary primary keys or unique constraints
43
43
  id: string,
44
44
  options?: RowQueryOptions & RowQueryOptionsDefaulValues<TTableDef>,
45
- ): LiveQuery<RowResult<TTableDef>, UpdatePathDescRow<TTableDef>>
45
+ ): LiveQuery<RowResult<TTableDef>, QueryInfoRow<TTableDef>>
46
46
  }
47
47
 
48
48
  // TODO also allow other where clauses and multiple rows
@@ -85,7 +85,7 @@ export const rowQuery: MakeRowQuery = <TTableDef extends TableDef>(
85
85
  if (results.length === 0) return shouldNeverHappen(`No results for query ${queryStr}`)
86
86
 
87
87
  const componentStateEffectSchema = SqliteDsl.structSchemaForTable(stateSchema)
88
- const parseResult = Schema.parseEither(componentStateEffectSchema)(results[0]!)
88
+ const parseResult = Schema.decodeEither(componentStateEffectSchema)(results[0]!)
89
89
 
90
90
  if (parseResult._tag === 'Left') {
91
91
  console.error('decode error', TreeFormatter.formatError(parseResult.left), 'results', results)
@@ -94,7 +94,7 @@ export const rowQuery: MakeRowQuery = <TTableDef extends TableDef>(
94
94
 
95
95
  return table.isSingleColumn === true ? parseResult.right.value : parseResult.right
96
96
  },
97
- updatePathDesc: { _tag: 'Row', table, id: id ?? 'singleton' },
97
+ queryInfo: { _tag: 'Row', table, id: id ?? 'singleton' },
98
98
  })
99
99
  }
100
100
 
@@ -107,20 +107,20 @@ export type RowResultEncoded<TTableDef extends TableDef> = TTableDef['isSingleCo
107
107
  : SqliteDsl.FromColumns.RowEncoded<TTableDef['sqliteDef']['columns']>
108
108
 
109
109
  export const deriveColQuery: {
110
- <TQuery extends LiveQuery<any, UpdatePathDescNone>, TCol extends keyof TQuery['result!'] & string>(
110
+ <TQuery extends LiveQuery<any, QueryInfoNone>, TCol extends keyof TQuery['__result!'] & string>(
111
111
  query$: TQuery,
112
112
  colName: TCol,
113
- ): LiveQuery<TQuery['result!'][TCol], UpdatePathDescNone>
114
- <TQuery extends LiveQuery<any, UpdatePathDescRow<any>>, TCol extends keyof TQuery['result!'] & string>(
113
+ ): LiveQuery<TQuery['__result!'][TCol], QueryInfoNone>
114
+ <TQuery extends LiveQuery<any, QueryInfoRow<any>>, TCol extends keyof TQuery['__result!'] & string>(
115
115
  query$: TQuery,
116
116
  colName: TCol,
117
- ): LiveQuery<TQuery['result!'][TCol], UpdatePathDescCol<TQuery['updatePathDesc']['table'], TCol>>
117
+ ): LiveQuery<TQuery['__result!'][TCol], QueryInfoCol<TQuery['queryInfo']['table'], TCol>>
118
118
  } = (query$: LiveQueryAny, colName: string) => {
119
119
  return computed((get) => get(query$)[colName], {
120
120
  label: `deriveColQuery:${query$.label}:${colName}`,
121
- updatePathDesc:
122
- query$.updatePathDesc._tag === 'Row'
123
- ? { _tag: 'Col', table: query$.updatePathDesc.table, column: colName, id: query$.updatePathDesc.id }
121
+ queryInfo:
122
+ query$.queryInfo._tag === 'Row'
123
+ ? { _tag: 'Col', table: query$.queryInfo.table, column: colName, id: query$.queryInfo.id }
124
124
  : undefined,
125
125
  }) as any
126
126
  }
@@ -138,20 +138,22 @@ const insertRowWithDefaultValuesOrIgnore = ({
138
138
  otelContext: otel.Context
139
139
  defaultValues: Partial<RowResult<TableDef>> | undefined
140
140
  }) => {
141
- const columnNames = Object.keys(table.sqliteDef.columns)
142
- const columnValues = columnNames.map((name) => `$${name}`).join(', ')
143
-
144
- const tableName = table.sqliteDef.name
145
- const insertQuery = sql`insert into ${tableName} (${columnNames.join(
146
- ', ',
147
- )}) select ${columnValues} where not exists(select 1 from ${tableName} where id = '${id}')`
148
-
149
141
  const defaultValues = pipe(
150
142
  getDefaultValuesEncoded(table),
151
143
  ReadonlyRecord.map((val, columnName) => explicitDefaultValues?.[columnName] ?? val),
152
144
  )
153
145
 
154
- db.execute(insertQuery, prepareBindValues({ ...defaultValues, id }, insertQuery), [tableName], { otelContext })
146
+ const defaultColumnNames = [...Object.keys(defaultValues), 'id']
147
+ const columnValues = defaultColumnNames.map((name) => `$${name}`).join(', ')
148
+
149
+ const tableName = table.sqliteDef.name
150
+ const insertQuery = sql`insert into ${tableName} (${defaultColumnNames.join(
151
+ ', ',
152
+ )}) select ${columnValues} where not exists(select 1 from ${tableName} where id = '${id}')`
153
+
154
+ db.execute(insertQuery, prepareBindValues({ ...defaultValues, id }, insertQuery), new Set([tableName]), {
155
+ otelContext,
156
+ })
155
157
  }
156
158
 
157
159
  const makeExecBeforeFirstRun =
@@ -1,33 +1,47 @@
1
+ import type { ReadonlyArray } from '@livestore/utils/effect'
1
2
  import type { SqliteDsl } from 'effect-db-schema'
2
3
 
3
- import type { ActionDefinitions } from './action.js'
4
+ import { isReadonlyArray } from '../utils/util.js'
5
+ import {
6
+ type MutationDef,
7
+ type MutationDefMap,
8
+ type MutationDefRecord,
9
+ type RawSqlMutation,
10
+ rawSqlMutation,
11
+ } from './mutations.js'
4
12
  import { systemTables } from './system-tables.js'
5
13
  import type { TableDef } from './table-def.js'
6
14
 
7
- export * from './action.js'
8
15
  export * from './system-tables.js'
9
16
  export * as DbSchema from './table-def.js'
10
17
  export * as ParseUtils from './parse-utils.js'
18
+ export * from './mutations.js'
11
19
 
12
- // export { SqliteDsl as DbSchema } from 'effect-db-schema'
13
-
14
- export type LiveStoreSchema<TDbSchema extends SqliteDsl.DbSchema = SqliteDsl.DbSchema> = {
20
+ export type LiveStoreSchema<
21
+ TDbSchema extends SqliteDsl.DbSchema = SqliteDsl.DbSchema,
22
+ TMutationsDefRecord extends MutationDefRecord = MutationDefRecord,
23
+ > = {
15
24
  /** Only used on type-level */
16
25
  readonly _DbSchemaType: TDbSchema
26
+ /** Only used on type-level */
27
+ readonly _MutationDefMapType: TMutationsDefRecord
17
28
 
18
29
  readonly tables: Map<string, TableDef>
19
- readonly actions: ActionDefinitions<any>
30
+ readonly mutations: MutationDefMap
20
31
  }
21
32
 
22
33
  export type InputSchema = {
23
- tables: Record<string, TableDef> | ReadonlyArray<TableDef>
24
- actions: ActionDefinitions<any>
34
+ readonly tables: Record<string, TableDef> | ReadonlyArray<TableDef>
35
+ readonly mutations?: ReadonlyArray<MutationDef.Any> | Record<string, MutationDef.Any>
25
36
  }
26
37
 
27
38
  export const makeSchema = <TInputSchema extends InputSchema>(
28
- /** Note when using the object-notation for tables, the object keys are ignored and not used as table names */
39
+ /** Note when using the object-notation for tables/mutations, the object keys are ignored and not used as table/mutation names */
29
40
  schema: TInputSchema,
30
- ): LiveStoreSchema<DbSchemaFromInputSchemaTables<TInputSchema['tables']>> => {
41
+ ): LiveStoreSchema<
42
+ DbSchemaFromInputSchemaTables<TInputSchema['tables']>,
43
+ MutationDefRecordFromInputSchemaMutations<TInputSchema['mutations']>
44
+ > => {
31
45
  const inputTables: ReadonlyArray<TableDef> = Array.isArray(schema.tables)
32
46
  ? schema.tables
33
47
  : // TODO validate that table names are unique in this case
@@ -44,10 +58,25 @@ export const makeSchema = <TInputSchema extends InputSchema>(
44
58
  tables.set(tableDef.sqliteDef.name, tableDef)
45
59
  }
46
60
 
61
+ const mutations: MutationDefMap = new Map()
62
+
63
+ if (isReadonlyArray(schema.mutations)) {
64
+ for (const mutation of schema.mutations) {
65
+ mutations.set(mutation.name, mutation)
66
+ }
67
+ } else {
68
+ for (const [name, mutation] of Object.entries(schema.mutations ?? {})) {
69
+ mutations.set(name, mutation)
70
+ }
71
+ }
72
+
73
+ mutations.set('livestore.RawSql', rawSqlMutation)
74
+
47
75
  return {
48
76
  _DbSchemaType: Symbol('livestore.DbSchemaType') as any,
77
+ _MutationDefMapType: Symbol('livestore.MutationDefMapType') as any,
49
78
  tables,
50
- actions: schema.actions,
79
+ mutations,
51
80
  } satisfies LiveStoreSchema
52
81
  }
53
82
 
@@ -62,3 +91,10 @@ export type DbSchemaFromInputSchemaTables<TTables extends InputSchema['tables']>
62
91
  : TTables extends Record<string, TableDef>
63
92
  ? { [K in keyof TTables as TTables[K]['sqliteDef']['name']]: TTables[K]['sqliteDef'] }
64
93
  : never
94
+
95
+ export type MutationDefRecordFromInputSchemaMutations<TMutations extends InputSchema['mutations']> =
96
+ TMutations extends ReadonlyArray<MutationDef.Any>
97
+ ? { [K in TMutations[number] as K['name']]: K } & { 'livestore.RawSql': RawSqlMutation }
98
+ : TMutations extends { [name: string]: MutationDef.Any }
99
+ ? { [K in keyof TMutations as TMutations[K]['name']]: TMutations[K] } & { 'livestore.RawSql': RawSqlMutation }
100
+ : never
@@ -0,0 +1,129 @@
1
+ import type { BindValues } from '@livestore/sql-queries'
2
+ import { cuid } from '@livestore/utils/cuid'
3
+ import { Schema } from '@livestore/utils/effect'
4
+
5
+ import type { LiveStoreSchema } from './index.js'
6
+
7
+ export type MutationDefMap = Map<string | 'livestore.RawSql', MutationDef.Any>
8
+ export type MutationDefRecord = {
9
+ 'livestore.RawSql': RawSqlMutation
10
+ [name: string]: MutationDef.Any
11
+ }
12
+
13
+ export type InternalMutationSchema<TRecord extends MutationDefRecord = MutationDefRecord> = {
14
+ _DefRecord: TRecord
15
+
16
+ map: Map<keyof TRecord, TRecord[keyof TRecord]>
17
+ schemaHashMap: Map<keyof TRecord, number>
18
+ }
19
+
20
+ export type MutationDef<TName extends string, TFrom, TTo> = {
21
+ name: TName
22
+ schema: Schema.Schema<never, TFrom, TTo>
23
+ sql:
24
+ | string
25
+ | ((args: TTo) =>
26
+ | string
27
+ | {
28
+ sql: string
29
+ /** Note args need to be manually encoded to `BindValues` when returning this argument */
30
+ bindValues: BindValues
31
+ writeTables?: ReadonlySet<string>
32
+ })
33
+
34
+ /** Helper function to construct mutation event */
35
+ (args: TTo): { mutation: TName; args: TTo; id: string }
36
+ }
37
+
38
+ export namespace MutationDef {
39
+ export type Any = MutationDef<string, any, any>
40
+ }
41
+
42
+ // TODO possibly also allow for mutation event subsumption behaviour
43
+ export const defineMutation = <TName extends string, TFrom, TTo>(
44
+ name: TName,
45
+ schema: Schema.Schema<never, TFrom, TTo>,
46
+ sql: string | ((args: TTo) => string | { sql: string; bindValues: BindValues; writeTables?: ReadonlySet<string> }),
47
+ ): MutationDef<TName, TFrom, TTo> => {
48
+ const makeEvent = (args: TTo) => ({ mutation: name, args, id: cuid() })
49
+
50
+ Object.defineProperty(makeEvent, 'name', { value: name })
51
+ Object.defineProperty(makeEvent, 'schema', { value: schema })
52
+ Object.defineProperty(makeEvent, 'sql', { value: sql })
53
+
54
+ return makeEvent as MutationDef<TName, TFrom, TTo>
55
+ }
56
+
57
+ export const makeMutationDefRecord = <TInputRecord extends Record<string, MutationDef.Any>>(
58
+ inputRecord: TInputRecord,
59
+ ): {
60
+ [K in TInputRecord[keyof TInputRecord]['name']]: Extract<TInputRecord[keyof TInputRecord], { name: K }>
61
+ } => {
62
+ const result: any = {}
63
+
64
+ for (const [name, def] of Object.entries(inputRecord)) {
65
+ result[name] = def
66
+ }
67
+
68
+ result['livestore.RawSql'] = rawSqlMutation
69
+
70
+ return result
71
+ }
72
+
73
+ export const rawSqlMutation = defineMutation(
74
+ 'livestore.RawSql',
75
+ Schema.struct({
76
+ sql: Schema.string,
77
+ bindValues: Schema.optional(Schema.record(Schema.string, Schema.any)),
78
+ writeTables: Schema.optional(Schema.readonlySet(Schema.string)),
79
+ }),
80
+ ({ sql, bindValues, writeTables }) => ({ sql, bindValues: bindValues ?? {}, writeTables }),
81
+ )
82
+
83
+ export type RawSqlMutation = typeof rawSqlMutation
84
+ export type RawSqlMutationEvent = ReturnType<typeof rawSqlMutation>
85
+
86
+ export type MutationEvent<TMutationsDef extends MutationDef.Any> = {
87
+ mutation: TMutationsDef['name']
88
+ args: Schema.Schema.To<TMutationsDef['schema']>
89
+ id: string
90
+ }
91
+
92
+ export namespace MutationEvent {
93
+ export type Any = MutationEvent<MutationDef.Any>
94
+
95
+ export type ForSchema<TSchema extends LiveStoreSchema> = {
96
+ [K in keyof TSchema['_MutationDefMapType']]: MutationEvent<TSchema['_MutationDefMapType'][K]>
97
+ }[keyof TSchema['_MutationDefMapType']]
98
+ }
99
+
100
+ export type MutationEventSchema<TMutationsDefRecord extends MutationDefRecord> = Schema.Schema<
101
+ never,
102
+ {
103
+ [K in keyof TMutationsDefRecord]: {
104
+ mutation: K
105
+ args: Schema.Schema.From<TMutationsDefRecord[K]['schema']>
106
+ id: string
107
+ }
108
+ }[keyof TMutationsDefRecord],
109
+ {
110
+ [K in keyof TMutationsDefRecord]: {
111
+ mutation: K
112
+ args: Schema.Schema.To<TMutationsDefRecord[K]['schema']>
113
+ id: string
114
+ }
115
+ }[keyof TMutationsDefRecord]
116
+ >
117
+
118
+ export const makeMutationEventSchema = <TMutationsDefRecord extends MutationDefRecord>(
119
+ mutationDefRecord: TMutationsDefRecord,
120
+ ): MutationEventSchema<TMutationsDefRecord> =>
121
+ Schema.union(
122
+ ...Object.values(mutationDefRecord).map((def) =>
123
+ Schema.struct({
124
+ mutation: Schema.literal(def.name),
125
+ args: def.schema,
126
+ id: Schema.string,
127
+ }),
128
+ ),
129
+ ) as any
@@ -1,56 +1,14 @@
1
1
  import { shouldNeverHappen } from '@livestore/utils'
2
2
  import type { ReadonlyArray } from '@livestore/utils/effect'
3
3
  import { pipe, ReadonlyRecord, Schema, TreeFormatter } from '@livestore/utils/effect'
4
- import { SqliteDsl as __SqliteDsl } from 'effect-db-schema'
4
+ import { SqliteDsl } from 'effect-db-schema' // eslint-disable-line
5
5
 
6
6
  import { type FromColumns, type FromTable, getDefaultValuesDecoded, type TableDef } from './table-def.js'
7
7
 
8
- export const headUnsafe = <From, To>(schema: Schema.Schema<From, To>) =>
9
- Schema.transform(
10
- Schema.array(schema),
11
- Schema.to(schema),
12
- (rows) => rows[0]!,
13
- (row) => [row],
14
- )
15
-
16
- // export const head = <From, To>(schema: Schema.Schema<From, To>) =>
17
- // Schema.transform(
18
- // Schema.array(schema),
19
- // Schema.optionFromSelf(Schema.to(schema)),
20
- // (rows) => Option.fromNullable(rows[0]),
21
- // (row) => (row._tag === 'None' ? [] : [row.value]),
22
- // )
23
-
24
- export const headOr = <From, To>(schema: Schema.Schema<From, To>, fallback: To) =>
25
- Schema.transform(
26
- Schema.array(schema),
27
- Schema.to(schema),
28
- (rows) => rows[0] ?? fallback,
29
- (row) => [row],
30
- )
31
-
32
- // export const pluck = <From extends {}, To, K extends keyof From & keyof To & string>(
33
- // schema: Schema.Schema<From, To>,
34
- // prop: K,
35
- // ): Schema.Schema<From, To[K]> => {
36
- // const toSchema = Schema.make(SchemaAST.getPropertySignatures(schema.ast).find((s) => s.name === prop)!.type) as any
37
- // return Schema.transform(
38
- // schema,
39
- // toSchema,
40
- // (row) => (row as any)[prop],
41
- // (val) => ({ [prop]: val }) as any,
42
- // )
43
- // }
44
-
45
- // export const schemaFor = <TTableDef extends TableDef>(
46
- // table: TTableDef,
47
- // ): Schema.Schema<FromTable.RowEncoded<TTableDef>, FromTable.RowDecoded<TTableDef>> =>
48
- // SqliteDsl.structSchemaForTable(table.sqliteDef) as any
49
-
50
8
  export const many = <TTableDef extends TableDef>(
51
9
  table: TTableDef,
52
10
  ): ((rawRows: ReadonlyArray<any>) => ReadonlyArray<FromTable.RowDecoded<TTableDef>>) => {
53
- return Schema.parseSync(Schema.array(table.schema)) as TODO
11
+ return Schema.decodeSync(Schema.array(table.schema)) as TODO
54
12
  }
55
13
 
56
14
  export const first =
@@ -59,7 +17,7 @@ export const first =
59
17
  fallback?: FromColumns.InsertRowDecoded<TTableDef['sqliteDef']['columns']>,
60
18
  ) =>
61
19
  (rawRows: ReadonlyArray<any>) => {
62
- const rows = Schema.parseSync(Schema.array(table.schema))(rawRows)
20
+ const rows = Schema.decodeSync(Schema.array(table.schema))(rawRows)
63
21
 
64
22
  if (rows.length === 0) {
65
23
  const schemaDefaultValues = getDefaultValuesDecoded(table)
@@ -5,7 +5,7 @@ import { SqliteAst, SqliteDsl } from 'effect-db-schema'
5
5
 
6
6
  export const { blob, boolean, column, datetime, integer, isColumnDefinition, json, real, text } = SqliteDsl
7
7
 
8
- export { type SqliteDsl as __SqliteDsl } from 'effect-db-schema'
8
+ export { type SqliteDsl } from 'effect-db-schema'
9
9
 
10
10
  import { dynamicallyRegisteredTables } from '../global-state.js'
11
11
 
@@ -18,7 +18,7 @@ export type DefaultSqliteTableDefConstrained = SqliteDsl.TableDefinition<string,
18
18
  // TSqliteDef extends DefaultSqliteTableDef = DefaultSqliteTableDef,
19
19
  // TIsSingleColumn extends boolean = boolean,
20
20
  // TOptions extends TableOptions = TableOptions,
21
- // > = TableDefBase<TSqliteDef, TIsSingleColumn, TOptions> & { schema: Schema.Schema<any, any> }
21
+ // > = TableDefBase<TSqliteDef, TIsSingleColumn, TOptions> & { schema: Schema.Schema<never, any, any> }
22
22
 
23
23
  // /**
24
24
  // * NOTE in the past we used to have a single `TableDef` but there are some TS issues when indroducing
@@ -44,6 +44,7 @@ export type TableDef<
44
44
  // NOTE we're not using `SqliteDsl.StructSchemaForColumns<TSqliteDef['columns']>`
45
45
  // as we don't want the alias type for users to show up
46
46
  TSchema = Schema.Schema<
47
+ never,
47
48
  SqliteDsl.AnyIfConstained<
48
49
  TSqliteDef['columns'],
49
50
  { readonly [K in keyof TSqliteDef['columns']]: Schema.Schema.From<TSqliteDef['columns'][K]['schema']> }
@@ -159,6 +160,9 @@ export const getDefaultValuesEncoded = <TTableDef extends TableDef>(tableDef: TT
159
160
  pipe(
160
161
  tableDef.sqliteDef.columns,
161
162
  ReadonlyRecord.filter((_, key) => key !== 'id'),
163
+ ReadonlyRecord.filter(
164
+ (col) => col!.default._tag === 'None' || SqliteDsl.isSqlDefaultValue(col!.default.value) === false,
165
+ ),
162
166
  ReadonlyRecord.map((column, columnName) =>
163
167
  column!.default._tag === 'None'
164
168
  ? column!.nullable === true
@@ -172,6 +176,9 @@ export const getDefaultValuesDecoded = <TTableDef extends TableDef>(tableDef: TT
172
176
  pipe(
173
177
  tableDef.sqliteDef.columns,
174
178
  ReadonlyRecord.filter((_, key) => key !== 'id'),
179
+ ReadonlyRecord.filter(
180
+ (col) => col!.default._tag === 'None' || SqliteDsl.isSqlDefaultValue(col!.default.value) === false,
181
+ ),
175
182
  ReadonlyRecord.map((column, columnName) =>
176
183
  column!.default._tag === 'None'
177
184
  ? column!.nullable === true
@@ -1,5 +1,6 @@
1
1
  import type * as otel from '@opentelemetry/api'
2
2
 
3
+ import type { MutationEvent } from '../../index.js'
3
4
  import type { PreparedBindValues } from '../../utils/util.js'
4
5
  import type { Storage, StorageOtelProps } from '../index.js'
5
6
 
@@ -17,5 +18,11 @@ export class InMemoryStorage implements Storage {
17
18
 
18
19
  execute = (_query: string, _bindValues?: PreparedBindValues): void => {}
19
20
 
21
+ mutate = (_mutationEventEncoded: MutationEvent.Any, _parentSpan?: otel.Span | undefined) => {}
22
+
20
23
  getPersistedData = async (): Promise<Uint8Array> => new Uint8Array()
24
+
25
+ getMutationLogData = async (): Promise<Uint8Array> => new Uint8Array()
26
+
27
+ dangerouslyReset = async () => {}
21
28
  }
@@ -8,6 +8,7 @@
8
8
 
9
9
  import type * as otel from '@opentelemetry/api'
10
10
 
11
+ import type { MutationEvent } from '../index.js'
11
12
  import type { PreparedBindValues } from '../utils/util.js'
12
13
 
13
14
  export type StorageInit = (otelProps: StorageOtelProps) => Promise<Storage> | Storage
@@ -16,8 +17,15 @@ export interface Storage {
16
17
  // TODO consider transferables for `bindValues` (e.g. Uint8Array values)
17
18
  execute(query: string, bindValues?: PreparedBindValues, parentSpan?: otel.Span): void
18
19
 
20
+ // TODO consider transferables for `bindValues` (e.g. Uint8Array values)
21
+ mutate(mutationEventEncoded: MutationEvent.Any, parentSpan?: otel.Span): void
22
+
19
23
  /** Return a snapshot of persisted data from the storage */
20
24
  getPersistedData(parentSpan?: otel.Span): Promise<Uint8Array>
25
+
26
+ getMutationLogData(parentSpan?: otel.Span): Promise<Uint8Array>
27
+
28
+ dangerouslyReset(): Promise<void>
21
29
  }
22
30
 
23
31
  export type StorageType = 'tauri' | 'web' | 'web-in-memory'
@@ -2,6 +2,7 @@ import { getTraceParentHeader } from '@livestore/utils'
2
2
  import type * as otel from '@opentelemetry/api'
3
3
  import { invoke } from '@tauri-apps/api'
4
4
 
5
+ import type { MutationEvent } from '../../schema/mutations.js'
5
6
  import type { PreparedBindValues } from '../../utils/util.js'
6
7
  import { prepareBindValues } from '../../utils/util.js'
7
8
  import type { Storage, StorageOtelProps } from '../index.js'
@@ -38,6 +39,8 @@ export class TauriStorage implements Storage {
38
39
  })
39
40
  }
40
41
 
42
+ mutate = (_mutationEventEncoded: MutationEvent.Any, _parentSpan?: otel.Span | undefined) => {}
43
+
41
44
  getPersistedData = async (parentSpan?: otel.Span): Promise<Uint8Array> => {
42
45
  const headers = new Headers()
43
46
  headers.set('traceparent', getTraceParentHeader(parentSpan ?? this.parentSpan))
@@ -47,6 +50,13 @@ export class TauriStorage implements Storage {
47
50
  )
48
51
  }
49
52
 
53
+ // TODO
54
+ getMutationLogData = async (): Promise<Uint8Array> => new Uint8Array()
55
+
56
+ dangerouslyReset = async () => {
57
+ // TODO
58
+ }
59
+
50
60
  private getOtelData = (parentSpan?: otel.Span) => getOtelData_(parentSpan ?? this.parentSpan)!
51
61
  }
52
62