@livestore/livestore 0.0.39 → 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 (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} +15 -7
  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 +7 -5
  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 +1 -1
  77. package/dist/schema/table-def.d.ts.map +1 -1
  78. package/dist/schema/table-def.js +2 -2
  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} +28 -22
  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 +11 -9
  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 +7 -1
  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
@@ -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
@@ -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,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
 
@@ -160,6 +160,9 @@ export const getDefaultValuesEncoded = <TTableDef extends TableDef>(tableDef: TT
160
160
  pipe(
161
161
  tableDef.sqliteDef.columns,
162
162
  ReadonlyRecord.filter((_, key) => key !== 'id'),
163
+ ReadonlyRecord.filter(
164
+ (col) => col!.default._tag === 'None' || SqliteDsl.isSqlDefaultValue(col!.default.value) === false,
165
+ ),
163
166
  ReadonlyRecord.map((column, columnName) =>
164
167
  column!.default._tag === 'None'
165
168
  ? column!.nullable === true
@@ -173,6 +176,9 @@ export const getDefaultValuesDecoded = <TTableDef extends TableDef>(tableDef: TT
173
176
  pipe(
174
177
  tableDef.sqliteDef.columns,
175
178
  ReadonlyRecord.filter((_, key) => key !== 'id'),
179
+ ReadonlyRecord.filter(
180
+ (col) => col!.default._tag === 'None' || SqliteDsl.isSqlDefaultValue(col!.default.value) === false,
181
+ ),
176
182
  ReadonlyRecord.map((column, columnName) =>
177
183
  column!.default._tag === 'None'
178
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
 
@@ -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 }