@livestore/livestore 0.0.0-snapshot-909cdd1ac2fd591945c2be2b0f53e14d87f3c9d4
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/README.md +1 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/QueryCache.d.ts +20 -0
- package/dist/QueryCache.d.ts.map +1 -0
- package/dist/QueryCache.js +61 -0
- package/dist/QueryCache.js.map +1 -0
- package/dist/SynchronousDatabaseWrapper.d.ts +36 -0
- package/dist/SynchronousDatabaseWrapper.d.ts.map +1 -0
- package/dist/SynchronousDatabaseWrapper.js +176 -0
- package/dist/SynchronousDatabaseWrapper.js.map +1 -0
- package/dist/effect/LiveStore.d.ts +38 -0
- package/dist/effect/LiveStore.d.ts.map +1 -0
- package/dist/effect/LiveStore.js +38 -0
- package/dist/effect/LiveStore.js.map +1 -0
- package/dist/effect/index.d.ts +2 -0
- package/dist/effect/index.d.ts.map +1 -0
- package/dist/effect/index.js +2 -0
- package/dist/effect/index.js.map +1 -0
- package/dist/global-state.d.ts +14 -0
- package/dist/global-state.d.ts.map +1 -0
- package/dist/global-state.js +16 -0
- package/dist/global-state.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/reactive.d.ts +163 -0
- package/dist/reactive.d.ts.map +1 -0
- package/dist/reactive.js +382 -0
- package/dist/reactive.js.map +1 -0
- package/dist/reactive.test.d.ts +2 -0
- package/dist/reactive.test.d.ts.map +1 -0
- package/dist/reactive.test.js +345 -0
- package/dist/reactive.test.js.map +1 -0
- package/dist/reactiveQueries/base-class.d.ts +59 -0
- package/dist/reactiveQueries/base-class.d.ts.map +1 -0
- package/dist/reactiveQueries/base-class.js +29 -0
- package/dist/reactiveQueries/base-class.js.map +1 -0
- package/dist/reactiveQueries/graphql.d.ts +52 -0
- package/dist/reactiveQueries/graphql.d.ts.map +1 -0
- package/dist/reactiveQueries/graphql.js +136 -0
- package/dist/reactiveQueries/graphql.js.map +1 -0
- package/dist/reactiveQueries/js.d.ts +35 -0
- package/dist/reactiveQueries/js.d.ts.map +1 -0
- package/dist/reactiveQueries/js.js +57 -0
- package/dist/reactiveQueries/js.js.map +1 -0
- package/dist/reactiveQueries/sql.d.ts +49 -0
- package/dist/reactiveQueries/sql.d.ts.map +1 -0
- package/dist/reactiveQueries/sql.js +130 -0
- package/dist/reactiveQueries/sql.js.map +1 -0
- package/dist/reactiveQueries/sql.test.d.ts +2 -0
- package/dist/reactiveQueries/sql.test.d.ts.map +1 -0
- package/dist/reactiveQueries/sql.test.js +284 -0
- package/dist/reactiveQueries/sql.test.js.map +1 -0
- package/dist/row-query.d.ts +33 -0
- package/dist/row-query.d.ts.map +1 -0
- package/dist/row-query.js +84 -0
- package/dist/row-query.js.map +1 -0
- package/dist/store-context.d.ts +26 -0
- package/dist/store-context.d.ts.map +1 -0
- package/dist/store-context.js +6 -0
- package/dist/store-context.js.map +1 -0
- package/dist/store-devtools.d.ts +19 -0
- package/dist/store-devtools.d.ts.map +1 -0
- package/dist/store-devtools.js +141 -0
- package/dist/store-devtools.js.map +1 -0
- package/dist/store.d.ts +175 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +507 -0
- package/dist/store.js.map +1 -0
- package/dist/utils/data-structures.d.ts +10 -0
- package/dist/utils/data-structures.d.ts.map +1 -0
- package/dist/utils/data-structures.js +32 -0
- package/dist/utils/data-structures.js.map +1 -0
- package/dist/utils/dev.d.ts +3 -0
- package/dist/utils/dev.d.ts.map +1 -0
- package/dist/utils/dev.js +17 -0
- package/dist/utils/dev.js.map +1 -0
- package/dist/utils/otel.d.ts +4 -0
- package/dist/utils/otel.d.ts.map +1 -0
- package/dist/utils/otel.js +6 -0
- package/dist/utils/otel.js.map +1 -0
- package/dist/utils/stack-info.d.ts +10 -0
- package/dist/utils/stack-info.d.ts.map +1 -0
- package/dist/utils/stack-info.js +41 -0
- package/dist/utils/stack-info.js.map +1 -0
- package/dist/utils/stack-info.test.d.ts +2 -0
- package/dist/utils/stack-info.test.d.ts.map +1 -0
- package/dist/utils/stack-info.test.js +75 -0
- package/dist/utils/stack-info.test.js.map +1 -0
- package/dist/utils/tests/fixture.d.ts +259 -0
- package/dist/utils/tests/fixture.d.ts.map +1 -0
- package/dist/utils/tests/fixture.js +33 -0
- package/dist/utils/tests/fixture.js.map +1 -0
- package/dist/utils/tests/mod.d.ts +3 -0
- package/dist/utils/tests/mod.d.ts.map +1 -0
- package/dist/utils/tests/mod.js +3 -0
- package/dist/utils/tests/mod.js.map +1 -0
- package/dist/utils/tests/otel.d.ts +10 -0
- package/dist/utils/tests/otel.d.ts.map +1 -0
- package/dist/utils/tests/otel.js +42 -0
- package/dist/utils/tests/otel.js.map +1 -0
- package/package.json +60 -0
- package/src/QueryCache.ts +81 -0
- package/src/SynchronousDatabaseWrapper.ts +256 -0
- package/src/ambient.d.ts +10 -0
- package/src/effect/LiveStore.ts +112 -0
- package/src/effect/index.ts +8 -0
- package/src/global-state.ts +20 -0
- package/src/index.ts +64 -0
- package/src/reactive.test.ts +426 -0
- package/src/reactive.ts +661 -0
- package/src/reactiveQueries/base-class.ts +115 -0
- package/src/reactiveQueries/graphql.ts +233 -0
- package/src/reactiveQueries/js.ts +108 -0
- package/src/reactiveQueries/sql.test.ts +308 -0
- package/src/reactiveQueries/sql.ts +226 -0
- package/src/row-query.ts +200 -0
- package/src/store-context.ts +23 -0
- package/src/store-devtools.ts +217 -0
- package/src/store.ts +920 -0
- package/src/utils/data-structures.ts +36 -0
- package/src/utils/dev.ts +24 -0
- package/src/utils/otel.ts +9 -0
- package/src/utils/stack-info.test.ts +79 -0
- package/src/utils/stack-info.ts +54 -0
- package/src/utils/tests/fixture.ts +77 -0
- package/src/utils/tests/mod.ts +2 -0
- package/src/utils/tests/otel.ts +61 -0
- package/tsconfig.json +18 -0
- package/vitest.config.js +9 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Bindable } from '@livestore/common'
|
|
2
|
+
import { BoundMap, BoundSet } from '@livestore/common'
|
|
3
|
+
|
|
4
|
+
type Opaque<BaseType, BrandType = unknown> = BaseType & {
|
|
5
|
+
readonly [Symbols.base]: BaseType
|
|
6
|
+
readonly [Symbols.brand]: BrandType
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
namespace Symbols {
|
|
10
|
+
export declare const base: unique symbol
|
|
11
|
+
export declare const brand: unique symbol
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type CacheKey = Opaque<string, string>
|
|
15
|
+
type TableName = string
|
|
16
|
+
|
|
17
|
+
const ignore = ['begin', 'rollback', 'commit', 'savepoint', 'release']
|
|
18
|
+
|
|
19
|
+
// TODO: profile to see how big we need this cache to be.
|
|
20
|
+
const cacheSize = 200
|
|
21
|
+
export default class QueryCache {
|
|
22
|
+
#entries = new BoundMap<CacheKey, any>(cacheSize)
|
|
23
|
+
#dependencies = new Map<TableName, BoundSet<CacheKey>>()
|
|
24
|
+
|
|
25
|
+
getKey = (sql: string, bindValues?: Bindable): CacheKey => {
|
|
26
|
+
if (bindValues == null) {
|
|
27
|
+
return sql as CacheKey
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (Array.isArray(bindValues)) {
|
|
31
|
+
return (sql + '\n' + bindValues.join('\n')) as CacheKey
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return (sql + '\n' + Object.values(bindValues).join('\n')) as CacheKey
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get = (key: CacheKey) => {
|
|
38
|
+
return this.#entries.get(key)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
set = (queriedTables: Iterable<string>, key: CacheKey, results: any) => {
|
|
42
|
+
this.#entries.set(key, results)
|
|
43
|
+
for (const table of queriedTables) {
|
|
44
|
+
let keys = this.#dependencies.get(table)
|
|
45
|
+
if (keys == null) {
|
|
46
|
+
keys = new BoundSet(cacheSize)
|
|
47
|
+
keys.onEvict = this.#dependencyTrackerEvicted
|
|
48
|
+
this.#dependencies.set(table, keys)
|
|
49
|
+
}
|
|
50
|
+
keys.add(key)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
#dependencyTrackerEvicted = (key: CacheKey) => {
|
|
55
|
+
this.#entries.delete(key)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
ignoreQuery = (query: string) => {
|
|
59
|
+
return ignore.some((prefix) => query.startsWith(prefix))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// The next simplest step is to create a specific implementation for invalidating
|
|
63
|
+
// the expensive track list queries only when constraints data in a write overlaps with read constraints.
|
|
64
|
+
//
|
|
65
|
+
// As well as either:
|
|
66
|
+
// a. removeing the big view (since we'll have our cache)
|
|
67
|
+
// b. incrementally updating the view on insert by the EventImporter
|
|
68
|
+
//
|
|
69
|
+
// We'll not try to tackle any generalized approach until we have a proof of concept working.
|
|
70
|
+
invalidate = (queriedTables: Iterable<string>) => {
|
|
71
|
+
for (const table of queriedTables) {
|
|
72
|
+
const keys = this.#dependencies.get(table)
|
|
73
|
+
if (keys == null) {
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
for (const k of keys) {
|
|
77
|
+
this.#entries.delete(k)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/* eslint-disable prefer-arrow/prefer-arrow-functions */
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
DebugInfo,
|
|
5
|
+
MutableDebugInfo,
|
|
6
|
+
PreparedBindValues,
|
|
7
|
+
PreparedStatement,
|
|
8
|
+
SynchronousDatabase,
|
|
9
|
+
} from '@livestore/common'
|
|
10
|
+
import { BoundArray, BoundMap, sql } from '@livestore/common'
|
|
11
|
+
import type * as otel from '@opentelemetry/api'
|
|
12
|
+
|
|
13
|
+
import QueryCache from './QueryCache.js'
|
|
14
|
+
import { getDurationMsFromSpan, getStartTimeHighResFromSpan } from './utils/otel.js'
|
|
15
|
+
|
|
16
|
+
export const emptyDebugInfo = (): DebugInfo => ({
|
|
17
|
+
slowQueries: new BoundArray(200),
|
|
18
|
+
queryFrameDuration: 0,
|
|
19
|
+
queryFrameCount: 0,
|
|
20
|
+
events: new BoundArray(1000),
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
export class SynchronousDatabaseWrapper {
|
|
24
|
+
// TODO: how many unique active statements are expected?
|
|
25
|
+
private cachedStmts = new BoundMap<string, PreparedStatement>(200)
|
|
26
|
+
private tablesUsedCache = new BoundMap<string, Set<string>>(200)
|
|
27
|
+
private resultCache = new QueryCache()
|
|
28
|
+
private db: SynchronousDatabase
|
|
29
|
+
private otelTracer: otel.Tracer
|
|
30
|
+
private otelRootSpanContext: otel.Context
|
|
31
|
+
private tablesUsedStmt
|
|
32
|
+
public debugInfo: MutableDebugInfo = emptyDebugInfo()
|
|
33
|
+
|
|
34
|
+
constructor({
|
|
35
|
+
db,
|
|
36
|
+
otel,
|
|
37
|
+
}: {
|
|
38
|
+
db: SynchronousDatabase
|
|
39
|
+
otel: {
|
|
40
|
+
tracer: otel.Tracer
|
|
41
|
+
rootSpanContext: otel.Context
|
|
42
|
+
}
|
|
43
|
+
}) {
|
|
44
|
+
this.db = db
|
|
45
|
+
this.otelTracer = otel.tracer
|
|
46
|
+
this.otelRootSpanContext = otel.rootSpanContext
|
|
47
|
+
|
|
48
|
+
this.tablesUsedStmt = db.prepare(
|
|
49
|
+
`SELECT tbl_name FROM tables_used(?) AS u JOIN sqlite_master ON sqlite_master.name = u.name WHERE u.schema = 'main';`,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
this.cachedStmts.onEvict = (_queryStr, stmt) => stmt.finalize()
|
|
53
|
+
|
|
54
|
+
configureSQLite(this)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
txn<TRes>(callback: () => TRes): TRes {
|
|
58
|
+
this.execute(sql`begin transaction;`)
|
|
59
|
+
|
|
60
|
+
let errored = false
|
|
61
|
+
let result: TRes
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
result = callback()
|
|
65
|
+
} catch (e) {
|
|
66
|
+
errored = true
|
|
67
|
+
this.execute(sql`rollback;`)
|
|
68
|
+
throw e
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!errored) {
|
|
72
|
+
this.execute(sql`commit;`)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return result
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getTablesUsed(query: string) {
|
|
79
|
+
// It seems that SQLite doesn't properly handle `DELETE FROM SOME_TABLE` queries without a WHERE clause
|
|
80
|
+
// So we need to handle these queries separately
|
|
81
|
+
const tableNameFromPlainDeleteQuery = tryGetTableNameFromPlainDeleteQuery(query)
|
|
82
|
+
if (tableNameFromPlainDeleteQuery !== undefined) {
|
|
83
|
+
return new Set<string>([tableNameFromPlainDeleteQuery])
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const cached = this.tablesUsedCache.get(query)
|
|
87
|
+
if (cached) {
|
|
88
|
+
return cached
|
|
89
|
+
}
|
|
90
|
+
const stmt = this.tablesUsedStmt
|
|
91
|
+
const tablesUsed = new Set<string>()
|
|
92
|
+
try {
|
|
93
|
+
const results = stmt.select<{ tbl_name: string }>([query] as unknown as PreparedBindValues)
|
|
94
|
+
|
|
95
|
+
for (const row of results) {
|
|
96
|
+
tablesUsed.add(row.tbl_name)
|
|
97
|
+
}
|
|
98
|
+
} catch (e) {
|
|
99
|
+
console.error('Error getting tables used', e, 'for query', query)
|
|
100
|
+
return new Set<string>()
|
|
101
|
+
}
|
|
102
|
+
this.tablesUsedCache.set(query, tablesUsed)
|
|
103
|
+
return tablesUsed
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
execute(
|
|
107
|
+
queryStr: string,
|
|
108
|
+
bindValues?: PreparedBindValues,
|
|
109
|
+
writeTables?: ReadonlySet<string>,
|
|
110
|
+
options?: { hasNoEffects?: boolean; otelContext?: otel.Context },
|
|
111
|
+
): { durationMs: number } {
|
|
112
|
+
// console.debug('in-memory-db:execute', query, bindValues)
|
|
113
|
+
|
|
114
|
+
return this.otelTracer.startActiveSpan(
|
|
115
|
+
'livestore.in-memory-db:execute',
|
|
116
|
+
// TODO truncate query string
|
|
117
|
+
{ attributes: { 'sql.query': queryStr } },
|
|
118
|
+
options?.otelContext ?? this.otelRootSpanContext,
|
|
119
|
+
(span) => {
|
|
120
|
+
let stmt = this.cachedStmts.get(queryStr)
|
|
121
|
+
if (stmt === undefined) {
|
|
122
|
+
stmt = this.db.prepare(queryStr)
|
|
123
|
+
this.cachedStmts.set(queryStr, stmt)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
stmt.execute(bindValues)
|
|
127
|
+
|
|
128
|
+
if (options?.hasNoEffects !== true && !this.resultCache.ignoreQuery(queryStr)) {
|
|
129
|
+
// TODO use write tables instead
|
|
130
|
+
// check what queries actually end up here.
|
|
131
|
+
this.resultCache.invalidate(writeTables ?? this.getTablesUsed(queryStr))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
span.end()
|
|
135
|
+
|
|
136
|
+
const durationMs = getDurationMsFromSpan(span)
|
|
137
|
+
|
|
138
|
+
this.debugInfo.queryFrameDuration += durationMs
|
|
139
|
+
this.debugInfo.queryFrameCount++
|
|
140
|
+
|
|
141
|
+
if (durationMs > 5 && import.meta.env.DEV) {
|
|
142
|
+
this.debugInfo.slowQueries.push({
|
|
143
|
+
queryStr,
|
|
144
|
+
bindValues,
|
|
145
|
+
durationMs,
|
|
146
|
+
rowsCount: undefined,
|
|
147
|
+
queriedTables: new Set(),
|
|
148
|
+
startTimePerfNow: getStartTimeHighResFromSpan(span),
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { durationMs }
|
|
153
|
+
},
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
select<T = any>(
|
|
158
|
+
queryStr: string,
|
|
159
|
+
options?: {
|
|
160
|
+
queriedTables?: ReadonlySet<string>
|
|
161
|
+
bindValues?: PreparedBindValues
|
|
162
|
+
skipCache?: boolean
|
|
163
|
+
otelContext?: otel.Context
|
|
164
|
+
},
|
|
165
|
+
): ReadonlyArray<T> {
|
|
166
|
+
const { queriedTables, bindValues, skipCache = false, otelContext } = options ?? {}
|
|
167
|
+
|
|
168
|
+
// console.debug('in-memory-db:select', query, bindValues)
|
|
169
|
+
|
|
170
|
+
return this.otelTracer.startActiveSpan(
|
|
171
|
+
'sql-in-memory-select',
|
|
172
|
+
{},
|
|
173
|
+
otelContext ?? this.otelRootSpanContext,
|
|
174
|
+
(span) => {
|
|
175
|
+
try {
|
|
176
|
+
span.setAttribute('sql.query', queryStr)
|
|
177
|
+
|
|
178
|
+
const key = this.resultCache.getKey(queryStr, bindValues)
|
|
179
|
+
const cachedResult = this.resultCache.get(key)
|
|
180
|
+
if (skipCache === false && cachedResult !== undefined) {
|
|
181
|
+
span.setAttribute('sql.rowsCount', cachedResult.length)
|
|
182
|
+
span.setAttribute('sql.cached', true)
|
|
183
|
+
span.end()
|
|
184
|
+
return cachedResult
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let stmt = this.cachedStmts.get(queryStr)
|
|
188
|
+
if (stmt === undefined) {
|
|
189
|
+
stmt = this.db.prepare(queryStr)
|
|
190
|
+
this.cachedStmts.set(queryStr, stmt)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const result = stmt.select<T>(bindValues)
|
|
194
|
+
|
|
195
|
+
span.setAttribute('sql.rowsCount', result.length)
|
|
196
|
+
span.setAttribute('sql.cached', false)
|
|
197
|
+
|
|
198
|
+
const queriedTables_ = queriedTables ?? this.getTablesUsed(queryStr)
|
|
199
|
+
this.resultCache.set(queriedTables_, key, result)
|
|
200
|
+
|
|
201
|
+
span.end()
|
|
202
|
+
|
|
203
|
+
const durationMs = getDurationMsFromSpan(span)
|
|
204
|
+
|
|
205
|
+
this.debugInfo.queryFrameDuration += durationMs
|
|
206
|
+
this.debugInfo.queryFrameCount++
|
|
207
|
+
|
|
208
|
+
// TODO also enable in non-dev mode
|
|
209
|
+
if (durationMs > 5 && import.meta.env.DEV) {
|
|
210
|
+
this.debugInfo.slowQueries.push({
|
|
211
|
+
queryStr,
|
|
212
|
+
bindValues,
|
|
213
|
+
durationMs,
|
|
214
|
+
rowsCount: result.length,
|
|
215
|
+
queriedTables: queriedTables_,
|
|
216
|
+
startTimePerfNow: getStartTimeHighResFromSpan(span),
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return result
|
|
221
|
+
} finally {
|
|
222
|
+
span.end()
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export() {
|
|
229
|
+
// Clear statement cache because exporting frees statements
|
|
230
|
+
for (const key of this.cachedStmts.keys()) {
|
|
231
|
+
this.cachedStmts.delete(key)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return this.db.export()
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Set up SQLite performance; hasn't been super carefully optimized yet. */
|
|
239
|
+
const configureSQLite = (db: SynchronousDatabaseWrapper) => {
|
|
240
|
+
db.execute(
|
|
241
|
+
// TODO: revisit these tuning parameters for max performance
|
|
242
|
+
sql`
|
|
243
|
+
PRAGMA page_size=32768;
|
|
244
|
+
PRAGMA cache_size=10000;
|
|
245
|
+
PRAGMA journal_mode='MEMORY'; -- we don't flush to disk before committing a write
|
|
246
|
+
PRAGMA synchronous='OFF';
|
|
247
|
+
PRAGMA temp_store='MEMORY';
|
|
248
|
+
PRAGMA foreign_keys='ON'; -- we want foreign key constraints to be enforced
|
|
249
|
+
`,
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const tryGetTableNameFromPlainDeleteQuery = (query: string) => {
|
|
254
|
+
const [_, tableName] = query.trim().match(/^delete\s+from\s+(\w+)$/i) ?? []
|
|
255
|
+
return tableName
|
|
256
|
+
}
|
package/src/ambient.d.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { Adapter, BootDb, BootStatus, UnexpectedError } from '@livestore/common'
|
|
2
|
+
import type { LiveStoreSchema } from '@livestore/common/schema'
|
|
3
|
+
import type { Cause, Scope } from '@livestore/utils/effect'
|
|
4
|
+
import { Context, Deferred, Duration, Effect, FiberSet, Layer, OtelTracer, pipe } from '@livestore/utils/effect'
|
|
5
|
+
import * as otel from '@opentelemetry/api'
|
|
6
|
+
import type { GraphQLSchema } from 'graphql'
|
|
7
|
+
|
|
8
|
+
import type { BaseGraphQLContext } from '../store.js'
|
|
9
|
+
import { createStore } from '../store.js'
|
|
10
|
+
import type { LiveStoreContextRunning as LiveStoreContextRunning_ } from '../store-context.js'
|
|
11
|
+
import type { SynchronousDatabaseWrapper } from '../SynchronousDatabaseWrapper.js'
|
|
12
|
+
|
|
13
|
+
export type LiveStoreContextRunning = LiveStoreContextRunning_
|
|
14
|
+
export const LiveStoreContextRunning = Context.GenericTag<LiveStoreContextRunning>(
|
|
15
|
+
'@livestore/livestore/effect/LiveStoreContextRunning',
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
export type DeferredStoreContext = Deferred.Deferred<LiveStoreContextRunning, UnexpectedError>
|
|
19
|
+
export const DeferredStoreContext = Context.GenericTag<DeferredStoreContext>(
|
|
20
|
+
'@livestore/livestore/effect/DeferredStoreContext',
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
export type LiveStoreContextProps<GraphQLContext extends BaseGraphQLContext> = {
|
|
24
|
+
schema: LiveStoreSchema
|
|
25
|
+
/**
|
|
26
|
+
* The `storeId` can be used to isolate multiple stores from each other.
|
|
27
|
+
* So it can be useful for multi-tenancy scenarios.
|
|
28
|
+
*
|
|
29
|
+
* The `storeId` is also used for persistence.
|
|
30
|
+
*
|
|
31
|
+
* @default 'default'
|
|
32
|
+
*/
|
|
33
|
+
storeId?: string
|
|
34
|
+
graphQLOptions?: {
|
|
35
|
+
schema: Effect.Effect<GraphQLSchema, never, otel.Tracer>
|
|
36
|
+
makeContext: (db: SynchronousDatabaseWrapper, tracer: otel.Tracer, sessionId: string) => GraphQLContext
|
|
37
|
+
}
|
|
38
|
+
boot?: (db: BootDb) => Effect.Effect<void, unknown, otel.Tracer>
|
|
39
|
+
adapter: Adapter
|
|
40
|
+
disableDevtools?: boolean
|
|
41
|
+
onBootStatus?: (status: BootStatus) => void
|
|
42
|
+
batchUpdates: (run: () => void) => void
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const LiveStoreContextLayer = <GraphQLContext extends BaseGraphQLContext>(
|
|
46
|
+
props: LiveStoreContextProps<GraphQLContext>,
|
|
47
|
+
): Layer.Layer<LiveStoreContextRunning, UnexpectedError | Cause.TimeoutException, otel.Tracer> =>
|
|
48
|
+
Layer.scoped(LiveStoreContextRunning, makeLiveStoreContext(props)).pipe(
|
|
49
|
+
Layer.withSpan('LiveStore'),
|
|
50
|
+
Layer.provide(LiveStoreContextDeferred),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
export const LiveStoreContextDeferred = Layer.effect(
|
|
54
|
+
DeferredStoreContext,
|
|
55
|
+
Deferred.make<LiveStoreContextRunning, UnexpectedError>(),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
export const makeLiveStoreContext = <GraphQLContext extends BaseGraphQLContext>({
|
|
59
|
+
schema,
|
|
60
|
+
storeId = 'default',
|
|
61
|
+
graphQLOptions: graphQLOptions_,
|
|
62
|
+
boot,
|
|
63
|
+
adapter,
|
|
64
|
+
disableDevtools,
|
|
65
|
+
onBootStatus,
|
|
66
|
+
batchUpdates,
|
|
67
|
+
}: LiveStoreContextProps<GraphQLContext>): Effect.Effect<
|
|
68
|
+
LiveStoreContextRunning,
|
|
69
|
+
UnexpectedError | Cause.TimeoutException,
|
|
70
|
+
DeferredStoreContext | Scope.Scope | otel.Tracer
|
|
71
|
+
> =>
|
|
72
|
+
pipe(
|
|
73
|
+
Effect.gen(function* () {
|
|
74
|
+
const otelRootSpanContext = otel.context.active()
|
|
75
|
+
|
|
76
|
+
const otelTracer = yield* OtelTracer.Tracer
|
|
77
|
+
|
|
78
|
+
const graphQLOptions = yield* graphQLOptions_
|
|
79
|
+
? Effect.all({ schema: graphQLOptions_.schema, makeContext: Effect.succeed(graphQLOptions_.makeContext) })
|
|
80
|
+
: Effect.succeed(undefined)
|
|
81
|
+
|
|
82
|
+
// TODO join fiber set and close tear down parent scope in case of error (Needs refactor with Mike A)
|
|
83
|
+
const fiberSet = yield* FiberSet.make()
|
|
84
|
+
|
|
85
|
+
const store = yield* createStore({
|
|
86
|
+
schema,
|
|
87
|
+
storeId,
|
|
88
|
+
graphQLOptions,
|
|
89
|
+
otelOptions: {
|
|
90
|
+
tracer: otelTracer,
|
|
91
|
+
rootSpanContext: otelRootSpanContext,
|
|
92
|
+
},
|
|
93
|
+
boot,
|
|
94
|
+
adapter,
|
|
95
|
+
disableDevtools,
|
|
96
|
+
fiberSet,
|
|
97
|
+
onBootStatus,
|
|
98
|
+
batchUpdates,
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
globalThis.__debugLiveStore ??= {}
|
|
102
|
+
// window.__debugLiveStore[schema.key] = store
|
|
103
|
+
|
|
104
|
+
return { stage: 'running', store } satisfies LiveStoreContextRunning
|
|
105
|
+
}),
|
|
106
|
+
Effect.tapErrorCause((cause) => Effect.flatMap(DeferredStoreContext, (def) => Deferred.failCause(def, cause))),
|
|
107
|
+
Effect.tap((storeCtx) => Effect.flatMap(DeferredStoreContext, (def) => Deferred.succeed(def, storeCtx))),
|
|
108
|
+
// This can take quite a while.
|
|
109
|
+
// TODO make this configurable
|
|
110
|
+
Effect.timeout(Duration.minutes(5)),
|
|
111
|
+
Effect.withSpan('@livestore/livestore/effect:makeLiveStoreContext'),
|
|
112
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* LiveStore currently relies on some global state in order to simplify the end-user API.
|
|
4
|
+
* This however also has the downside that LiveStore can't be used in multiple instances in the same app.
|
|
5
|
+
* It could possibly also lead to some other problems.
|
|
6
|
+
*
|
|
7
|
+
* We should find some better way to do this and ideally remove this global state.
|
|
8
|
+
*
|
|
9
|
+
* Another approach could be to use the global state by default but provide an additional way to let the user
|
|
10
|
+
* explicitly pass instances of state below into the LiveStore constructors.
|
|
11
|
+
*
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { GlobalValue } from '@livestore/utils/effect'
|
|
15
|
+
|
|
16
|
+
import { makeReactivityGraph } from './reactiveQueries/base-class.js'
|
|
17
|
+
|
|
18
|
+
export const globalReactivityGraph = GlobalValue.globalValue('livestore-global-reactivityGraph', () =>
|
|
19
|
+
makeReactivityGraph(),
|
|
20
|
+
)
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export { Store, createStorePromise, createStore } from './store.js'
|
|
2
|
+
export type {
|
|
3
|
+
BaseGraphQLContext,
|
|
4
|
+
QueryDebugInfo,
|
|
5
|
+
RefreshReason,
|
|
6
|
+
CreateStoreOptions,
|
|
7
|
+
GraphQLOptions,
|
|
8
|
+
OtelOptions,
|
|
9
|
+
} from './store.js'
|
|
10
|
+
|
|
11
|
+
export type { LiveStoreContextRunning } from './effect/LiveStore.js'
|
|
12
|
+
export { StoreAbort, StoreInterrupted, type LiveStoreContext } from './store-context.js'
|
|
13
|
+
|
|
14
|
+
export { SynchronousDatabaseWrapper, emptyDebugInfo } from './SynchronousDatabaseWrapper.js'
|
|
15
|
+
|
|
16
|
+
export type {
|
|
17
|
+
GetAtom,
|
|
18
|
+
AtomDebugInfo,
|
|
19
|
+
RefreshDebugInfo,
|
|
20
|
+
ReactiveGraphSnapshot,
|
|
21
|
+
SerializedAtom,
|
|
22
|
+
SerializedEffect,
|
|
23
|
+
Atom,
|
|
24
|
+
Node,
|
|
25
|
+
Ref,
|
|
26
|
+
Effect,
|
|
27
|
+
} from './reactive.js'
|
|
28
|
+
export { LiveStoreJSQuery, computed } from './reactiveQueries/js.js'
|
|
29
|
+
export { LiveStoreSQLQuery, querySQL } from './reactiveQueries/sql.js'
|
|
30
|
+
export { LiveStoreGraphQLQuery, queryGraphQL } from './reactiveQueries/graphql.js'
|
|
31
|
+
export {
|
|
32
|
+
type GetAtomResult,
|
|
33
|
+
type ReactivityGraph,
|
|
34
|
+
makeReactivityGraph,
|
|
35
|
+
type LiveQuery,
|
|
36
|
+
type GetResult,
|
|
37
|
+
type LiveQueryAny,
|
|
38
|
+
} from './reactiveQueries/base-class.js'
|
|
39
|
+
|
|
40
|
+
export { globalReactivityGraph } from './global-state.js'
|
|
41
|
+
|
|
42
|
+
export { type RowResult, type RowResultEncoded, rowQuery, deriveColQuery } from './row-query.js'
|
|
43
|
+
|
|
44
|
+
export * from '@livestore/common/schema'
|
|
45
|
+
export {
|
|
46
|
+
sql,
|
|
47
|
+
SessionIdSymbol,
|
|
48
|
+
type BootDb,
|
|
49
|
+
type BootStatus,
|
|
50
|
+
type SynchronousDatabase,
|
|
51
|
+
type DebugInfo,
|
|
52
|
+
type MutableDebugInfo,
|
|
53
|
+
prepareBindValues,
|
|
54
|
+
type Bindable,
|
|
55
|
+
type PreparedBindValues,
|
|
56
|
+
} from '@livestore/common'
|
|
57
|
+
|
|
58
|
+
export { SqliteAst, SqliteDsl } from '@livestore/db-schema'
|
|
59
|
+
|
|
60
|
+
export { deepEqual } from '@livestore/utils'
|
|
61
|
+
|
|
62
|
+
export * from './utils/stack-info.js'
|
|
63
|
+
|
|
64
|
+
export type { ClientSession, Adapter, PreparedStatement } from '@livestore/common'
|