@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
@@ -0,0 +1,52 @@
1
+ import { describe, expect, test } from 'vitest'
2
+
3
+ import { tables } from './__tests__/react/fixture.js'
4
+ import { makeCudMutations } from './cud.js'
5
+ import type { MutationEvent } from './index.js'
6
+
7
+ describe('cud mutations', () => {
8
+ const cud = makeCudMutations(tables)
9
+
10
+ test('basic', () => {
11
+ expect(patchId(cud.todos.insert({ id: 't1', completed: true, text: 'Task 1' }))).toMatchInlineSnapshot(`
12
+ {
13
+ "args": {
14
+ "bindValues": {
15
+ "completed": 1,
16
+ "id": "t1",
17
+ "text": "Task 1",
18
+ },
19
+ "sql": "INSERT INTO todos (id, text, completed) VALUES ($id, $text, $completed)",
20
+ "writeTables": Set {
21
+ "todos",
22
+ },
23
+ },
24
+ "id": "00000000-0000-0000-0000-000000000000",
25
+ "mutation": "livestore.RawSql",
26
+ }
27
+ `)
28
+
29
+ expect(patchId(cud.todos.update({ where: { id: 't1' }, values: { text: 'Task 1 - fixed' } })))
30
+ .toMatchInlineSnapshot(`
31
+ {
32
+ "args": {
33
+ "bindValues": {
34
+ "update_text": "Task 1 - fixed",
35
+ "where_id": "t1",
36
+ },
37
+ "sql": "UPDATE todos SET text = $update_text WHERE id = $where_id",
38
+ "writeTables": Set {
39
+ "todos",
40
+ },
41
+ },
42
+ "id": "00000000-0000-0000-0000-000000000000",
43
+ "mutation": "livestore.RawSql",
44
+ }
45
+ `)
46
+ })
47
+ })
48
+
49
+ const patchId = (muationEvent: MutationEvent.Any) => {
50
+ const id = `00000000-0000-0000-0000-000000000000`
51
+ return { ...muationEvent, id }
52
+ }
@@ -1,28 +1,34 @@
1
1
  import * as SqlQueries from '@livestore/sql-queries'
2
- import { pipe, ReadonlyRecord } from '@livestore/utils/effect'
3
2
  import type { SqliteDsl } from 'effect-db-schema'
4
3
 
5
4
  import type { RowResult } from './row-query.js'
6
- import type { LiveStoreSchema } from './schema/index.js'
5
+ import { rawSqlMutation, type RawSqlMutationEvent } from './schema/index.js'
7
6
  import { getDefaultValuesEncoded, type TableDef } from './schema/table-def.js'
8
- import type { GetValForKey } from './utils/util.js'
7
+ import { type GetValForKey, isIterable } from './utils/util.js'
9
8
 
