@livestore/livestore 0.0.41 → 0.0.42-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 (146) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/{inMemoryDatabase.d.ts → MainDatabaseWrapper.d.ts} +11 -18
  3. package/dist/MainDatabaseWrapper.d.ts.map +1 -0
  4. package/dist/{inMemoryDatabase.js → MainDatabaseWrapper.js} +26 -72
  5. package/dist/MainDatabaseWrapper.js.map +1 -0
  6. package/dist/__tests__/react/fixture.d.ts +41 -40
  7. package/dist/__tests__/react/fixture.d.ts.map +1 -1
  8. package/dist/__tests__/react/fixture.js +2 -8
  9. package/dist/__tests__/react/fixture.js.map +1 -1
  10. package/dist/cud.d.ts +9 -9
  11. package/dist/cud.d.ts.map +1 -1
  12. package/dist/cud.js +7 -8
  13. package/dist/cud.js.map +1 -1
  14. package/dist/effect/LiveStore.d.ts +10 -10
  15. package/dist/effect/LiveStore.d.ts.map +1 -1
  16. package/dist/effect/LiveStore.js +2 -11
  17. package/dist/effect/LiveStore.js.map +1 -1
  18. package/dist/global-state.d.ts +2 -2
  19. package/dist/global-state.d.ts.map +1 -1
  20. package/dist/global-state.js.map +1 -1
  21. package/dist/index.d.ts +6 -6
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +4 -3
  24. package/dist/index.js.map +1 -1
  25. package/dist/migrations.d.ts +5 -5
  26. package/dist/migrations.d.ts.map +1 -1
  27. package/dist/migrations.js +37 -21
  28. package/dist/migrations.js.map +1 -1
  29. package/dist/query-info.d.ts +8 -9
  30. package/dist/query-info.d.ts.map +1 -1
  31. package/dist/query-info.js +1 -1
  32. package/dist/query-info.js.map +1 -1
  33. package/dist/react/LiveStoreProvider.d.ts +7 -7
  34. package/dist/react/LiveStoreProvider.d.ts.map +1 -1
  35. package/dist/react/LiveStoreProvider.js +11 -19
  36. package/dist/react/LiveStoreProvider.js.map +1 -1
  37. package/dist/react/useRow.d.ts +6 -6
  38. package/dist/react/useRow.d.ts.map +1 -1
  39. package/dist/react/useRow.js +2 -2
  40. package/dist/react/useRow.js.map +1 -1
  41. package/dist/reactiveQueries/sql.js +2 -2
  42. package/dist/reactiveQueries/sql.js.map +1 -1
  43. package/dist/reactiveQueries/sql.test.js +12 -31
  44. package/dist/reactiveQueries/sql.test.js.map +1 -1
  45. package/dist/row-query.d.ts +6 -6
  46. package/dist/row-query.d.ts.map +1 -1
  47. package/dist/row-query.js +9 -12
  48. package/dist/row-query.js.map +1 -1
  49. package/dist/store.d.ts +18 -18
  50. package/dist/store.d.ts.map +1 -1
  51. package/dist/store.js +80 -86
  52. package/dist/store.js.map +1 -1
  53. package/dist/utils/bounded-collections.d.ts +1 -1
  54. package/dist/utils/bounded-collections.d.ts.map +1 -1
  55. package/dist/utils/bounded-collections.js +2 -1
  56. package/dist/utils/bounded-collections.js.map +1 -1
  57. package/dist/utils/util.d.ts +0 -16
  58. package/dist/utils/util.d.ts.map +1 -1
  59. package/dist/utils/util.js +0 -38
  60. package/dist/utils/util.js.map +1 -1
  61. package/package.json +8 -44
  62. package/src/{inMemoryDatabase.ts → MainDatabaseWrapper.ts} +40 -94
  63. package/src/__tests__/react/fixture.tsx +3 -9
  64. package/src/cud.ts +17 -16
  65. package/src/effect/LiveStore.ts +12 -24
  66. package/src/global-state.ts +3 -2
  67. package/src/index.ts +7 -23
  68. package/src/migrations.ts +51 -34
  69. package/src/query-info.ts +10 -9
  70. package/src/react/LiveStoreProvider.tsx +19 -27
  71. package/src/react/useRow.ts +24 -12
  72. package/src/reactiveQueries/sql.test.ts +12 -31
  73. package/src/reactiveQueries/sql.ts +2 -2
  74. package/src/row-query.ts +32 -29
  75. package/src/store.ts +98 -103
  76. package/src/utils/bounded-collections.ts +3 -2
  77. package/src/utils/util.ts +0 -44
  78. package/tsconfig.json +1 -1
  79. package/vitest.config.js +4 -0
  80. package/dist/inMemoryDatabase.d.ts.map +0 -1
  81. package/dist/inMemoryDatabase.js.map +0 -1
  82. package/dist/schema/index.d.ts +0 -42
  83. package/dist/schema/index.d.ts.map +0 -1
  84. package/dist/schema/index.js +0 -42
  85. package/dist/schema/index.js.map +0 -1
  86. package/dist/schema/mutations.d.ts +0 -81
  87. package/dist/schema/mutations.d.ts.map +0 -1
  88. package/dist/schema/mutations.js +0 -29
  89. package/dist/schema/mutations.js.map +0 -1
  90. package/dist/schema/parse-utils.d.ts +0 -6
  91. package/dist/schema/parse-utils.d.ts.map +0 -1
  92. package/dist/schema/parse-utils.js +0 -22
  93. package/dist/schema/parse-utils.js.map +0 -1
  94. package/dist/schema/system-tables.d.ts +0 -76
  95. package/dist/schema/system-tables.d.ts.map +0 -1
  96. package/dist/schema/system-tables.js +0 -11
  97. package/dist/schema/system-tables.js.map +0 -1
  98. package/dist/schema/table-def.d.ts +0 -100
  99. package/dist/schema/table-def.d.ts.map +0 -1
  100. package/dist/schema/table-def.js +0 -70
  101. package/dist/schema/table-def.js.map +0 -1
  102. package/dist/storage/in-memory/index.d.ts +0 -19
  103. package/dist/storage/in-memory/index.d.ts.map +0 -1
  104. package/dist/storage/in-memory/index.js +0 -16
  105. package/dist/storage/in-memory/index.js.map +0 -1
  106. package/dist/storage/index.d.ts +0 -18
  107. package/dist/storage/index.d.ts.map +0 -1
  108. package/dist/storage/index.js +0 -9
  109. package/dist/storage/index.js.map +0 -1
  110. package/dist/storage/tauri/index.d.ts +0 -23
  111. package/dist/storage/tauri/index.d.ts.map +0 -1
  112. package/dist/storage/tauri/index.js +0 -46
  113. package/dist/storage/tauri/index.js.map +0 -1
  114. package/dist/storage/utils/idb.d.ts +0 -11
  115. package/dist/storage/utils/idb.d.ts.map +0 -1
  116. package/dist/storage/utils/idb.js +0 -71
  117. package/dist/storage/utils/idb.js.map +0 -1
  118. package/dist/storage/web-worker/common.d.ts +0 -11
  119. package/dist/storage/web-worker/common.d.ts.map +0 -1
  120. package/dist/storage/web-worker/common.js +0 -2
  121. package/dist/storage/web-worker/common.js.map +0 -1
  122. package/dist/storage/web-worker/index.d.ts +0 -34
  123. package/dist/storage/web-worker/index.d.ts.map +0 -1
  124. package/dist/storage/web-worker/index.js +0 -134
  125. package/dist/storage/web-worker/index.js.map +0 -1
  126. package/dist/storage/web-worker/make-worker.d.ts +0 -20
  127. package/dist/storage/web-worker/make-worker.d.ts.map +0 -1
  128. package/dist/storage/web-worker/make-worker.js +0 -155
  129. package/dist/storage/web-worker/make-worker.js.map +0 -1
  130. package/dist/storage/web-worker/vite-dev-polyfill.d.ts +0 -2
  131. package/dist/storage/web-worker/vite-dev-polyfill.d.ts.map +0 -1
  132. package/dist/storage/web-worker/vite-dev-polyfill.js +0 -35
  133. package/dist/storage/web-worker/vite-dev-polyfill.js.map +0 -1
  134. package/src/schema/index.ts +0 -100
  135. package/src/schema/mutations.ts +0 -128
  136. package/src/schema/parse-utils.ts +0 -42
  137. package/src/schema/system-tables.ts +0 -21
  138. package/src/schema/table-def.ts +0 -270
  139. package/src/storage/in-memory/index.ts +0 -28
  140. package/src/storage/index.ts +0 -36
  141. package/src/storage/tauri/index.ts +0 -66
  142. package/src/storage/utils/idb.ts +0 -85
  143. package/src/storage/web-worker/common.ts +0 -6
  144. package/src/storage/web-worker/index.ts +0 -185
  145. package/src/storage/web-worker/make-worker.ts +0 -214
  146. package/src/storage/web-worker/vite-dev-polyfill.ts +0 -33
