@livestore/livestore 0.0.41-dev.2 → 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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/{inMemoryDatabase.d.ts → MainDatabaseWrapper.d.ts} +11 -18
- package/dist/MainDatabaseWrapper.d.ts.map +1 -0
- package/dist/{inMemoryDatabase.js → MainDatabaseWrapper.js} +26 -72
- package/dist/MainDatabaseWrapper.js.map +1 -0
- package/dist/__tests__/react/fixture.d.ts +41 -40
- package/dist/__tests__/react/fixture.d.ts.map +1 -1
- package/dist/__tests__/react/fixture.js +2 -8
- package/dist/__tests__/react/fixture.js.map +1 -1
- package/dist/cud.d.ts +9 -9
- package/dist/cud.d.ts.map +1 -1
- package/dist/cud.js +7 -8
- package/dist/cud.js.map +1 -1
- package/dist/effect/LiveStore.d.ts +10 -10
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +2 -11
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/global-state.d.ts +2 -2
- package/dist/global-state.d.ts.map +1 -1
- package/dist/global-state.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/migrations.d.ts +5 -5
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +37 -21
- package/dist/migrations.js.map +1 -1
- package/dist/query-info.d.ts +8 -9
- package/dist/query-info.d.ts.map +1 -1
- package/dist/query-info.js +1 -1
- package/dist/query-info.js.map +1 -1
- package/dist/react/LiveStoreProvider.d.ts +7 -7
- package/dist/react/LiveStoreProvider.d.ts.map +1 -1
- package/dist/react/LiveStoreProvider.js +11 -19
- package/dist/react/LiveStoreProvider.js.map +1 -1
- package/dist/react/useRow.d.ts +6 -6
- package/dist/react/useRow.d.ts.map +1 -1
- package/dist/react/useRow.js +2 -2
- package/dist/react/useRow.js.map +1 -1
- package/dist/reactiveQueries/sql.js +2 -2
- package/dist/reactiveQueries/sql.js.map +1 -1
- package/dist/reactiveQueries/sql.test.js +12 -31
- package/dist/reactiveQueries/sql.test.js.map +1 -1
- package/dist/row-query.d.ts +6 -6
- package/dist/row-query.d.ts.map +1 -1
- package/dist/row-query.js +9 -12
- package/dist/row-query.js.map +1 -1
- package/dist/store.d.ts +18 -18
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +80 -86
- package/dist/store.js.map +1 -1
- package/dist/utils/bounded-collections.d.ts +1 -1
- package/dist/utils/bounded-collections.d.ts.map +1 -1
- package/dist/utils/bounded-collections.js +2 -1
- package/dist/utils/bounded-collections.js.map +1 -1
- package/dist/utils/util.d.ts +0 -16
- package/dist/utils/util.d.ts.map +1 -1
- package/dist/utils/util.js +0 -38
- package/dist/utils/util.js.map +1 -1
- package/package.json +8 -44
- package/src/{inMemoryDatabase.ts → MainDatabaseWrapper.ts} +40 -94
- package/src/__tests__/react/fixture.tsx +3 -9
- package/src/cud.ts +17 -16
- package/src/effect/LiveStore.ts +12 -24
- package/src/global-state.ts +3 -2
- package/src/index.ts +7 -23
- package/src/migrations.ts +51 -34
- package/src/query-info.ts +10 -9
- package/src/react/LiveStoreProvider.tsx +19 -27
- package/src/react/useRow.ts +24 -12
- package/src/reactiveQueries/sql.test.ts +12 -31
- package/src/reactiveQueries/sql.ts +2 -2
- package/src/row-query.ts +32 -29
- package/src/store.ts +98 -103
- package/src/utils/bounded-collections.ts +3 -2
- package/src/utils/util.ts +0 -44
- package/tsconfig.json +1 -1
- package/vitest.config.js +4 -0
- package/dist/inMemoryDatabase.d.ts.map +0 -1
- package/dist/inMemoryDatabase.js.map +0 -1
- package/dist/schema/index.d.ts +0 -42
- package/dist/schema/index.d.ts.map +0 -1
- package/dist/schema/index.js +0 -42
- package/dist/schema/index.js.map +0 -1
- package/dist/schema/mutations.d.ts +0 -81
- package/dist/schema/mutations.d.ts.map +0 -1
- package/dist/schema/mutations.js +0 -29
- package/dist/schema/mutations.js.map +0 -1
- package/dist/schema/parse-utils.d.ts +0 -6
- package/dist/schema/parse-utils.d.ts.map +0 -1
- package/dist/schema/parse-utils.js +0 -22
- package/dist/schema/parse-utils.js.map +0 -1
- package/dist/schema/system-tables.d.ts +0 -76
- package/dist/schema/system-tables.d.ts.map +0 -1
- package/dist/schema/system-tables.js +0 -11
- package/dist/schema/system-tables.js.map +0 -1
- package/dist/schema/table-def.d.ts +0 -100
- package/dist/schema/table-def.d.ts.map +0 -1
- package/dist/schema/table-def.js +0 -70
- package/dist/schema/table-def.js.map +0 -1
- package/dist/storage/in-memory/index.d.ts +0 -19
- package/dist/storage/in-memory/index.d.ts.map +0 -1
- package/dist/storage/in-memory/index.js +0 -16
- package/dist/storage/in-memory/index.js.map +0 -1
- package/dist/storage/index.d.ts +0 -18
- package/dist/storage/index.d.ts.map +0 -1
- package/dist/storage/index.js +0 -9
- package/dist/storage/index.js.map +0 -1
- package/dist/storage/tauri/index.d.ts +0 -23
- package/dist/storage/tauri/index.d.ts.map +0 -1
- package/dist/storage/tauri/index.js +0 -46
- package/dist/storage/tauri/index.js.map +0 -1
- package/dist/storage/utils/idb.d.ts +0 -11
- package/dist/storage/utils/idb.d.ts.map +0 -1
- package/dist/storage/utils/idb.js +0 -71
- package/dist/storage/utils/idb.js.map +0 -1
- package/dist/storage/web-worker/common.d.ts +0 -11
- package/dist/storage/web-worker/common.d.ts.map +0 -1
- package/dist/storage/web-worker/common.js +0 -2
- package/dist/storage/web-worker/common.js.map +0 -1
- package/dist/storage/web-worker/index.d.ts +0 -34
- package/dist/storage/web-worker/index.d.ts.map +0 -1
- package/dist/storage/web-worker/index.js +0 -134
- package/dist/storage/web-worker/index.js.map +0 -1
- package/dist/storage/web-worker/make-worker.d.ts +0 -20
- package/dist/storage/web-worker/make-worker.d.ts.map +0 -1
- package/dist/storage/web-worker/make-worker.js +0 -155
- package/dist/storage/web-worker/make-worker.js.map +0 -1
- package/dist/storage/web-worker/vite-dev-polyfill.d.ts +0 -2
- package/dist/storage/web-worker/vite-dev-polyfill.d.ts.map +0 -1
- package/dist/storage/web-worker/vite-dev-polyfill.js +0 -35
- package/dist/storage/web-worker/vite-dev-polyfill.js.map +0 -1
- package/src/schema/index.ts +0 -100
- package/src/schema/mutations.ts +0 -128
- package/src/schema/parse-utils.ts +0 -42
- package/src/schema/system-tables.ts +0 -21
- package/src/schema/table-def.ts +0 -270
- package/src/storage/in-memory/index.ts +0 -28
- package/src/storage/index.ts +0 -36
- package/src/storage/tauri/index.ts +0 -66
- package/src/storage/utils/idb.ts +0 -85
- package/src/storage/web-worker/common.ts +0 -6
- package/src/storage/web-worker/index.ts +0 -185
- package/src/storage/web-worker/make-worker.ts +0 -214
- 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
|
|
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
|
|
35
|
+
export class MainDatabaseWrapper {
|
|
40
36
|
// TODO: how many unique active statements are expected?
|
|
41
|
-
private cachedStmts = new BoundMap<string,
|
|
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
|
-
|
|
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
|
-
|
|
51
|
+
db: MainDatabase
|
|
65
52
|
otelTracer: otel.Tracer
|
|
66
53
|
otelRootSpanContext: otel.Context
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
|
|
63
|
+
this.cachedStmts.onEvict = (_queryStr, stmt) => stmt.finalize()
|
|
92
64
|
|
|
93
|
-
|
|
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.
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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:
|
|
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
|
-
|
|
59
|
+
makeDb: makeDb(),
|
|
66
60
|
dbGraph,
|
|
67
61
|
otelTracer,
|
|
68
62
|
otelRootSpanContext: otelContext,
|
package/src/cud.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import
|
|
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 {
|
|
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 =
|
|
32
|
+
const values = DbSchema.getDefaultValuesDecoded(tableDef, values_)
|
|
32
33
|
|
|
33
|
-
const [sql, bindValues] =
|
|
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] =
|
|
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] =
|
|
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
|
}
|
package/src/effect/LiveStore.ts
CHANGED
|
@@ -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 {
|
|
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:
|
|
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:
|
|
42
|
+
makeContext: (db: MainDatabase) => GraphQLContext
|
|
52
43
|
}
|
|
53
|
-
boot?: (db:
|
|
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:
|
|
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
|
-
|
|
94
|
+
makeDb,
|
|
107
95
|
}),
|
|
108
96
|
),
|
|
109
97
|
Effect.acquireRelease((store) => Effect.sync(() => store.destroy())),
|
package/src/global-state.ts
CHANGED
|
@@ -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 './
|
|
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
|
-
|
|
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,
|
|
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 {
|
|
7
|
-
import
|
|
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:
|
|
47
|
+
db: DatabaseImpl
|
|
22
48
|
otelContext: otel.Context
|
|
23
49
|
schema: LiveStoreSchema
|
|
24
50
|
}) => {
|
|
25
|
-
|
|
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 =
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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:
|
|
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
|
|
84
|
-
db
|
|
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
|
|
104
|
+
dbExecute(db, createIndexFromDefinition(tableName, index))
|
|
88
105
|
}
|
|
89
106
|
|
|
90
107
|
const updatedAt = getMemoizedTimestamp()
|
|
91
|
-
|
|
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
|
-
{
|
|
97
|
-
new Set(),
|
|
98
|
-
{ otelContext },
|
|
115
|
+
{ tableName, schemaHash, updatedAt },
|
|
99
116
|
)
|
|
100
117
|
}
|
|
101
118
|
|