10
- export const makeMutations = <TDbSchema extends SqliteDsl.DbSchema>(
11
- schema: LiveStoreSchema<TDbSchema>,
12
- ): Mutations<TDbSchema> => {
13
- return Object.fromEntries(Array.from(schema.tables.values()).map(mutationsForTable)) as any
9
+ export const makeCudMutations = <TTableDef extends TableDef>(
10
+ tables: Iterable<TTableDef> | Record<string, TTableDef>,
11
+ ): CudMutations<TTableDef> => {
12
+ const cudMutationRecord: CudMutations<TTableDef> = {} as any
13
+
14
+ const tables_ = isIterable(tables) ? tables : Object.values(tables)
15
+
16
+ for (const tableDef of tables_) {
17
+ const [tableName, cudMutation] = cudMutationsForTable(tableDef)
18
+ cudMutationRecord[tableName] = cudMutation as any
19
+ }
20
+
21
+ return cudMutationRecord
14
22
  }
15
23
 
16
- const mutationsForTable = <TTableDef extends TableDef>(tableDef: TTableDef): [string, Mutation<TTableDef>] => {
24
+ const cudMutationsForTable = <TTableDef extends TableDef>(
25
+ tableDef: TTableDef,
26
+ ): [TTableDef['sqliteDef']['name'], CudMutation<TTableDef>] => {
17
27
  const table = tableDef.sqliteDef
18
28
  const writeTables = new Set([table.name])
19
29
  const api = {
20
30
  insert: (values_: any) => {
21
- const defaultValues = getDefaultValuesEncoded(tableDef)
22
- const values = pipe(
23
- tableDef.sqliteDef.columns,
24
- ReadonlyRecord.map((_, columnName) => values_?.[columnName] ?? defaultValues[columnName]),
25
- )
31
+ const values = getDefaultValuesEncoded(tableDef, values_)
26
32
 
27
33
  const [sql, bindValues] = SqlQueries.insertRow({
28
34
  tableName: table.name,
@@ -30,7 +36,7 @@ const mutationsForTable = <TTableDef extends TableDef>(tableDef: TTableDef): [st
30
36
  options: { orReplace: false },
31
37
  values: values as any,
32
38
  })
33
- return { eventType: 'livestore.RawSql', args: { sql, bindValues, writeTables } }
39
+ return rawSqlMutation({ sql, bindValues, writeTables })
34
40
  },
35
41
  update: ({ where, values }) => {
36
42
  const [sql, bindValues] = SqlQueries.updateRows({
@@ -39,7 +45,7 @@ const mutationsForTable = <TTableDef extends TableDef>(tableDef: TTableDef): [st
39
45
  where: where,
40
46
  updateValues: values,
41
47
  })
42
- return { eventType: 'livestore.RawSql', args: { sql, bindValues, writeTables } }
48
+ return rawSqlMutation({ sql, bindValues, writeTables })
43
49
  },
44
50
  delete: ({ where }) => {
45
51
  const [sql, bindValues] = SqlQueries.deleteRows({
@@ -47,40 +53,35 @@ const mutationsForTable = <TTableDef extends TableDef>(tableDef: TTableDef): [st
47
53
  columns: table.columns,
48
54
  where: where,
49
55
  })
50
- return { eventType: 'livestore.RawSql', args: { sql, bindValues, writeTables } }
56
+ return rawSqlMutation({ sql, bindValues, writeTables })
51
57
  },
52
- } satisfies Mutation<TTableDef>
58
+ } satisfies CudMutation<TTableDef>
53
59
 
54
60
  return [tableDef.sqliteDef.name, api]
55
61
  }
56
62
 
57
- export type MutationEvent = {
58
- eventType: 'livestore.RawSql'
59
- args: { sql: string; bindValues: SqlQueries.BindValues; writeTables: Set<string> }
60
- }
61
-
62
63
  export type UpdateMutation<TTableDef extends TableDef> = (args: {
63
64
  // TODO also allow `id` if present in `TTableDef`
64
65
  where: Partial<RowResult<TTableDef>>
65
66
  values: Partial<RowResult<TTableDef>>
66
- }) => MutationEvent
67
+ }) => RawSqlMutationEvent
67
68
 
68
69
  export type RowInsert<TTableDef extends TableDef> = TTableDef['isSingleColumn'] extends true
69
70
  ? GetValForKey<SqliteDsl.FromColumns.InsertRowDecoded<TTableDef['sqliteDef']['columns']>, 'value'>
70
71
  : SqliteDsl.FromColumns.InsertRowDecoded<TTableDef['sqliteDef']['columns']>
71
72
 
72
- export type InsertMutation<TTableDef extends TableDef> = (values: RowInsert<TTableDef>) => MutationEvent
73
+ export type InsertMutation<TTableDef extends TableDef> = (values: RowInsert<TTableDef>) => RawSqlMutationEvent
73
74
 
74
75
  export type DeleteMutation<TTableDef extends TableDef> = (args: {
75
76
  where: Partial<RowResult<TTableDef>>
76
- }) => MutationEvent
77
+ }) => RawSqlMutationEvent
77
78
 
