@livestore/livestore 0.0.39 → 0.0.41-dev.0

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 (190) hide show
  1. package/README.md +15 -24
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/react/fixture.d.ts +192 -17
  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/{mutations.d.ts → cud.d.ts} +14 -19
  8. package/dist/cud.d.ts.map +1 -0
  9. package/dist/{mutations.js → cud.js} +16 -10
  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 +2 -5
  27. package/dist/query-info.d.ts.map +1 -1
  28. package/dist/query-info.js +3 -2
  29. package/dist/query-info.js.map +1 -1
  30. package/dist/react/useAtom.d.ts.map +1 -1
  31. package/dist/react/useAtom.js +2 -2
  32. package/dist/react/useAtom.js.map +1 -1
  33. package/dist/react/useQuery.test.d.ts.map +1 -0
  34. package/dist/{__tests__/react → react}/useQuery.test.js +8 -11
  35. package/dist/react/useQuery.test.js.map +1 -0
  36. package/dist/react/useRow.js +4 -4
  37. package/dist/react/useRow.js.map +1 -1
  38. package/dist/react/useRow.test.d.ts.map +1 -0
  39. package/dist/{__tests__/react → react}/useRow.test.js +14 -38
  40. package/dist/react/useRow.test.js.map +1 -0
  41. package/dist/reactive.d.ts +2 -2
  42. package/dist/reactive.d.ts.map +1 -1
  43. package/dist/reactive.js +50 -15
  44. package/dist/reactive.js.map +1 -1
  45. package/dist/reactive.test.d.ts.map +1 -0
  46. package/dist/{__tests__/reactive.test.js → reactive.test.js} +1 -1
  47. package/dist/reactive.test.js.map +1 -0
  48. package/dist/reactiveQueries/base-class.d.ts +2 -0
  49. package/dist/reactiveQueries/base-class.d.ts.map +1 -1
  50. package/dist/reactiveQueries/base-class.js +1 -0
  51. package/dist/reactiveQueries/base-class.js.map +1 -1
  52. package/dist/reactiveQueries/graphql.d.ts.map +1 -1
  53. package/dist/reactiveQueries/graphql.js +1 -0
  54. package/dist/reactiveQueries/graphql.js.map +1 -1
  55. package/dist/reactiveQueries/js.d.ts.map +1 -1
  56. package/dist/reactiveQueries/js.js +1 -0
  57. package/dist/reactiveQueries/js.js.map +1 -1
  58. package/dist/reactiveQueries/sql.d.ts.map +1 -1
  59. package/dist/reactiveQueries/sql.js +1 -0
  60. package/dist/reactiveQueries/sql.js.map +1 -1
  61. package/dist/reactiveQueries/sql.test.d.ts.map +1 -0
  62. package/dist/{__tests__/reactiveQueries → reactiveQueries}/sql.test.js +44 -34
  63. package/dist/reactiveQueries/sql.test.js.map +1 -0
  64. package/dist/row-query.js +8 -6
  65. package/dist/row-query.js.map +1 -1
  66. package/dist/schema/index.d.ts +20 -7
  67. package/dist/schema/index.d.ts.map +1 -1
  68. package/dist/schema/index.js +18 -3
  69. package/dist/schema/index.js.map +1 -1
  70. package/dist/schema/mutations.d.ts +81 -0
  71. package/dist/schema/mutations.d.ts.map +1 -0
  72. package/dist/schema/mutations.js +29 -0
  73. package/dist/schema/mutations.js.map +1 -0
  74. package/dist/schema/parse-utils.d.ts +3 -3
  75. package/dist/schema/parse-utils.d.ts.map +1 -1
  76. package/dist/schema/table-def.d.ts +2 -2
  77. package/dist/schema/table-def.d.ts.map +1 -1
  78. package/dist/schema/table-def.js +14 -6
  79. package/dist/schema/table-def.js.map +1 -1
  80. package/dist/storage/in-memory/index.d.ts +4 -0
  81. package/dist/storage/in-memory/index.d.ts.map +1 -1
  82. package/dist/storage/in-memory/index.js +3 -0
  83. package/dist/storage/in-memory/index.js.map +1 -1
  84. package/dist/storage/index.d.ts +4 -0
  85. package/dist/storage/index.d.ts.map +1 -1
  86. package/dist/storage/tauri/index.d.ts +4 -0
  87. package/dist/storage/tauri/index.d.ts.map +1 -1
  88. package/dist/storage/tauri/index.js +6 -0
  89. package/dist/storage/tauri/index.js.map +1 -1
  90. package/dist/storage/utils/idb.d.ts +1 -0
  91. package/dist/storage/utils/idb.d.ts.map +1 -1
  92. package/dist/storage/utils/idb.js +11 -0
  93. package/dist/storage/utils/idb.js.map +1 -1
  94. package/dist/storage/web-worker/common.d.ts +11 -0
  95. package/dist/storage/web-worker/common.d.ts.map +1 -0
  96. package/dist/storage/web-worker/common.js +2 -0
  97. package/dist/storage/web-worker/common.js.map +1 -0
  98. package/dist/storage/web-worker/index.d.ts +14 -7
  99. package/dist/storage/web-worker/index.d.ts.map +1 -1
  100. package/dist/storage/web-worker/index.js +70 -14
  101. package/dist/storage/web-worker/index.js.map +1 -1
  102. package/dist/storage/web-worker/make-worker.d.ts +20 -0
  103. package/dist/storage/web-worker/make-worker.d.ts.map +1 -0
  104. package/dist/storage/web-worker/make-worker.js +155 -0
  105. package/dist/storage/web-worker/make-worker.js.map +1 -0
  106. package/dist/storage/web-worker/vite-dev-polyfill.d.ts +2 -0
  107. package/dist/storage/web-worker/vite-dev-polyfill.d.ts.map +1 -0
  108. package/dist/storage/web-worker/vite-dev-polyfill.js +35 -0
  109. package/dist/storage/web-worker/vite-dev-polyfill.js.map +1 -0
  110. package/dist/store.d.ts +32 -42
  111. package/dist/store.d.ts.map +1 -1
  112. package/dist/store.js +82 -131
  113. package/dist/store.js.map +1 -1
  114. package/dist/utils/dev.d.ts +3 -0
  115. package/dist/utils/dev.d.ts.map +1 -0
  116. package/dist/utils/dev.js +16 -0
  117. package/dist/utils/dev.js.map +1 -0
  118. package/dist/utils/util.d.ts +2 -0
  119. package/dist/utils/util.d.ts.map +1 -1
  120. package/dist/utils/util.js +2 -0
  121. package/dist/utils/util.js.map +1 -1
  122. package/package.json +24 -12
  123. package/src/__tests__/react/fixture.tsx +12 -30
  124. package/src/cud.test.ts +52 -0
  125. package/src/{mutations.ts → cud.ts} +29 -28
  126. package/src/inMemoryDatabase.ts +2 -7
  127. package/src/index.ts +14 -8
  128. package/src/migrations.ts +10 -7
  129. package/src/query-info.ts +4 -7
  130. package/src/react/useAtom.ts +2 -2
  131. package/src/{__tests__/react → react}/useQuery.test.tsx +11 -11
  132. package/src/{__tests__/react → react}/useRow.test.tsx +21 -39
  133. package/src/react/useRow.ts +4 -4
  134. package/src/{__tests__/reactive.test.ts → reactive.test.ts} +1 -1
  135. package/src/reactive.ts +60 -19
  136. package/src/reactiveQueries/base-class.ts +4 -0
  137. package/src/reactiveQueries/graphql.ts +2 -0
  138. package/src/reactiveQueries/js.ts +2 -0
  139. package/src/{__tests__/reactiveQueries → reactiveQueries}/sql.test.ts +44 -34
  140. package/src/reactiveQueries/sql.ts +2 -0
  141. package/src/row-query.ts +9 -10
  142. package/src/schema/index.ts +47 -11
  143. package/src/schema/mutations.ts +129 -0
  144. package/src/schema/parse-utils.ts +1 -1
  145. package/src/schema/table-def.ts +20 -8
  146. package/src/storage/in-memory/index.ts +7 -0
  147. package/src/storage/index.ts +8 -0
  148. package/src/storage/tauri/index.ts +10 -0
  149. package/src/storage/utils/idb.ts +14 -0
  150. package/src/storage/web-worker/common.ts +6 -0
  151. package/src/storage/web-worker/index.ts +86 -17
  152. package/src/storage/web-worker/make-worker.ts +214 -0
  153. package/src/storage/web-worker/vite-dev-polyfill.ts +33 -0
  154. package/src/store.ts +142 -212
  155. package/src/utils/dev.ts +23 -0
  156. package/src/utils/util.ts +4 -0
  157. package/dist/__tests__/mutations.test.d.ts +0 -2
  158. package/dist/__tests__/mutations.test.d.ts.map +0 -1
  159. package/dist/__tests__/mutations.test.js +0 -40
  160. package/dist/__tests__/mutations.test.js.map +0 -1
  161. package/dist/__tests__/react/useQuery.test.d.ts.map +0 -1
  162. package/dist/__tests__/react/useQuery.test.js.map +0 -1
  163. package/dist/__tests__/react/useRow.test.d.ts.map +0 -1
  164. package/dist/__tests__/react/useRow.test.js.map +0 -1
  165. package/dist/__tests__/reactive.test.d.ts.map +0 -1
  166. package/dist/__tests__/reactive.test.js.map +0 -1
  167. package/dist/__tests__/reactiveQueries/sql.test.d.ts.map +0 -1
  168. package/dist/__tests__/reactiveQueries/sql.test.js.map +0 -1
  169. package/dist/events.d.ts +0 -7
  170. package/dist/events.d.ts.map +0 -1
  171. package/dist/events.js +0 -2
  172. package/dist/events.js.map +0 -1
  173. package/dist/mutations.d.ts.map +0 -1
  174. package/dist/mutations.js.map +0 -1
  175. package/dist/schema/action.d.ts +0 -30
  176. package/dist/schema/action.d.ts.map +0 -1
  177. package/dist/schema/action.js +0 -3
  178. package/dist/schema/action.js.map +0 -1
  179. package/dist/storage/web-worker/worker.d.ts +0 -13
  180. package/dist/storage/web-worker/worker.d.ts.map +0 -1
  181. package/dist/storage/web-worker/worker.js +0 -110
  182. package/dist/storage/web-worker/worker.js.map +0 -1
  183. package/src/__tests__/mutations.test.ts +0 -43
  184. package/src/events.ts +0 -8
  185. package/src/schema/action.ts +0 -41
  186. package/src/storage/web-worker/worker.ts +0 -141
  187. /package/dist/{__tests__/react → react}/useQuery.test.d.ts +0 -0
  188. /package/dist/{__tests__/react → react}/useRow.test.d.ts +0 -0
  189. /package/dist/{__tests__/reactive.test.d.ts → reactive.test.d.ts} +0 -0
  190. /package/dist/{__tests__/reactiveQueries → reactiveQueries}/sql.test.d.ts +0 -0