@@ -1,17 +1,13 @@
1
1
  /* eslint-disable prefer-arrow/prefer-arrow-functions */
2
2
 
3
+ import { type MainDatabase, type PreparedStatement, sql } from '@livestore/common'
3
4
  import { shouldNeverHappen } from '@livestore/utils'
4
5
  import type * as otel from '@opentelemetry/api'
5
- import type * as Sqlite from 'sqlite-esm'
6
6
 
7
- // import { EVENTS_TABLE_NAME } from './events.js'
8
- import { sql } from './index.js'
9
7
  import QueryCache from './QueryCache.js'
10
8
  import BoundMap, { BoundArray } from './utils/bounded-collections.js'
11
9
  import { getDurationMsFromSpan, getStartTimeHighResFromSpan } from './utils/otel.js'
12
- import type { Bindable, PreparedBindValues } from './utils/util.js'
13
-
14
- type DatabaseWithCAPI = Sqlite.Database & { capi: Sqlite.CAPI }
10
+ import { type Bindable, type PreparedBindValues } from './utils/util.js'
15
11
 
16
12
  export interface DebugInfo {
17
13
  slowQueries: BoundArray<SlowQueryInfo>
@@ -36,65 +32,42 @@ export const emptyDebugInfo = (): DebugInfo => ({
36
32
  events: new BoundArray(1000),
37
33
  })
38
34
 
39
- export class InMemoryDatabase {
35
+ export class MainDatabaseWrapper {
40
36
  // TODO: how many unique active statements are expected?
41
- private cachedStmts = new BoundMap<string, Sqlite.PreparedStatement>(200)
37
+ private cachedStmts = new BoundMap<string, PreparedStatement>(200)
42
38
  private tablesUsedCache = new BoundMap<string, Set<string>>(200)
43
39
  private resultCache = new QueryCache()
40
+ private db: MainDatabase
41
+ private otelTracer: otel.Tracer
42
+ private otelRootSpanContext: otel.Context
44
43
  private tablesUsedStmt
45
44
  public debugInfo: DebugInfo = emptyDebugInfo()
46
45
 
47
- constructor(
48
- private db: DatabaseWithCAPI,
49
- private otelTracer: otel.Tracer,
50
- private otelRootSpanContext: otel.Context,
51
- public SQL: Sqlite.Sqlite3Static,
52
- ) {
53
- this.tablesUsedStmt = this.db.prepare(
54
- `SELECT tbl_name FROM tables_used(?) AS u JOIN sqlite_master ON sqlite_master.name = u.name WHERE u.schema = 'main';`,
55
- )
56
- }
57
-
58
- static load({
59
- data,
46
+ constructor({
47
+ db,
60
48
  otelTracer,
61
49
  otelRootSpanContext,
62
- sqlite3,
63
50
  }: {
64
- data: Uint8Array | undefined
51
+ db: MainDatabase
65
52
  otelTracer: otel.Tracer
66
53
  otelRootSpanContext: otel.Context
67
- sqlite3: Sqlite.Sqlite3Static
68
- }): InMemoryDatabase {
69
- // TODO move WASM init higher up in the init process (to do some other work while it's loading)
70
-
71
- const db = new sqlite3.oo1.DB({ filename: ':memory:', flags: 'c' }) as DatabaseWithCAPI
72
- db.capi = sqlite3.capi
73
-
74
- if (data !== undefined) {
75
- // Based on https://sqlite.org/forum/forumpost/2119230da8ac5357a13b731f462dc76e08621a4a29724f7906d5f35bb8508465
76
- // TODO find cleaner way to do this once possible in sqlite3-wasm
77
- const bytes = data
78
- const p = sqlite3.wasm.allocFromTypedArray(bytes)
79
- const _rc = sqlite3.capi.sqlite3_deserialize(
80
- db.pointer,
81
- 'main',
82
- p,
83
- bytes.length,
84
- bytes.length,
85
- sqlite3.capi.SQLITE_DESERIALIZE_FREEONCLOSE && sqlite3.capi.SQLITE_DESERIALIZE_RESIZEABLE,
86
- )
87
- }
54
+ }) {
55
+ this.db = db
56
+ this.otelTracer = otelTracer
57
+ this.otelRootSpanContext = otelRootSpanContext
88
58
 
89
- const inMemoryDatabase = new InMemoryDatabase(db, otelTracer, otelRootSpanContext, sqlite3)
59
+ this.tablesUsedStmt = db.prepare(
60
+ `SELECT tbl_name FROM tables_used(?) AS u JOIN sqlite_master ON sqlite_master.name = u.name WHERE u.schema = 'main';`,
61
+ )
90
62
 
91
- configureSQLite(inMemoryDatabase)
63
+ this.cachedStmts.onEvict = (_queryStr, stmt) => stmt.finalize()
92
64
 
93
- return inMemoryDatabase
65
+ configureSQLite(this)
94
66
  }
95
67
 
96
68
  txn<TRes>(callback: () => TRes): TRes {
97
69
  this.execute(sql`begin transaction;`)
70
+
98
71
  let errored = false
99
72
  let result: TRes
100
73
 
@@ -114,6 +87,13 @@ export class InMemoryDatabase {
114
87
  }
115
88
 
116
89
  getTablesUsed(query: string) {
90
+ // It seems that SQLite doesn't properly handle `DELETE FROM SOME_TABLE` queries without a WHERE clause
91
+ // So we need to handle these queries separately
92
+ const tableNameFromPlainDeleteQuery = tryGetTableNameFromPlainDeleteQuery(query)
93
+ if (tableNameFromPlainDeleteQuery !== undefined) {
94
+ return new Set<string>([tableNameFromPlainDeleteQuery])
95
+ }
96
+
117
97
  const cached = this.tablesUsedCache.get(query)
118
98
  if (cached) {
119
99
  return cached
@@ -121,15 +101,14 @@ export class InMemoryDatabase {
121
101
  const stmt = this.tablesUsedStmt
122
102
  const tablesUsed = new Set<string>()
123
103
  try {
124
- stmt.bind([query])
125
- while (stmt.step()) {
126
- tablesUsed.add(stmt.get(0))
104
+ const results = stmt.select<{ tbl_name: string }>([query] as unknown as PreparedBindValues)
105
+
106
+ for (const row of results) {
107
+ tablesUsed.add(row.tbl_name)
127
108
  }
128
109
  } catch (e) {
129
110
  console.error('Error getting tables used', e, 'for query', query)
130
111
  return new Set<string>()
131
- } finally {
132
- stmt.reset()
133
112
  }
134
113
  this.tablesUsedCache.set(query, tablesUsed)
135
114
  return tablesUsed
@@ -156,19 +135,7 @@ export class InMemoryDatabase {
156
135
  this.cachedStmts.set(query, stmt)
157
136
  }
158
137
 
159
- if (bindValues !== undefined && Object.keys(bindValues).length > 0) {
160
- stmt.bind(bindValues)
161
- }
162
-
163
- if (import.meta.env.DEV) {
164
- this.debugInfo.events.push([query, bindValues])
165
- }
166
-
167
- try {
168
- stmt.step()
169
- } finally {
170
- stmt.reset() // Reset is needed for next execution
171
- }
138
+ stmt.execute(bindValues)
172
139
  } catch (error) {
173
140
  shouldNeverHappen(`Error executing query: ${error} \n ${JSON.stringify({ query, bindValues })}`)
174
141
  }
@@ -237,34 +204,8 @@ export class InMemoryDatabase {
237
204
  stmt = this.db.prepare(query)
238
205
  this.cachedStmts.set(query, stmt)
239
206
  }
240
- if (bindValues !== undefined && Object.keys(bindValues).length > 0) {
241
- stmt.bind(bindValues)
242
- }
243
207
 
244
- const result: T[] = []
245
-
246
- try {
247
- // NOTE `getColumnNames` only works for `SELECT` statements, ignoring other statements for now
248
- let columns = undefined
249
- try {
250
- columns = stmt.getColumnNames()
251
- } catch (_e) {}
252
-
253
- while (stmt.step()) {
254
- if (columns !== undefined) {
255
- const obj: { [key: string]: any } = {}
256
- for (const [i, c] of columns.entries()) {
257
- obj[c] = stmt.get(i)
258
- }
259
- result.push(obj as unknown as T)
260
- }
261
- }
262
- } finally {
263
- // we're caching statements in this iteration. do not free.
264
- // stmt.free();
265
- // reset the cached statement so we can use it again in the future
266
- stmt.reset()
267
- }
208
+ const result = stmt.select<T>(bindValues)
268
209
 
269
210
  span.setAttribute('sql.rowsCount', result.length)
270
211
  span.setAttribute('sql.cached', false)
@@ -308,12 +249,12 @@ export class InMemoryDatabase {
308
249
  this.cachedStmts.delete(key)
309
250
  }
310
251
 
311
- return this.db.capi.sqlite3_js_db_export(this.db.pointer)
252
+ return this.db.export()
312
253
  }
313
254
  }
314
255
 
315
256
  /** Set up SQLite performance; hasn't been super carefully optimized yet. */
316
- const configureSQLite = (db: InMemoryDatabase) => {
257
+ const configureSQLite = (db: MainDatabaseWrapper) => {
317
258
  db.execute(
318
259
  // TODO: revisit these tuning parameters for max performance
319
260
  sql`
@@ -326,3 +267,8 @@ const configureSQLite = (db: InMemoryDatabase) => {
326
267
  `,
327
268
  )
328
269
  }
270
+
271
+ const tryGetTableNameFromPlainDeleteQuery = (query: string) => {
272
+ const [_, tableName] = query.trim().match(/^delete\s+from\s+(\w+)$/i) ?? []
273
+ return tableName
274
+ }
@@ -1,12 +1,12 @@
1
+ import { Schema as __Schema } from '@livestore/utils/effect'
2
+ import { makeDb } from '@livestore/web'
1
3
  import type * as otel from '@opentelemetry/api'
2
4
  import React from 'react'
3
- import initSqlite3Wasm from 'sqlite-esm'
4
5
 
5
6
  import { globalDbGraph } from '../../global-state.js'
6
7
  import type { LiveStoreContext } from '../../index.js'
7
8
  import { createStore, DbSchema, makeCudMutations, makeDbGraph, makeSchema, ParseUtils, sql } from '../../index.js'
8
9
  import * as LiveStoreReact from '../../react/index.js'
9
- import { InMemoryStorage } from '../../storage/in-memory/index.js'
10
10
 
11
11
  export type Todo = {
12
12
  id: string
@@ -51,18 +51,12 @@ export const makeTodoMvc = async ({
51
51
  username: DbSchema.text({ default: '' }),
52
52
  })
53
53
 
54
- const sqlite3 = await initSqlite3Wasm({
55
- print: (message) => console.log(`[livestore sqlite] ${message}`),
56
- printErr: (message) => console.error(`[livestore sqlite] ${message}`),
57
- })
58
-
59
54
  const dbGraph = useGlobalDbGraph ? globalDbGraph : makeDbGraph()
60
55
 
61
56
  const store = await createStore({
62
57
  schema,
63
- loadStorage: () => InMemoryStorage.load(),
64
58
  boot: (db) => db.execute(sql`INSERT OR IGNORE INTO app (id, newTodoText, filter) VALUES ('static', '', 'all');`),
65
- sqlite3,
59
+ makeDb: makeDb(),
66
60
  dbGraph,
67
61
  otelTracer,
68
62
  otelRootSpanContext: otelContext,
package/src/cud.ts CHANGED
@@ -1,12 +1,13 @@
1
- import * as SqlQueries from '@livestore/sql-queries'
1
+ import { deleteRows, insertRow, updateRows } from '@livestore/common'
2
+ import type { RawSqlMutationEvent } from '@livestore/common/schema'
3
+ import { DbSchema, rawSqlMutation } from '@livestore/common/schema'
4
+ import { isIterable } from '@livestore/utils'
2
5
  import type { SqliteDsl } from 'effect-db-schema'
3
6
 
4
7
  import type { RowResult } from './row-query.js'
5
- import { rawSqlMutation, type RawSqlMutationEvent } from './schema/index.js'
6
- import { getDefaultValuesEncoded, type TableDef } from './schema/table-def.js'
7
- import { type GetValForKey, isIterable } from './utils/util.js'
8
+ import { type GetValForKey } from './utils/util.js'
8
9
 
9
- export const makeCudMutations = <TTableDef extends TableDef>(
10
+ export const makeCudMutations = <TTableDef extends DbSchema.TableDef>(
10
11
  tables: Iterable<TTableDef> | Record<string, TTableDef>,
11
12
  ): CudMutations<TTableDef> => {
12
13
  const cudMutationRecord: CudMutations<TTableDef> = {} as any
@@ -21,16 +22,16 @@ export const makeCudMutations = <TTableDef extends TableDef>(
21
22
  return cudMutationRecord
22
23
  }
23
24
 
24
- const cudMutationsForTable = <TTableDef extends TableDef>(
25
+ const cudMutationsForTable = <TTableDef extends DbSchema.TableDef>(
25
26
  tableDef: TTableDef,
26
27
  ): [TTableDef['sqliteDef']['name'], CudMutation<TTableDef>] => {
27
28
  const table = tableDef.sqliteDef
28
29
  const writeTables = new Set([table.name])
29
30
  const api = {
30
31
  insert: (values_: any) => {
31
- const values = getDefaultValuesEncoded(tableDef, values_)
32
+ const values = DbSchema.getDefaultValuesDecoded(tableDef, values_)
32
33
 
33
- const [sql, bindValues] = SqlQueries.insertRow({
34
+ const [sql, bindValues] = insertRow({
34
35
  tableName: table.name,
35
36
  columns: table.columns,
36
37
  options: { orReplace: false },
@@ -39,7 +40,7 @@ const cudMutationsForTable = <TTableDef extends TableDef>(
39
40
  return rawSqlMutation({ sql, bindValues, writeTables })
40
41
  },
41
42
  update: ({ where, values }) => {
42
- const [sql, bindValues] = SqlQueries.updateRows({
43
+ const [sql, bindValues] = updateRows({
43
44
  tableName: table.name,
44
45
  columns: table.columns,
45
46
  where: where,
@@ -48,7 +49,7 @@ const cudMutationsForTable = <TTableDef extends TableDef>(
48
49
  return rawSqlMutation({ sql, bindValues, writeTables })
49
50
  },
50
51
  delete: ({ where }) => {
51
- const [sql, bindValues] = SqlQueries.deleteRows({
52
+ const [sql, bindValues] = deleteRows({
52
53
  tableName: table.name,
53
54
  columns: table.columns,
54
55
  where: where,
@@ -60,28 +61,28 @@ const cudMutationsForTable = <TTableDef extends TableDef>(
60
61
  return [tableDef.sqliteDef.name, api]
61
62
  }
62
63
 
63
- export type UpdateMutation<TTableDef extends TableDef> = (args: {
64
+ export type UpdateMutation<TTableDef extends DbSchema.TableDef> = (args: {
64
65
  // TODO also allow `id` if present in `TTableDef`
65
66
  where: Partial<RowResult<TTableDef>>
66
67
  values: Partial<RowResult<TTableDef>>
67
68
  }) => RawSqlMutationEvent
68
69
 
69
- export type RowInsert<TTableDef extends TableDef> = TTableDef['isSingleColumn'] extends true
70
+ export type RowInsert<TTableDef extends DbSchema.TableDef> = TTableDef['isSingleColumn'] extends true
70
71
  ? GetValForKey<SqliteDsl.FromColumns.InsertRowDecoded<TTableDef['sqliteDef']['columns']>, 'value'>
71
72
  : SqliteDsl.FromColumns.InsertRowDecoded<TTableDef['sqliteDef']['columns']>
72
73
 
73
- export type InsertMutation<TTableDef extends TableDef> = (values: RowInsert<TTableDef>) => RawSqlMutationEvent
74
+ export type InsertMutation<TTableDef extends DbSchema.TableDef> = (values: RowInsert<TTableDef>) => RawSqlMutationEvent
74
75
 
75
- export type DeleteMutation<TTableDef extends TableDef> = (args: {
76
+ export type DeleteMutation<TTableDef extends DbSchema.TableDef> = (args: {
76
77
  where: Partial<RowResult<TTableDef>>
77
78
  }) => RawSqlMutationEvent
78
79
 
79
- export type CudMutation<TTableDef extends TableDef> = {
80
+ export type CudMutation<TTableDef extends DbSchema.TableDef> = {
80
81
  insert: InsertMutation<TTableDef>
81
82
  update: UpdateMutation<TTableDef>
82
83
  delete: DeleteMutation<TTableDef>
83
84
  }
84
85
 
85
- export type CudMutations<TTableDef extends TableDef> = {
86
+ export type CudMutations<TTableDef extends DbSchema.TableDef> = {
86
87
  [TTableName in TTableDef['sqliteDef']['name']]: CudMutation<Extract<TTableDef, { sqliteDef: { name: TTableName } }>>
87
88
  }
@@ -1,23 +1,14 @@
1
+ import type { DatabaseFactory, MainDatabase } from '@livestore/common'
2
+ import type { LiveStoreSchema } from '@livestore/common/schema'
1
3
  import type { Scope } from '@livestore/utils/effect'
2
4
  import { Context, Deferred, Duration, Effect, Layer, OtelTracer, pipe, Runtime } from '@livestore/utils/effect'
3
5
  import * as otel from '@opentelemetry/api'
4
6
  import type { GraphQLSchema } from 'graphql'
5
- import initSqlite3Wasm from 'sqlite-esm'
6
7
 
7
- import type { InMemoryDatabase } from '../inMemoryDatabase.js'
8
8
  import type { LiveQuery } from '../reactiveQueries/base-class.js'
9
- import type { LiveStoreSchema } from '../schema/index.js'
10
- import type { StorageInit } from '../storage/index.js'
11
- import type { BaseGraphQLContext, GraphQLOptions, Store } from '../store.js'
9
+ import type { BaseGraphQLContext, BootDb, GraphQLOptions, Store } from '../store.js'
12
10
  import { createStore } from '../store.js'
13
11
 
14
- // NOTE we're starting to initialize the sqlite wasm binary here (already before calling `createStore`),
15
- // so that it's ready when we need it
16
- const sqlite3Promise = initSqlite3Wasm({
17
- print: (message) => console.log(`[livestore sqlite] ${message}`),
18
- printErr: (message) => console.error(`[livestore sqlite] ${message}`),
19
- })
20
-
21
12
  // TODO get rid of `LiveStoreContext` wrapper and only expose the `Store` directly
22
13
  export type LiveStoreContext = {
23
14
  store: Store
@@ -27,11 +18,12 @@ export type QueryDefinition = <TResult>(store: Store) => LiveQuery<TResult>
27
18
 
28
19
  export type LiveStoreCreateStoreOptions<GraphQLContext extends BaseGraphQLContext> = {
29
20
  schema: LiveStoreSchema
30
- loadStorage: () => StorageInit | Promise<StorageInit>
31
21
  graphQLOptions?: GraphQLOptions<GraphQLContext>
32
22
  otelTracer?: otel.Tracer
33
23
  otelRootSpanContext?: otel.Context
34
- boot?: (db: InMemoryDatabase, parentSpan: otel.Span) => unknown | Promise<unknown>
24
+ boot?: (db: BootDb, parentSpan: otel.Span) => unknown | Promise<unknown>
25
+ makeDb: DatabaseFactory
26
+ batchUpdates?: (run: () => void) => void
35
27
  }
36
28
 
37
29
  export const LiveStoreContext = Context.GenericTag<LiveStoreContext>('@livestore/livestore/LiveStoreContext')
@@ -45,12 +37,12 @@ export const DeferredStoreContext = Context.GenericTag<DeferredStoreContext>(
45
37
 
46
38
  export type LiveStoreContextProps<GraphQLContext extends BaseGraphQLContext> = {
47
39
  schema: LiveStoreSchema
48
- loadStorage: () => StorageInit | Promise<StorageInit>
49
40
  graphQLOptions?: {
50
41
  schema: Effect.Effect<GraphQLSchema, never, otel.Tracer>
51
- makeContext: (db: InMemoryDatabase) => GraphQLContext
42
+ makeContext: (db: MainDatabase) => GraphQLContext
52
43
  }
53
- boot?: (db: InMemoryDatabase) => Effect.Effect<void>
44
+ boot?: (db: BootDb) => Effect.Effect<void>
45
+ makeDb: DatabaseFactory
54
46
  }
55
47
 
56
48
  export const LiveStoreContextLayer = <GraphQLContext extends BaseGraphQLContext>(
@@ -65,9 +57,9 @@ export const LiveStoreContextDeferred = Layer.effect(DeferredStoreContext, Defer
65
57
 
66
58
  export const makeLiveStoreContext = <GraphQLContext extends BaseGraphQLContext>({
67
59
  schema,
68
- loadStorage,
69
60
  graphQLOptions: graphQLOptions_,
70
61
  boot: boot_,
62
+ makeDb,
71
63
  }: LiveStoreContextProps<GraphQLContext>): Effect.Effect<
72
64
  LiveStoreContext,
73
65
  never,
@@ -88,22 +80,18 @@ export const makeLiveStoreContext = <GraphQLContext extends BaseGraphQLContext>(
88
80
  )
89
81
 
90
82
  const boot = boot_
91
- ? (db: InMemoryDatabase) =>
92
- boot_(db).pipe(Effect.withSpan('boot'), Effect.tapCauseLogPretty, Runtime.runPromise(runtime))
83
+ ? (db: BootDb) => boot_(db).pipe(Effect.withSpan('boot'), Effect.tapCauseLogPretty, Runtime.runPromise(runtime))
93
84
  : undefined
94
85
 
95
- const sqlite3 = yield* $(Effect.promise(() => sqlite3Promise))
96
-
97
86
  const store = yield* $(
98
87
  Effect.tryPromise(() =>
99
88
  createStore({
100
89
  schema,
101
- loadStorage,
102
90
  graphQLOptions,
103
91
  otelTracer,
104
92
  otelRootSpanContext,
105
93
  boot,
106
- sqlite3,
94
+ makeDb,
107
95
  }),
108
96
  ),
109
97
  Effect.acquireRelease((store) => Effect.sync(() => store.destroy())),
@@ -11,9 +11,10 @@
11
11
  *
12
12
  */
13
13
 
14
+ import type { DbSchema } from '@livestore/common/schema'
15
+
14
16
  import { makeDbGraph } from './reactiveQueries/base-class.js'
15
- import type { TableDef } from './schema/table-def.js'
16
17
 
17
18
  export const globalDbGraph = makeDbGraph()
18
19
 
19
- export const dynamicallyRegisteredTables: Map<string, TableDef> = new Map()
20
+ export const dynamicallyRegisteredTables: Map<string, DbSchema.TableDef> = new Map()
package/src/index.ts CHANGED
@@ -1,11 +1,9 @@
1
1
  export { Store, createStore } from './store.js'
2
- export type { BaseGraphQLContext, QueryDebugInfo, RefreshReason } from './store.js'
2
+ export type { BaseGraphQLContext, QueryDebugInfo, RefreshReason, BootDb } from './store.js'
3
3
 
4
4
  export type { QueryDefinition, LiveStoreCreateStoreOptions, LiveStoreContext } from './effect/LiveStore.js'
5
5
 
6
- export { InMemoryDatabase, type DebugInfo, emptyDebugInfo } from './inMemoryDatabase.js'
7
-
8
- export type { Storage, StorageType, StorageInit } from './storage/index.js'
6
+ export { MainDatabaseWrapper as InMemoryDatabase, type DebugInfo, emptyDebugInfo } from './MainDatabaseWrapper.js'
9
7
 
10
8
  export type { GetAtom, AtomDebugInfo, RefreshDebugInfo, SerializedAtom, Atom, Node, Ref, Effect } from './reactive.js'
11
9
  export { LiveStoreJSQuery, computed } from './reactiveQueries/js.js'
@@ -19,26 +17,12 @@ export { type RowResult, type RowResultEncoded, rowQuery, deriveColQuery } from
19
17
 
20
18
  export * from './cud.js'
21
19
 
22
- export {
23
- makeSchema,
24
- DbSchema,
25
- ParseUtils,
26
- defineMutation,
27
- rawSqlMutation,
28
- makeMutationEventSchema,
29
- makeMutationDefRecord,
30
- } from './schema/index.js'
31
-
32
- export type {
33
- LiveStoreSchema,
34
- InputSchema,
35
- SchemaMetaRow,
36
- MutationDef,
37
- MutationEvent,
38
- MutationDefMap,
39
- } from './schema/index.js'
20
+ export * from '@livestore/common/schema'
21
+ export { sql } from '@livestore/common'
40
22
 
41
23
  export { SqliteAst, SqliteDsl } from 'effect-db-schema'
42
24
 
43
- export { prepareBindValues, sql, type Bindable, type PreparedBindValues } from './utils/util.js'
25
+ export { prepareBindValues, type Bindable, type PreparedBindValues } from './utils/util.js'
44
26
  export { isEqual } from 'lodash-es'
27
+
28
+ export type { DatabaseImpl, DatabaseFactory, PreparedStatement } from '@livestore/common'
package/src/migrations.ts CHANGED
@@ -1,36 +1,60 @@
1
+ import { type DatabaseImpl, sql } from '@livestore/common'
2
+ import type { LiveStoreSchema, SchemaMetaRow } from '@livestore/common/schema'
3
+ import { SCHEMA_META_TABLE, systemTables } from '@livestore/common/schema'
1
4
  import { Schema as EffectSchema } from '@livestore/utils/effect'
2
5
  import type * as otel from '@opentelemetry/api'
3
6
  import { SqliteAst, SqliteDsl } from 'effect-db-schema'
4
7
  import { memoize } from 'lodash-es'
5
8
 
6
- import { dynamicallyRegisteredTables } from './global-state.js'
7
- import type { InMemoryDatabase } from './index.js'
8
- import type { LiveStoreSchema, SchemaMetaRow } from './schema/index.js'
9
- import { SCHEMA_META_TABLE, systemTables } from './schema/index.js'
10
- import type { PreparedBindValues } from './utils/util.js'
11
- import { sql } from './utils/util.js'
9
+ import type { ParamsObject } from './utils/util.js'
10
+ import { prepareBindValues } from './utils/util.js'
12
11
 
13
12
  const getMemoizedTimestamp = memoize(() => new Date().toISOString())
14
13
 
14
+ // TODO bring back statement caching
15
+ // const cachedStmts = new Map<string, PreparedStatement>()
16
+
17
+ const dbExecute = (db: DatabaseImpl, queryStr: string, bindValues?: ParamsObject) => {
18
+ // let stmt = cachedStmts.get(queryStr)
19
+ // if (!stmt) {
20
+ const stmt = db.mainDb.prepare(queryStr)
21
+ // cachedStmts.set(queryStr, stmt)
22
+ // }
23
+
24
+ const preparedBindValues = bindValues ? prepareBindValues(bindValues, queryStr) : undefined
25
+
26
+ stmt.execute(preparedBindValues)
27
+
28
+ void db.storageDb.execute(queryStr, preparedBindValues, undefined)
29
+ }
30
+
31
+ const dbSelect = <T>(db: DatabaseImpl, queryStr: string, bindValues?: ParamsObject) => {
32
+ // let stmt = cachedStmts.get(queryStr)
33
+ // if (!stmt) {
34
+ const stmt = db.mainDb.prepare(queryStr)
35
+ // cachedStmts.set(queryStr, stmt)
36
+ // }
37
+
38
+ return stmt.select<T>(bindValues ? prepareBindValues(bindValues, queryStr) : undefined)
39
+ }
40
+
15
41
  // TODO more graceful DB migration (e.g. backup DB before destructive migrations)
16
42
  export const migrateDb = ({
17
43
  db,
18
44
  otelContext,
19
45
  schema,
20
46
  }: {
21
- db: InMemoryDatabase
47
+ db: DatabaseImpl
22
48
  otelContext: otel.Context
23
49
  schema: LiveStoreSchema
24
50
  }) => {
25
- db.execute(
51
+ dbExecute(
52
+ db,
26
53
  // TODO use schema migration definition from schema.ts instead
27
54
  sql`create table if not exists ${SCHEMA_META_TABLE} (tableName text primary key, schemaHash text, updatedAt text);`,
28
- undefined,
29
- new Set(),
30
- { otelContext },
31
55
  )
32
56
 
33
- const schemaMetaRows = db.select<SchemaMetaRow>(sql`SELECT * FROM ${SCHEMA_META_TABLE}`)
57
+ const schemaMetaRows = dbSelect<SchemaMetaRow>(db, sql`SELECT * FROM ${SCHEMA_META_TABLE}`)
34
58
 
35
59
  const dbSchemaHashByTable = Object.fromEntries(
36
60
  schemaMetaRows.map(({ tableName, schemaHash }) => [tableName, schemaHash]),
@@ -40,7 +64,6 @@ export const migrateDb = ({
40
64
  // NOTE it's important the `SCHEMA_META_TABLE` comes first since we're writing to it below
41
65
  ...systemTables,
42
66
  ...Array.from(schema.tables.values()).filter((_) => _.sqliteDef.name !== SCHEMA_META_TABLE),
43
- ...dynamicallyRegisteredTables.values(),
44
67
  ])
45
68
 
46
69
  for (const tableDef of tableDefs) {
@@ -48,18 +71,12 @@ export const migrateDb = ({
48
71
  const tableName = tableAst.name
49
72
  const dbSchemaHash = dbSchemaHashByTable[tableName]
50
73
  const schemaHash = SqliteAst.hash(tableAst)
51
- if (schemaHash !== dbSchemaHash) {
52
- if (import.meta.env.VITE_LIVESTORE_SKIP_MIGRATIONS) {
53
- console.log(
54
- `Schema hash mismatch for table '${tableName}' (DB: ${dbSchemaHash}, expected: ${schemaHash}), skipping migration...`,
55
- )
56
- } else {
57
- console.log(
58
- `Schema hash mismatch for table '${tableName}' (DB: ${dbSchemaHash}, expected: ${schemaHash}), migrating table...`,
59
- )
60
-
61
- migrateTable({ db, tableAst, otelContext, schemaHash })
62
- }
74
+ if (schemaHash !== dbSchemaHash && import.meta.env.VITE_LIVESTORE_SKIP_MIGRATIONS === undefined) {
75
+ console.log(
76
+ `Schema hash mismatch for table '${tableName}' (DB: ${dbSchemaHash}, expected: ${schemaHash}), migrating table...`,
77
+ )
78
+
79
+ migrateTable({ db, tableAst, otelContext, schemaHash })
63
80
  }
64
81
  }
65
82
  }
@@ -67,10 +84,10 @@ export const migrateDb = ({
67
84
  export const migrateTable = ({
68
85
  db,
69
86
  tableAst,
70
- otelContext,
87
+ // otelContext,
71
88
  schemaHash,
72
89
  }: {
73
- db: InMemoryDatabase
90
+ db: DatabaseImpl
74
91
  tableAst: SqliteAst.Table
75
92
  otelContext: otel.Context
76
93
  schemaHash: number
@@ -80,22 +97,22 @@ export const migrateTable = ({
80
97
  const columnSpec = makeColumnSpec(tableAst)
81
98
 
82
99
  // TODO need to possibly handle cascading deletes due to foreign keys
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 })
100
+ dbExecute(db, sql`drop table if exists ${tableName}`)
101
+ dbExecute(db, sql`create table if not exists ${tableName} (${columnSpec})`)
85
102
 
86
103
  for (const index of tableAst.indexes) {
87
- db.execute(createIndexFromDefinition(tableName, index), undefined, new Set(), { otelContext })
104
+ dbExecute(db, createIndexFromDefinition(tableName, index))
88
105
  }
89
106
 
90
107
  const updatedAt = getMemoizedTimestamp()
91
- db.execute(
108
+
109
+ dbExecute(
110
+ db,
92
111
  sql`
93
112
  INSERT INTO ${SCHEMA_META_TABLE} (tableName, schemaHash, updatedAt) VALUES ($tableName, $schemaHash, $updatedAt)
94
113
  ON CONFLICT (tableName) DO UPDATE SET schemaHash = $schemaHash, updatedAt = $updatedAt;
95
114
  `,
96
- { $tableName: tableName, $schemaHash: schemaHash, $updatedAt: updatedAt } as unknown as PreparedBindValues,
97
- new Set(),
98
- { otelContext },
115
+ { tableName, schemaHash, updatedAt },
99
116
  )
100
117
  }
101
118