78
- export type Mutation<TTableDef extends TableDef> = {
79
+ export type CudMutation<TTableDef extends TableDef> = {
79
80
  insert: InsertMutation<TTableDef>
80
81
  update: UpdateMutation<TTableDef>
81
82
  delete: DeleteMutation<TTableDef>
82
83
  }
83
84
 
84
- export type Mutations<TDbSchema extends SqliteDsl.DbSchema> = {
85
- [TTableName in keyof TDbSchema]: Mutation<TableDef<TDbSchema[TTableName]>>
85
+ export type CudMutations<TTableDef extends TableDef> = {
86
+ [TTableName in TTableDef['sqliteDef']['name']]: CudMutation<Extract<TTableDef, { sqliteDef: { name: TTableName } }>>
86
87
  }
@@ -138,7 +138,7 @@ export class InMemoryDatabase {
138
138
  execute(
139
139
  query: string,
140
140
  bindValues?: PreparedBindValues,
141
- writeTables?: ReadonlyArray<string>,
141
+ writeTables?: ReadonlySet<string>,
142
142
  options?: { hasNoEffects?: boolean; otelContext?: otel.Context },
143
143
  ): { durationMs: number } {
144
144
  // console.debug('in-memory-db:execute', query, bindValues)
@@ -170,12 +170,7 @@ export class InMemoryDatabase {
170
170
  stmt.reset() // Reset is needed for next execution
171
171
  }
172
172
  } catch (error) {
173
- shouldNeverHappen(
174
- `Error executing query: ${error} \n ${JSON.stringify({
175
- query,
176
- bindValues,
177
- })}`,
178
- )
173
+ shouldNeverHappen(`Error executing query: ${error} \n ${JSON.stringify({ query, bindValues })}`)
179
174
  }
180
175
 
181
176
  if (options?.hasNoEffects !== true && !this.resultCache.ignoreQuery(query)) {
package/src/index.ts CHANGED
@@ -13,23 +13,29 @@ export { LiveStoreSQLQuery, querySQL, type MapRows } from './reactiveQueries/sql
13
13
  export { LiveStoreGraphQLQuery, queryGraphQL } from './reactiveQueries/graphql.js'
14
14
  export { type GetAtomResult, type DbGraph, makeDbGraph, type LiveQuery } from './reactiveQueries/base-class.js'
15
15
 
16
- export { globalDbGraph } from './global-state.js'
16
+ export { globalDbGraph, dynamicallyRegisteredTables } from './global-state.js'
17
17
 
18
18
  export { type RowResult, type RowResultEncoded, rowQuery, deriveColQuery } from './row-query.js'
19
19
 
20
- export * from './mutations.js'
20
+ export * from './cud.js'
21
21
 
22
- export { defineAction, defineActions, makeSchema, DbSchema, ParseUtils } from './schema/index.js'
22
+ export {
23
+ makeSchema,
24
+ DbSchema,
25
+ ParseUtils,
26
+ defineMutation,
27
+ rawSqlMutation,
28
+ makeMutationEventSchema,
29
+ makeMutationDefRecord,
30
+ } from './schema/index.js'
23
31
 
24
32
  export type {
25
33
  LiveStoreSchema,
26
34
  InputSchema,
27
- GetActionArgs,
28
- GetApplyEventArgs,
29
- ActionDefinition,
30
- ActionDefinitions,
31
- SQLWriteStatement,
32
35
  SchemaMetaRow,
36
+ MutationDef,
37
+ MutationEvent,
38
+ MutationDefMap,
33
39
  } from './schema/index.js'
34
40
 
35
41
  export { SqliteAst, SqliteDsl } from 'effect-db-schema'
package/src/migrations.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Schema as EffectSchema } from '@livestore/utils/effect'
2
2
  import type * as otel from '@opentelemetry/api'
3
- import { SqliteAst } from 'effect-db-schema'
3
+ import { SqliteAst, SqliteDsl } from 'effect-db-schema'
4
4
  import { memoize } from 'lodash-es'
5
5
 
6
6
  import { dynamicallyRegisteredTables } from './global-state.js'
@@ -26,7 +26,7 @@ export const migrateDb = ({
26
26
  // TODO use schema migration definition from schema.ts instead
27
27
  sql`create table if not exists ${SCHEMA_META_TABLE} (tableName text primary key, schemaHash text, updatedAt text);`,
28
28
  undefined,
29
- [],
29
+ new Set(),
30
30
  { otelContext },
31
31
  )
32
32
 
@@ -80,11 +80,11 @@ export const migrateTable = ({
80
80
  const columnSpec = makeColumnSpec(tableAst)
81
81
 
82
82
  // TODO need to possibly handle cascading deletes due to foreign keys
83
- db.execute(sql`drop table if exists ${tableName}`, undefined, [], { otelContext })
84
- db.execute(sql`create table if not exists ${tableName} (${columnSpec});`, undefined, [], { otelContext })
83
+ db.execute(sql`drop table if exists ${tableName}`, undefined, new Set(), { otelContext })
84
+ db.execute(sql`create table if not exists ${tableName} (${columnSpec});`, undefined, new Set(), { otelContext })
85
85
 
86
86
  for (const index of tableAst.indexes) {
87
- db.execute(createIndexFromDefinition(tableName, index), undefined, [], { otelContext })
87
+ db.execute(createIndexFromDefinition(tableName, index), undefined, new Set(), { otelContext })
88
88
  }
89
89
 
90
90
  const updatedAt = getMemoizedTimestamp()
@@ -94,7 +94,7 @@ export const migrateTable = ({
94
94
  ON CONFLICT (tableName) DO UPDATE SET schemaHash = $schemaHash, updatedAt = $updatedAt;
95
95
  `,
96
96
  { $tableName: tableName, $schemaHash: schemaHash, $updatedAt: updatedAt } as unknown as PreparedBindValues,
97
- [],
97
+ new Set(),
98
98
  { otelContext },
99
99
  )
100
100
  }
@@ -121,10 +121,13 @@ const toSqliteColumnSpec = (column: SqliteAst.Column) => {
121
121
  const defaultValueStr = (() => {
122
122
  if (column.default._tag === 'None') return ''
123
123
 
124
+ if (SqliteDsl.isSqlDefaultValue(column.default.value)) return `default ${column.default.value.sql}`
125
+
124
126
  const encodeValue = EffectSchema.encodeSync(column.schema)
125
127
  const encodedDefaultValue = encodeValue(column.default.value)
126
128
 
127
- return columnTypeStr === 'text' ? `default '${encodedDefaultValue}'` : `default ${encodedDefaultValue}`
129
+ if (columnTypeStr === 'text') return `default '${encodedDefaultValue}'`
130
+ return `default ${encodedDefaultValue}`
128
131
  })()
129
132
 
130
133
  return `${column.name} ${columnTypeStr} ${nullableStr} ${defaultValueStr}`
package/src/query-info.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { notYetImplemented, shouldNeverHappen } from '@livestore/utils'
2
2
  import { Schema } from '@livestore/utils/effect'
3
3
 
4
+ import { rawSqlMutation, type RawSqlMutationEvent } from './schema/mutations.js'
4
5
  import type { FromTable, TableDef } from './schema/table-def.js'
5
6
 
6
7
  /**
@@ -49,8 +50,6 @@ type GetJsonColumn<TTableDef extends TableDef> = keyof {
49
50
  : never]: {}
50
51
  }
51
52
 
52
- // type GetObjValues<TObj extends {}> = TObj[keyof TObj]
53
-
54
53
  export type UpdateValueForPath<TPath extends QueryInfo> = TPath extends { _tag: 'Row' }
55
54
  ? Partial<FromTable.RowDecodedAll<TPath['table']>>
56
55
  : TPath extends { _tag: 'Col' }
@@ -59,10 +58,10 @@ export type UpdateValueForPath<TPath extends QueryInfo> = TPath extends { _tag:
59
58
  ? { TODO: true }
60
59
  : never
61
60
 
62
- export const storeEventForQueryInfo = <TPath extends QueryInfo>(
61
+ export const mutationForQueryInfo = <const TPath extends QueryInfo>(
63
62
  updatePath: TPath,
64
63
  value: UpdateValueForPath<TPath>,
65
- ): StoreEvent => {
64
+ ): RawSqlMutationEvent => {
66
65
  if (updatePath._tag === 'ColJsonValue' || updatePath._tag === 'None') {
67
66
  return notYetImplemented('TODO')
68
67
  }
@@ -96,7 +95,5 @@ export const storeEventForQueryInfo = <TPath extends QueryInfo>(
96
95
  const sql = `UPDATE ${sqliteTableDef.name} SET ${updateClause} ${whereClause}`
97
96
  const writeTables = new Set<string>([updatePath.table.sqliteDef.name])
98
97
 
99
- return { eventType: 'livestore.RawSql', args: { sql, bindValues, writeTables } }
98
+ return rawSqlMutation({ sql, bindValues, writeTables })
100
99
  }
101
-
102
- type StoreEvent = { eventType: string; args: any }
@@ -1,6 +1,6 @@
1
1
  import React from 'react'
2
2
 
3
- import { type QueryInfoCol, type QueryInfoRow, storeEventForQueryInfo } from '../query-info.js'
3
+ import { mutationForQueryInfo, type QueryInfoCol, type QueryInfoRow } from '../query-info.js'
4
4
  import type { LiveQuery } from '../reactiveQueries/base-class.js'
5
5
  import { useStore } from './LiveStoreContext.js'
6
6
  import { useQueryRef } from './useQuery.js'
@@ -17,7 +17,7 @@ export const useAtom = <TQuery extends LiveQuery<any, QueryInfoRow<any> | QueryI
17
17
  return (newValueOrFn: any) => {
18
18
  const newValue = typeof newValueOrFn === 'function' ? newValueOrFn(query$Ref.current) : newValueOrFn
19
19
 
20
- store.applyEvents([storeEventForQueryInfo(query$.queryInfo!, newValue)])
20
+ store.mutate(mutationForQueryInfo(query$.queryInfo!, newValue))
21
21
  }
22
22
  }, [query$.queryInfo, query$Ref, store])
23
23
 
@@ -2,15 +2,15 @@ import { act, renderHook } from '@testing-library/react'
2
2
  import React from 'react'
3
3
  import { describe, expect, it } from 'vitest'
4
4
 
5
- import * as LiveStoreReact from '../../react/index.js'
6
- import { querySQL } from '../../reactiveQueries/sql.js'
7
- import { makeTodoMvc, parseTodos } from './fixture.js'
5
+ import { makeTodoMvc, parseTodos } from '../__tests__/react/fixture.js'
6
+ import { querySQL } from '../reactiveQueries/sql.js'
7
+ import * as LiveStoreReact from './index.js'
8
8
 
9
9
  describe('useQuery', () => {
10
10
  it('simple', async () => {
11
11
  let renderCount = 0
12
12
 
13
- const { wrapper, store, mutations } = await makeTodoMvc()
13
+ const { wrapper, store, cud } = await makeTodoMvc()
14
14
 
15
15
  const allTodos$ = querySQL(`select * from todos`, { map: parseTodos })
16
16
 
@@ -26,7 +26,7 @@ describe('useQuery', () => {
26
26
  expect(result.current.length).toBe(0)
27
27
  expect(renderCount).toBe(1)
28
28
 
29
- act(() => store.applyEvents([mutations.todos.insert({ id: 't1', text: 'buy milk', completed: false })]))
29
+ act(() => store.mutate(cud.todos.insert({ id: 't1', text: 'buy milk', completed: false })))
30
30
 
31
31
  expect(result.current.length).toBe(1)
32
32
  expect(result.current[0]!.text).toBe('buy milk')
@@ -36,15 +36,15 @@ describe('useQuery', () => {
36
36
  it('same `useQuery` hook invoked with different queries', async () => {
37
37
  let renderCount = 0
38
38
 
39
- const { wrapper, store, mutations } = await makeTodoMvc()
39
+ const { wrapper, store, cud } = await makeTodoMvc()
40
40
 
41
41
  const todo1$ = querySQL(`select * from todos where id = 't1'`, { label: 'libraryTracksView1', map: parseTodos })
42
42
  const todo2$ = querySQL(`select * from todos where id = 't2'`, { label: 'libraryTracksView2', map: parseTodos })
43
43
 
44
- store.applyEvents([
45
- mutations.todos.insert({ id: 't1', text: 'buy milk', completed: false }),
46
- mutations.todos.insert({ id: 't2', text: 'buy eggs', completed: false }),
47
- ])
44
+ store.mutate(
45
+ cud.todos.insert({ id: 't1', text: 'buy milk', completed: false }),
46
+ cud.todos.insert({ id: 't2', text: 'buy eggs', completed: false }),
47
+ )
48
48
 
49
49
  const { result, rerender } = renderHook(
50
50
  (todoId: string) => {
@@ -60,7 +60,7 @@ describe('useQuery', () => {
60
60
  expect(result.current).toBe('buy milk')
61
61
  expect(renderCount).toBe(1)
62
62
 
63
- act(() => store.applyEvents([mutations.todos.update({ where: { id: 't1' }, values: { text: 'buy soy milk' } })]))
63
+ act(() => store.mutate(cud.todos.update({ where: { id: 't1' }, values: { text: 'buy soy milk' } })))
64
64
 
65
65
  expect(result.current).toBe('buy soy milk')
66
66
  expect(renderCount).toBe(2)
@@ -2,10 +2,11 @@ import { act, render, renderHook } from '@testing-library/react'
2
2
  import React from 'react'
3
3
  import { describe, expect, it } from 'vitest'
4
4
 
5
- import * as LiveStore from '../../index.js'
6
- import * as LiveStoreReact from '../../react/index.js'
7
- import type { Todo } from './fixture.js'
8
- import { makeTodoMvc, todos } from './fixture.js'
5
+ import type { Todo } from '../__tests__/react/fixture.js'
6
+ import { makeTodoMvc, todos } from '../__tests__/react/fixture.js'
7
+ import * as LiveStore from '../index.js'
8
+ import { mutationForQueryInfo } from '../query-info.js'
9
+ import * as LiveStoreReact from './index.js'
9
10
 
10
11
  describe('useRow', () => {
11
12
  it('should update the data based on component key', async () => {
@@ -27,9 +28,7 @@ describe('useRow', () => {
27
28
  expect(result.current.state.username).toBe('')
28
29
  expect(renderCount).toBe(1)
29
30
 
30
- act(() => {
31
- void store.execute(LiveStore.sql`INSERT INTO UserInfo (id, username) VALUES ('u2', 'username_u2');`)
32
- })
31
+ act(() => store.execute(LiveStore.sql`INSERT INTO UserInfo (id, username) VALUES ('u2', 'username_u2')`))
33
32
 
34
33
  rerender('u2')
35
34
 
@@ -85,9 +84,7 @@ describe('useRow', () => {
85
84
 
86
85
  act(() => result.current.setState.username('username_u1_hello'))
87
86
 
88
- act(() => {
89
- void store.execute(LiveStore.sql`UPDATE UserInfo SET username = 'username_u1_hello' WHERE id = 'u1';`)
90
- })
87
+ act(() => store.execute(LiveStore.sql`UPDATE UserInfo SET username = 'username_u1_hello' WHERE id = 'u1';`))
91
88
 
92
89
  expect(result.current.state.id).toBe('u1')
93
90
  expect(result.current.state.username).toBe('username_u1_hello')
@@ -149,10 +146,11 @@ describe('useRow', () => {
149
146
  expect(appRouterRenderCount).toBe(1)
150
147
 
151
148
  act(() =>
152
- store.applyEvent('livestore.RawSql', {
153
- sql: LiveStore.sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);`,
154
- writeTables: ['todos'],
155
- }),
149
+ store.mutate(
150
+ LiveStore.rawSqlMutation({
151
+ sql: LiveStore.sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)`,
152
+ }),
153
+ ),
156
154
  )
157
155
 
158
156
  expect(appRouterRenderCount).toBe(1)
@@ -168,31 +166,15 @@ describe('useRow', () => {
168
166
  expect(renderResult.getByRole('current-id').innerHTML).toMatchInlineSnapshot('"Current Task Id: t1"')
169
167
 
170
168
  act(() =>
171
- store.applyEvents([
172
- {
173
- eventType: 'livestore.RawSql',
174
- args: {
175
- sql: LiveStore.sql`INSERT INTO todos (id, text, completed) VALUES ('t2', 'buy eggs', 0);`,
176
- writeTables: ['todos'],
177
- },
178
- },
179
- {
180
- eventType: 'livestore.UpdateComponentState',
181
- args: {
182
- id: 'singleton',
183
- columnNames: ['currentTaskId'],
184
- tableName: AppRouterSchema.sqliteDef.name,
185
- bindValues: { currentTaskId: 't2' },
186
- },
187
- },
188
- {
189
- eventType: 'livestore.RawSql',
190
- args: {
191
- sql: LiveStore.sql`INSERT INTO todos (id, text, completed) VALUES ('t3', 'buy bread', 0);`,
192
- writeTables: ['todos'],
193
- },
194
- },
195
- ]),
169
+ store.mutate(
170
+ LiveStore.rawSqlMutation({
171
+ sql: LiveStore.sql`INSERT INTO todos (id, text, completed) VALUES ('t2', 'buy eggs', 0)`,
172
+ }),
173
+ mutationForQueryInfo({ _tag: 'Col', table: AppRouterSchema, column: 'currentTaskId', id: 'singleton' }, 't2'),
174
+ LiveStore.rawSqlMutation({
175
+ sql: LiveStore.sql`INSERT INTO todos (id, text, completed) VALUES ('t3', 'buy bread', 0)`,
176
+ }),
177
+ ),
196
178
  )
197
179
 
198
180
  expect(appRouterRenderCount).toBe(3)
@@ -5,7 +5,7 @@ import React from 'react'
5
5
 
6
6
  import type { DbGraph, LiveQuery } from '../index.js'
7
7
  import type { QueryInfo } from '../query-info.js'
8
- import { storeEventForQueryInfo } from '../query-info.js'
8
+ import { mutationForQueryInfo } from '../query-info.js'
9
9
  import type { RowResult } from '../row-query.js'
10
10
  import { rowQuery } from '../row-query.js'
11
11
  import { type DefaultSqliteTableDef, type TableDef, tableIsSingleton, type TableOptions } from '../schema/table-def.js'
@@ -117,7 +117,7 @@ export const useRow: {
117
117
  const newValue = typeof newValueOrFn === 'function' ? newValueOrFn(query$Ref.current) : newValueOrFn
118
118
  if (query$Ref.current === newValue) return
119
119
 
120
- store.applyEvents([storeEventForQueryInfo(query$.queryInfo!, { value: newValue })])
120
+ store.mutate(mutationForQueryInfo(query$.queryInfo!, { value: newValue }))
121
121
  }
122
122
  } else {
123
123
  const setState = // TODO: do we have a better type for the values that can go in SQLite?
@@ -130,7 +130,7 @@ export const useRow: {
130
130
  // @ts-expect-error TODO fix typing
131
131
  if (query$Ref.current[columnName] === newValue) return
132
132
 
133
- store.applyEvents([storeEventForQueryInfo(query$.queryInfo!, { [columnName]: newValue })])
133
+ store.mutate(mutationForQueryInfo(query$.queryInfo!, { [columnName]: newValue }))
134
134
  })
135
135
 
136
136
  setState.setMany = (columnValuesOrFn: Partial<TComponentState>) => {
@@ -147,7 +147,7 @@ export const useRow: {
147
147
  return
148
148
  }
149
149
 
150
- store.applyEvents([storeEventForQueryInfo(query$.queryInfo!, columnValues)])
150
+ store.mutate(mutationForQueryInfo(query$.queryInfo!, columnValues))
151
151
  }
152
152
 
153
153
  return setState as any
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it } from 'vitest'
2
2
 
3
- import { ReactiveGraph } from '../reactive.js'
3
+ import { ReactiveGraph } from './reactive.js'
4
4
 
5
5
  describe('a trivial graph', () => {
6
6
  const makeGraph = () => {
package/src/reactive.ts CHANGED
@@ -24,7 +24,7 @@
24
24
  /* eslint-disable prefer-arrow/prefer-arrow-functions */
25
25
 
26
26
  import type { PrettifyFlat } from '@livestore/utils'
27
- import { pick, shouldNeverHappen } from '@livestore/utils'
27
+ import { shouldNeverHappen } from '@livestore/utils'
28
28
  import type * as otel from '@opentelemetry/api'
29
29
  import { isEqual } from 'lodash-es'
30
30
 
@@ -94,7 +94,7 @@ export type DebugThunkInfo<T extends string = string> = {
94
94
  }
95
95
 
96
96
  export type DebugRefreshReasonBase =
97
- /** Usually in response to some `applyEvent`/`applyEvents` with `skipRefresh: true` */
97
+ /** Usually in response to some `mutate` calls with `skipRefresh: true` */
98
98
  | {
99
99
  _tag: 'runDeferredEffects'
100
100
  originalRefreshReasons?: ReadonlyArray<DebugRefreshReasonBase>
@@ -135,7 +135,7 @@ const unknownRefreshReason = () => {
135
135
 
136
136
  export type SerializedAtom = Readonly<
137
137
  PrettifyFlat<
138
- Pick<Atom<unknown, unknown, any>, '_tag' | 'id' | 'label' | 'meta'> & {
138
+ Pick<Atom<unknown, unknown, any>, '_tag' | 'id' | 'label' | 'meta' | 'isDirty'> & {
139
139
  sub: ReadonlyArray<string>
140
140
  super: ReadonlyArray<string>
141
141
  }
@@ -162,17 +162,6 @@ const uniqueNodeId = () => `node-${++nodeIdCounter}`
162
162
  let refreshInfoIdCounter = 0
163
163
  const uniqueRefreshInfoId = () => `refresh-info-${++refreshInfoIdCounter}`
164
164
 
165
- const serializeAtom = (atom: Atom<any, unknown, any>): SerializedAtom => ({
166
- ...pick(atom, ['_tag', 'id', 'label', 'meta', 'isDirty']),
167
- sub: Array.from(atom.sub).map((a) => a.id),
168
- super: Array.from(atom.super).map((a) => a.id),
169
- })
170
-
171
- const serializeEffect = (effect: Effect): SerializedEffect => ({
172
- ...pick(effect, ['_tag', 'id', 'label']),
173
- sub: Array.from(effect.sub).map((a) => a.id),
174
- })
175
-
176
165
  let globalGraphIdCounter = 0
177
166
  const uniqueGraphId = () => `graph-${++globalGraphIdCounter}`
178
167
 
@@ -515,11 +504,25 @@ export class ReactiveGraph<
515
504
  subComp.super.delete(superComp)
516
505
  }
517
506
 
518
- getSnapshot = (): ReactiveGraphSnapshot => ({
519
- atoms: Array.from(this.atoms).map(serializeAtom),
520
- effects: Array.from(this.effects).map(serializeEffect),
521
- deferredEffects: Array.from(this.deferredEffects.keys()).map((_) => _.id),
522
- })
507
+ // NOTE This function is performance-optimized (i.e. not using `Array.from`)
508
+ getSnapshot = (): ReactiveGraphSnapshot => {
509
+ const atoms: SerializedAtom[] = []
510
+ for (const atom of this.atoms) {
511
+ atoms.push(serializeAtom(atom))
512
+ }
513
+
514
+ const effects: SerializedEffect[] = []
515
+ for (const effect of this.effects) {
516
+ effects.push(serializeEffect(effect))
517
+ }
518
+
519
+ const deferredEffects: string[] = []
520
+ for (const [effect] of this.deferredEffects) {
521
+ deferredEffects.push(effect.id)
522
+ }
523
+
524
+ return { atoms, effects, deferredEffects }
525
+ }
523
526
 
524
527
  subscribeToRefresh = (cb: () => void) => {
525
528
  this.refreshCallbacks.add(cb)
@@ -561,3 +564,41 @@ const markSuperCompDirtyRec = <T>(atom: Atom<T, unknown, any>, effectsToRefresh:
561
564
  export const throwContextNotSetError = (graph: ReactiveGraph<any, any, any>): never => {
562
565
  throw new Error(`LiveStore Error: \`context\` not set on ReactiveGraph (${graph.id})`)
563
566
  }
567
+
568
+ // NOTE This function is performance-optimized (i.e. not using `pick` and `Array.from`)
569
+ const serializeAtom = (atom: Atom<any, unknown, any>): SerializedAtom => {
570
+ const sub: string[] = []
571
+ for (const a of atom.sub) {
572
+ sub.push(a.id)
573
+ }
574
+
575
+ const super_: string[] = []
576
+ for (const a of atom.super) {
577
+ super_.push(a.id)
578
+ }
579
+
580
+ return {
581
+ _tag: atom._tag,
582
+ id: atom.id,
583
+ label: atom.label,
584
+ meta: atom.meta,
585
+ isDirty: atom.isDirty,
586
+ sub,
587
+ super: super_,
588
+ }
589
+ }
590
+
591
+ // NOTE This function is performance-optimized (i.e. not using `pick` and `Array.from`)
592
+ const serializeEffect = (effect: Effect): SerializedEffect => {
593
+ const sub: string[] = []
594
+ for (const a of effect.sub) {
595
+ sub.push(a.id)
596
+ }
597
+
598
+ return {
599
+ _tag: effect._tag,
600
+ id: effect.id,
601
+ label: effect.label,
602
+ sub,
603
+ }
604
+ }