@@ -57,6 +57,8 @@ export interface LiveQuery<TResult, TQueryInfo extends QueryInfo = QueryInfoNone
57
57
  queryInfo: TQueryInfo
58
58
 
59
59
  runs: number
60
+
61
+ executionTimes: number[]
60
62
  }
61
63
 
62
64
  export abstract class LiveStoreQueryBase<TResult, TQueryInfo extends QueryInfo>
@@ -81,6 +83,8 @@ export abstract class LiveStoreQueryBase<TResult, TQueryInfo extends QueryInfo>
81
83
  return this.results$.recomputations
82
84
  }
83
85
 
86
+ executionTimes: number[] = []
87
+
84
88
  abstract destroy: () => void
85
89
 
86
90
  run = (otelContext?: otel.Context, debugRefreshReason?: RefreshReason): TResult =>
@@ -197,6 +197,8 @@ export class LiveStoreGraphQLQuery<
197
197
 
198
198
  const durationMs = getDurationMsFromSpan(span)
199
199
 
200
+ this.executionTimes.push(durationMs)
201
+
200
202
  return {
201
203
  result,
202
204
  queriedTables: Array.from(context.queriedTables.values()),
@@ -80,6 +80,8 @@ export class LiveStoreJSQuery<TResult, TQueryInfo extends QueryInfo = QueryInfoN
80
80
 
81
81
  const durationMs = getDurationMsFromSpan(span)
82
82
 
83
+ this.executionTimes.push(durationMs)
84
+
83
85
  setDebugInfo({ _tag: 'js', label, query: fn.toString(), durationMs })
84
86
 
85
87
  return res
@@ -3,8 +3,8 @@ import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'
3
3
  import { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
4
4
  import { describe, expect, it } from 'vitest'
5
5
 
6
- import { computed, ParseUtils, querySQL, sql } from '../../index.js'
7
- import { makeTodoMvc, todos } from '../react/fixture.js'
6
+ import { makeTodoMvc, todos } from '../__tests__/react/fixture.js'
7
+ import { computed, ParseUtils, querySQL, rawSqlMutation, sql } from '../index.js'
8
8
 
9
9
  /*
10
10
  TODO write tests for:
@@ -40,10 +40,7 @@ describe('otel', () => {
40
40
  const query = querySQL(`select * from todos`, { queriedTables: new Set(['todos']) })
41
41
  expect(query.run()).toMatchInlineSnapshot('[]')
42
42
 
43
- store.applyEvent('livestore.RawSql', {
44
- sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);`,
45
- writeTables: ['todos'],
46
- })
43
+ store.mutate(rawSqlMutation({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
47
44
 
48
45
  expect(query.run()).toMatchInlineSnapshot(`
49
46
  [
@@ -91,28 +88,36 @@ describe('otel', () => {
91
88
  },
92
89
  },
93
90
  {
94
- "_name": "LiveStore:applyEvents",
91
+ "_name": "LiveStore:mutations",
95
92
  "children": [
96
93
  {
97
- "_name": "LiveStore:applyEvent",
94
+ "_name": "LiveStore:mutate",
95
+ "attributes": {
96
+ "livestore.mutateLabel": "mutate",
97
+ },
98
98
  "children": [
99
99
  {
100
- "_name": "LiveStore:applyEventWithoutRefresh",
100
+ "_name": "LiveStore:processWrites",
101
101
  "attributes": {
102
- "livestore.actionType": "livestore.RawSql",
103
- "livestore.args": "{
104
- "sql": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);",
105
- "writeTables": [
106
- "todos"
107
- ]
108
- }",
102
+ "livestore.mutateLabel": "mutate",
109
103
  },
110
104
  "children": [
111
105
  {
112
- "_name": "livestore.in-memory-db:execute",
106
+ "_name": "LiveStore:mutatetWithoutRefresh",
113
107
  "attributes": {
114
- "sql.query": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);",
108
+ "livestore.args": "{
109
+ "sql": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)"
110
+ }",
111
+ "livestore.mutation": "livestore.RawSql",
115
112
  },
113
+ "children": [
114
+ {
115
+ "_name": "livestore.in-memory-db:execute",
116
+ "attributes": {
117
+ "sql.query": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)",
118
+ },
119
+ },
120
+ ],
116
121
  },
117
122
  ],
118
123
  },
@@ -183,10 +188,7 @@ describe('otel', () => {
183
188
  }
184
189
  `)
185
190
 
186
- store.applyEvent('livestore.RawSql', {
187
- sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);`,
188
- writeTables: ['todos'],
189
- })
191
+ store.mutate(rawSqlMutation({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
190
192
 
191
193
  expect(query.run()).toMatchInlineSnapshot(`
192
194
  {
@@ -232,28 +234,36 @@ describe('otel', () => {
232
234
  },
233
235
  },
234
236
  {
235
- "_name": "LiveStore:applyEvents",
237
+ "_name": "LiveStore:mutations",
236
238
  "children": [
237
239
  {
238
- "_name": "LiveStore:applyEvent",
240
+ "_name": "LiveStore:mutate",
241
+ "attributes": {
242
+ "livestore.mutateLabel": "mutate",
243
+ },
239
244
  "children": [
240
245
  {
241
- "_name": "LiveStore:applyEventWithoutRefresh",
246
+ "_name": "LiveStore:processWrites",
242
247
  "attributes": {
243
- "livestore.actionType": "livestore.RawSql",
244
- "livestore.args": "{
245
- "sql": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);",
246
- "writeTables": [
247
- "todos"
248
- ]
249
- }",
248
+ "livestore.mutateLabel": "mutate",
250
249
  },
251
250
  "children": [
252
251
  {
253
- "_name": "livestore.in-memory-db:execute",
252
+ "_name": "LiveStore:mutatetWithoutRefresh",
254
253
  "attributes": {
255
- "sql.query": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);",
254
+ "livestore.args": "{
255
+ "sql": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)"
256
+ }",
257
+ "livestore.mutation": "livestore.RawSql",
256
258
  },
259
+ "children": [
260
+ {
261
+ "_name": "livestore.in-memory-db:execute",
262
+ "attributes": {
263
+ "sql.query": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)",
264
+ },
265
+ },
266
+ ],
257
267
  },
258
268
  ],
259
269
  },
@@ -173,6 +173,8 @@ export class LiveStoreSQLQuery<TResult, TQueryInfo extends QueryInfo = QueryInfo
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
package/src/row-query.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { shouldNeverHappen } from '@livestore/utils'
2
- import { pipe, ReadonlyRecord, Schema, TreeFormatter } from '@livestore/utils/effect'
2
+ import { Schema, TreeFormatter } from '@livestore/utils/effect'
3
3
  import type * as otel from '@opentelemetry/api'
4
4
  import { SqliteAst, SqliteDsl } from 'effect-db-schema'
5
5
 
@@ -138,20 +138,19 @@ 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(', ')
141
+ const defaultValues = getDefaultValuesEncoded(table, explicitDefaultValues)
142
+
143
+ const defaultColumnNames = [...Object.keys(defaultValues), 'id']
144
+ const columnValues = defaultColumnNames.map((name) => `$${name}`).join(', ')
143
145
 
144
146
  const tableName = table.sqliteDef.name
145
- const insertQuery = sql`insert into ${tableName} (${columnNames.join(
147
+ const insertQuery = sql`insert into ${tableName} (${defaultColumnNames.join(
146
148
  ', ',
147
149
  )}) select ${columnValues} where not exists(select 1 from ${tableName} where id = '${id}')`
148
150
 
149
- const defaultValues = pipe(
150
- getDefaultValuesEncoded(table),
151
- ReadonlyRecord.map((val, columnName) => explicitDefaultValues?.[columnName] ?? val),
152
- )
153
-
154
- db.execute(insertQuery, prepareBindValues({ ...defaultValues, id }, insertQuery), [tableName], { otelContext })
151
+ db.execute(insertQuery, prepareBindValues({ ...defaultValues, id }, insertQuery), new Set([tableName]), {
152
+ otelContext,
153
+ })
155
154
  }
156
155
 
157
156
  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,7 +1,7 @@
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
 
@@ -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
 
@@ -156,16 +156,25 @@ export const tableIsSingleton = <TTableDef extends TableDef>(
156
156
  tableDef: TTableDef,
157
157
  ): tableDef is TTableDef & { options: { isSingleton: true } } => tableDef.options.isSingleton === true
158
158
 
159
- export const getDefaultValuesEncoded = <TTableDef extends TableDef>(tableDef: TTableDef) =>
159
+ export const getDefaultValuesEncoded = <TTableDef extends TableDef>(
160
+ tableDef: TTableDef,
161
+ fallbackValues?: Record<string, any>,
162
+ ) =>
160
163
  pipe(
161
164
  tableDef.sqliteDef.columns,
162
- ReadonlyRecord.filter((_, key) => key !== 'id'),
165
+ ReadonlyRecord.filter((col, key) => {
166
+ if (fallbackValues?.[key] !== undefined) return true
167
+ if (key === 'id') return false
168
+ return col!.default._tag === 'None' || SqliteDsl.isSqlDefaultValue(col!.default.value) === false
169
+ }),
163
170
  ReadonlyRecord.map((column, columnName) =>
164
- column!.default._tag === 'None'
165
- ? column!.nullable === true
166
- ? null
167
- : shouldNeverHappen(`Column ${columnName} has no default value and is not nullable`)
168
- : Schema.encodeSync(column!.schema)(column!.default.value),
171
+ fallbackValues?.[columnName] === undefined
172
+ ? column!.default._tag === 'None'
173
+ ? column!.nullable === true
174
+ ? null
175
+ : shouldNeverHappen(`Column ${columnName} has no default value and is not nullable`)
176
+ : Schema.encodeSync(column!.schema)(column!.default.value)
177
+ : fallbackValues[columnName],
169
178
  ),
170
179
  )
171
180
 
@@ -173,6 +182,9 @@ export const getDefaultValuesDecoded = <TTableDef extends TableDef>(tableDef: TT
173
182
  pipe(
174
183
  tableDef.sqliteDef.columns,
175
184
  ReadonlyRecord.filter((_, key) => key !== 'id'),
185
+ ReadonlyRecord.filter(
186
+ (col) => col!.default._tag === 'None' || SqliteDsl.isSqlDefaultValue(col!.default.value) === false,
187
+ ),
176
188
  ReadonlyRecord.map((column, columnName) =>
177
189
  column!.default._tag === 'None'
178
190
  ? 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
 
@@ -68,4 +68,18 @@ export class IDB {
68
68
  }
69
69
  })
70
70
  }
71
+
72
+ public async deleteDb(): Promise<void> {
73
+ return new Promise((resolve, reject) => {
74
+ const deleteRequest = indexedDB.deleteDatabase(this.dbName)
75
+
76
+ deleteRequest.onsuccess = () => {
77
+ resolve()
78
+ }
79
+
80
+ deleteRequest.onerror = () => {
81
+ reject(new Error('Failed to delete database.'))
82
+ }
83
+ })
84
+ }
71
85
  }
@@ -0,0 +1,6 @@
1
+ import type { MutationEvent } from '../../schema/mutations.js'
2
+ import type { PreparedBindValues } from '../../utils/util.js'
3
+
4
+ export type ExecutionBacklogItem =
5
+ | { _tag: 'execute'; query: string; bindValues?: PreparedBindValues }
6
+ | { _tag: 'mutate'; mutationEventEncoded: MutationEvent.Any }