@livestore/livestore 0.0.39 → 0.0.41-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -24
- package/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/react/fixture.d.ts +192 -17
- package/dist/__tests__/react/fixture.d.ts.map +1 -1
- package/dist/__tests__/react/fixture.js +10 -29
- package/dist/__tests__/react/fixture.js.map +1 -1
- package/dist/{mutations.d.ts → cud.d.ts} +14 -19
- package/dist/cud.d.ts.map +1 -0
- package/dist/{mutations.js → cud.js} +16 -10
- package/dist/cud.js.map +1 -0
- package/dist/cud.test.d.ts +2 -0
- package/dist/cud.test.d.ts.map +1 -0
- package/dist/cud.test.js +47 -0
- package/dist/cud.test.js.map +1 -0
- package/dist/inMemoryDatabase.d.ts +1 -1
- package/dist/inMemoryDatabase.d.ts.map +1 -1
- package/dist/inMemoryDatabase.js +1 -4
- package/dist/inMemoryDatabase.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +11 -7
- package/dist/migrations.js.map +1 -1
- package/dist/query-info.d.ts +2 -5
- package/dist/query-info.d.ts.map +1 -1
- package/dist/query-info.js +3 -2
- package/dist/query-info.js.map +1 -1
- package/dist/react/useAtom.d.ts.map +1 -1
- package/dist/react/useAtom.js +2 -2
- package/dist/react/useAtom.js.map +1 -1
- package/dist/react/useQuery.test.d.ts.map +1 -0
- package/dist/{__tests__/react → react}/useQuery.test.js +8 -11
- package/dist/react/useQuery.test.js.map +1 -0
- package/dist/react/useRow.js +4 -4
- package/dist/react/useRow.js.map +1 -1
- package/dist/react/useRow.test.d.ts.map +1 -0
- package/dist/{__tests__/react → react}/useRow.test.js +14 -38
- package/dist/react/useRow.test.js.map +1 -0
- package/dist/reactive.d.ts +2 -2
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +50 -15
- package/dist/reactive.js.map +1 -1
- package/dist/reactive.test.d.ts.map +1 -0
- package/dist/{__tests__/reactive.test.js → reactive.test.js} +1 -1
- package/dist/reactive.test.js.map +1 -0
- package/dist/reactiveQueries/base-class.d.ts +2 -0
- package/dist/reactiveQueries/base-class.d.ts.map +1 -1
- package/dist/reactiveQueries/base-class.js +1 -0
- package/dist/reactiveQueries/base-class.js.map +1 -1
- package/dist/reactiveQueries/graphql.d.ts.map +1 -1
- package/dist/reactiveQueries/graphql.js +1 -0
- package/dist/reactiveQueries/graphql.js.map +1 -1
- package/dist/reactiveQueries/js.d.ts.map +1 -1
- package/dist/reactiveQueries/js.js +1 -0
- package/dist/reactiveQueries/js.js.map +1 -1
- package/dist/reactiveQueries/sql.d.ts.map +1 -1
- package/dist/reactiveQueries/sql.js +1 -0
- package/dist/reactiveQueries/sql.js.map +1 -1
- package/dist/reactiveQueries/sql.test.d.ts.map +1 -0
- package/dist/{__tests__/reactiveQueries → reactiveQueries}/sql.test.js +44 -34
- package/dist/reactiveQueries/sql.test.js.map +1 -0
- package/dist/row-query.js +8 -6
- package/dist/row-query.js.map +1 -1
- package/dist/schema/index.d.ts +20 -7
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +18 -3
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/mutations.d.ts +81 -0
- package/dist/schema/mutations.d.ts.map +1 -0
- package/dist/schema/mutations.js +29 -0
- package/dist/schema/mutations.js.map +1 -0
- package/dist/schema/parse-utils.d.ts +3 -3
- package/dist/schema/parse-utils.d.ts.map +1 -1
- package/dist/schema/table-def.d.ts +2 -2
- package/dist/schema/table-def.d.ts.map +1 -1
- package/dist/schema/table-def.js +14 -6
- package/dist/schema/table-def.js.map +1 -1
- package/dist/storage/in-memory/index.d.ts +4 -0
- package/dist/storage/in-memory/index.d.ts.map +1 -1
- package/dist/storage/in-memory/index.js +3 -0
- package/dist/storage/in-memory/index.js.map +1 -1
- package/dist/storage/index.d.ts +4 -0
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/tauri/index.d.ts +4 -0
- package/dist/storage/tauri/index.d.ts.map +1 -1
- package/dist/storage/tauri/index.js +6 -0
- package/dist/storage/tauri/index.js.map +1 -1
- package/dist/storage/utils/idb.d.ts +1 -0
- package/dist/storage/utils/idb.d.ts.map +1 -1
- package/dist/storage/utils/idb.js +11 -0
- package/dist/storage/utils/idb.js.map +1 -1
- package/dist/storage/web-worker/common.d.ts +11 -0
- package/dist/storage/web-worker/common.d.ts.map +1 -0
- package/dist/storage/web-worker/common.js +2 -0
- package/dist/storage/web-worker/common.js.map +1 -0
- package/dist/storage/web-worker/index.d.ts +14 -7
- package/dist/storage/web-worker/index.d.ts.map +1 -1
- package/dist/storage/web-worker/index.js +70 -14
- package/dist/storage/web-worker/index.js.map +1 -1
- package/dist/storage/web-worker/make-worker.d.ts +20 -0
- package/dist/storage/web-worker/make-worker.d.ts.map +1 -0
- package/dist/storage/web-worker/make-worker.js +155 -0
- package/dist/storage/web-worker/make-worker.js.map +1 -0
- package/dist/storage/web-worker/vite-dev-polyfill.d.ts +2 -0
- package/dist/storage/web-worker/vite-dev-polyfill.d.ts.map +1 -0
- package/dist/storage/web-worker/vite-dev-polyfill.js +35 -0
- package/dist/storage/web-worker/vite-dev-polyfill.js.map +1 -0
- package/dist/store.d.ts +32 -42
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +82 -131
- package/dist/store.js.map +1 -1
- package/dist/utils/dev.d.ts +3 -0
- package/dist/utils/dev.d.ts.map +1 -0
- package/dist/utils/dev.js +16 -0
- package/dist/utils/dev.js.map +1 -0
- package/dist/utils/util.d.ts +2 -0
- package/dist/utils/util.d.ts.map +1 -1
- package/dist/utils/util.js +2 -0
- package/dist/utils/util.js.map +1 -1
- package/package.json +24 -12
- package/src/__tests__/react/fixture.tsx +12 -30
- package/src/cud.test.ts +52 -0
- package/src/{mutations.ts → cud.ts} +29 -28
- package/src/inMemoryDatabase.ts +2 -7
- package/src/index.ts +14 -8
- package/src/migrations.ts +10 -7
- package/src/query-info.ts +4 -7
- package/src/react/useAtom.ts +2 -2
- package/src/{__tests__/react → react}/useQuery.test.tsx +11 -11
- package/src/{__tests__/react → react}/useRow.test.tsx +21 -39
- package/src/react/useRow.ts +4 -4
- package/src/{__tests__/reactive.test.ts → reactive.test.ts} +1 -1
- package/src/reactive.ts +60 -19
- package/src/reactiveQueries/base-class.ts +4 -0
- package/src/reactiveQueries/graphql.ts +2 -0
- package/src/reactiveQueries/js.ts +2 -0
- package/src/{__tests__/reactiveQueries → reactiveQueries}/sql.test.ts +44 -34
- package/src/reactiveQueries/sql.ts +2 -0
- package/src/row-query.ts +9 -10
- package/src/schema/index.ts +47 -11
- package/src/schema/mutations.ts +129 -0
- package/src/schema/parse-utils.ts +1 -1
- package/src/schema/table-def.ts +20 -8
- package/src/storage/in-memory/index.ts +7 -0
- package/src/storage/index.ts +8 -0
- package/src/storage/tauri/index.ts +10 -0
- package/src/storage/utils/idb.ts +14 -0
- package/src/storage/web-worker/common.ts +6 -0
- package/src/storage/web-worker/index.ts +86 -17
- package/src/storage/web-worker/make-worker.ts +214 -0
- package/src/storage/web-worker/vite-dev-polyfill.ts +33 -0
- package/src/store.ts +142 -212
- package/src/utils/dev.ts +23 -0
- package/src/utils/util.ts +4 -0
- package/dist/__tests__/mutations.test.d.ts +0 -2
- package/dist/__tests__/mutations.test.d.ts.map +0 -1
- package/dist/__tests__/mutations.test.js +0 -40
- package/dist/__tests__/mutations.test.js.map +0 -1
- package/dist/__tests__/react/useQuery.test.d.ts.map +0 -1
- package/dist/__tests__/react/useQuery.test.js.map +0 -1
- package/dist/__tests__/react/useRow.test.d.ts.map +0 -1
- package/dist/__tests__/react/useRow.test.js.map +0 -1
- package/dist/__tests__/reactive.test.d.ts.map +0 -1
- package/dist/__tests__/reactive.test.js.map +0 -1
- package/dist/__tests__/reactiveQueries/sql.test.d.ts.map +0 -1
- package/dist/__tests__/reactiveQueries/sql.test.js.map +0 -1
- package/dist/events.d.ts +0 -7
- package/dist/events.d.ts.map +0 -1
- package/dist/events.js +0 -2
- package/dist/events.js.map +0 -1
- package/dist/mutations.d.ts.map +0 -1
- package/dist/mutations.js.map +0 -1
- package/dist/schema/action.d.ts +0 -30
- package/dist/schema/action.d.ts.map +0 -1
- package/dist/schema/action.js +0 -3
- package/dist/schema/action.js.map +0 -1
- package/dist/storage/web-worker/worker.d.ts +0 -13
- package/dist/storage/web-worker/worker.d.ts.map +0 -1
- package/dist/storage/web-worker/worker.js +0 -110
- package/dist/storage/web-worker/worker.js.map +0 -1
- package/src/__tests__/mutations.test.ts +0 -43
- package/src/events.ts +0 -8
- package/src/schema/action.ts +0 -41
- package/src/storage/web-worker/worker.ts +0 -141
- /package/dist/{__tests__/react → react}/useQuery.test.d.ts +0 -0
- /package/dist/{__tests__/react → react}/useRow.test.d.ts +0 -0
- /package/dist/{__tests__/reactive.test.d.ts → reactive.test.d.ts} +0 -0
- /package/dist/{__tests__/reactiveQueries → reactiveQueries}/sql.test.d.ts +0 -0
|
@@ -57,6 +57,8 @@ export interface LiveQuery<TResult, TQueryInfo extends QueryInfo = QueryInfoNone
|
|
|
57
57
|
queryInfo: TQueryInfo
|
|
58
58
|
|
|
59
59
|
runs: number
|
|
60
|
+
|
|
61
|
+
executionTimes: number[]
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
export abstract class LiveStoreQueryBase<TResult, TQueryInfo extends QueryInfo>
|
|
@@ -81,6 +83,8 @@ export abstract class LiveStoreQueryBase<TResult, TQueryInfo extends QueryInfo>
|
|
|
81
83
|
return this.results$.recomputations
|
|
82
84
|
}
|
|
83
85
|
|
|
86
|
+
executionTimes: number[] = []
|
|
87
|
+
|
|
84
88
|
abstract destroy: () => void
|
|
85
89
|
|
|
86
90
|
run = (otelContext?: otel.Context, debugRefreshReason?: RefreshReason): TResult =>
|
|
@@ -80,6 +80,8 @@ export class LiveStoreJSQuery<TResult, TQueryInfo extends QueryInfo = QueryInfoN
|
|
|
80
80
|
|
|
81
81
|
const durationMs = getDurationMsFromSpan(span)
|
|
82
82
|
|
|
83
|
+
this.executionTimes.push(durationMs)
|
|
84
|
+
|
|
83
85
|
setDebugInfo({ _tag: 'js', label, query: fn.toString(), durationMs })
|
|
84
86
|
|
|
85
87
|
return res
|
|
@@ -3,8 +3,8 @@ import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'
|
|
|
3
3
|
import { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
|
|
4
4
|
import { describe, expect, it } from 'vitest'
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { makeTodoMvc, todos } from '../__tests__/react/fixture.js'
|
|
7
|
+
import { computed, ParseUtils, querySQL, rawSqlMutation, sql } from '../index.js'
|
|
8
8
|
|
|
9
9
|
/*
|
|
10
10
|
TODO write tests for:
|
|
@@ -40,10 +40,7 @@ describe('otel', () => {
|
|
|
40
40
|
const query = querySQL(`select * from todos`, { queriedTables: new Set(['todos']) })
|
|
41
41
|
expect(query.run()).toMatchInlineSnapshot('[]')
|
|
42
42
|
|
|
43
|
-
store.
|
|
44
|
-
sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);`,
|
|
45
|
-
writeTables: ['todos'],
|
|
46
|
-
})
|
|
43
|
+
store.mutate(rawSqlMutation({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
|
|
47
44
|
|
|
48
45
|
expect(query.run()).toMatchInlineSnapshot(`
|
|
49
46
|
[
|
|
@@ -91,28 +88,36 @@ describe('otel', () => {
|
|
|
91
88
|
},
|
|
92
89
|
},
|
|
93
90
|
{
|
|
94
|
-
"_name": "LiveStore:
|
|
91
|
+
"_name": "LiveStore:mutations",
|
|
95
92
|
"children": [
|
|
96
93
|
{
|
|
97
|
-
"_name": "LiveStore:
|
|
94
|
+
"_name": "LiveStore:mutate",
|
|
95
|
+
"attributes": {
|
|
96
|
+
"livestore.mutateLabel": "mutate",
|
|
97
|
+
},
|
|
98
98
|
"children": [
|
|
99
99
|
{
|
|
100
|
-
"_name": "LiveStore:
|
|
100
|
+
"_name": "LiveStore:processWrites",
|
|
101
101
|
"attributes": {
|
|
102
|
-
"livestore.
|
|
103
|
-
"livestore.args": "{
|
|
104
|
-
"sql": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);",
|
|
105
|
-
"writeTables": [
|
|
106
|
-
"todos"
|
|
107
|
-
]
|
|
108
|
-
}",
|
|
102
|
+
"livestore.mutateLabel": "mutate",
|
|
109
103
|
},
|
|
110
104
|
"children": [
|
|
111
105
|
{
|
|
112
|
-
"_name": "
|
|
106
|
+
"_name": "LiveStore:mutatetWithoutRefresh",
|
|
113
107
|
"attributes": {
|
|
114
|
-
"
|
|
108
|
+
"livestore.args": "{
|
|
109
|
+
"sql": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)"
|
|
110
|
+
}",
|
|
111
|
+
"livestore.mutation": "livestore.RawSql",
|
|
115
112
|
},
|
|
113
|
+
"children": [
|
|
114
|
+
{
|
|
115
|
+
"_name": "livestore.in-memory-db:execute",
|
|
116
|
+
"attributes": {
|
|
117
|
+
"sql.query": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)",
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
],
|
|
116
121
|
},
|
|
117
122
|
],
|
|
118
123
|
},
|
|
@@ -183,10 +188,7 @@ describe('otel', () => {
|
|
|
183
188
|
}
|
|
184
189
|
`)
|
|
185
190
|
|
|
186
|
-
store.
|
|
187
|
-
sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);`,
|
|
188
|
-
writeTables: ['todos'],
|
|
189
|
-
})
|
|
191
|
+
store.mutate(rawSqlMutation({ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)` }))
|
|
190
192
|
|
|
191
193
|
expect(query.run()).toMatchInlineSnapshot(`
|
|
192
194
|
{
|
|
@@ -232,28 +234,36 @@ describe('otel', () => {
|
|
|
232
234
|
},
|
|
233
235
|
},
|
|
234
236
|
{
|
|
235
|
-
"_name": "LiveStore:
|
|
237
|
+
"_name": "LiveStore:mutations",
|
|
236
238
|
"children": [
|
|
237
239
|
{
|
|
238
|
-
"_name": "LiveStore:
|
|
240
|
+
"_name": "LiveStore:mutate",
|
|
241
|
+
"attributes": {
|
|
242
|
+
"livestore.mutateLabel": "mutate",
|
|
243
|
+
},
|
|
239
244
|
"children": [
|
|
240
245
|
{
|
|
241
|
-
"_name": "LiveStore:
|
|
246
|
+
"_name": "LiveStore:processWrites",
|
|
242
247
|
"attributes": {
|
|
243
|
-
"livestore.
|
|
244
|
-
"livestore.args": "{
|
|
245
|
-
"sql": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);",
|
|
246
|
-
"writeTables": [
|
|
247
|
-
"todos"
|
|
248
|
-
]
|
|
249
|
-
}",
|
|
248
|
+
"livestore.mutateLabel": "mutate",
|
|
250
249
|
},
|
|
251
250
|
"children": [
|
|
252
251
|
{
|
|
253
|
-
"_name": "
|
|
252
|
+
"_name": "LiveStore:mutatetWithoutRefresh",
|
|
254
253
|
"attributes": {
|
|
255
|
-
"
|
|
254
|
+
"livestore.args": "{
|
|
255
|
+
"sql": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)"
|
|
256
|
+
}",
|
|
257
|
+
"livestore.mutation": "livestore.RawSql",
|
|
256
258
|
},
|
|
259
|
+
"children": [
|
|
260
|
+
{
|
|
261
|
+
"_name": "livestore.in-memory-db:execute",
|
|
262
|
+
"attributes": {
|
|
263
|
+
"sql.query": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0)",
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
],
|
|
257
267
|
},
|
|
258
268
|
],
|
|
259
269
|
},
|
|
@@ -173,6 +173,8 @@ export class LiveStoreSQLQuery<TResult, TQueryInfo extends QueryInfo = QueryInfo
|
|
|
173
173
|
|
|
174
174
|
const durationMs = getDurationMsFromSpan(span)
|
|
175
175
|
|
|
176
|
+
this.executionTimes.push(durationMs)
|
|
177
|
+
|
|
176
178
|
setDebugInfo({ _tag: 'sql', label, query: sqlString, durationMs })
|
|
177
179
|
|
|
178
180
|
return result
|
package/src/row-query.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { shouldNeverHappen } from '@livestore/utils'
|
|
2
|
-
import {
|
|
2
|
+
import { Schema, TreeFormatter } from '@livestore/utils/effect'
|
|
3
3
|
import type * as otel from '@opentelemetry/api'
|
|
4
4
|
import { SqliteAst, SqliteDsl } from 'effect-db-schema'
|
|
5
5
|
|
|
@@ -138,20 +138,19 @@ const insertRowWithDefaultValuesOrIgnore = ({
|
|
|
138
138
|
otelContext: otel.Context
|
|
139
139
|
defaultValues: Partial<RowResult<TableDef>> | undefined
|
|
140
140
|
}) => {
|
|
141
|
-
const
|
|
142
|
-
|
|
141
|
+
const defaultValues = getDefaultValuesEncoded(table, explicitDefaultValues)
|
|
142
|
+
|
|
143
|
+
const defaultColumnNames = [...Object.keys(defaultValues), 'id']
|
|
144
|
+
const columnValues = defaultColumnNames.map((name) => `$${name}`).join(', ')
|
|
143
145
|
|
|
144
146
|
const tableName = table.sqliteDef.name
|
|
145
|
-
const insertQuery = sql`insert into ${tableName} (${
|
|
147
|
+
const insertQuery = sql`insert into ${tableName} (${defaultColumnNames.join(
|
|
146
148
|
', ',
|
|
147
149
|
)}) select ${columnValues} where not exists(select 1 from ${tableName} where id = '${id}')`
|
|
148
150
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
db.execute(insertQuery, prepareBindValues({ ...defaultValues, id }, insertQuery), [tableName], { otelContext })
|
|
151
|
+
db.execute(insertQuery, prepareBindValues({ ...defaultValues, id }, insertQuery), new Set([tableName]), {
|
|
152
|
+
otelContext,
|
|
153
|
+
})
|
|
155
154
|
}
|
|
156
155
|
|
|
157
156
|
const makeExecBeforeFirstRun =
|
package/src/schema/index.ts
CHANGED
|
@@ -1,33 +1,47 @@
|
|
|
1
|
+
import type { ReadonlyArray } from '@livestore/utils/effect'
|
|
1
2
|
import type { SqliteDsl } from 'effect-db-schema'
|
|
2
3
|
|
|
3
|
-
import
|
|
4
|
+
import { isReadonlyArray } from '../utils/util.js'
|
|
5
|
+
import {
|
|
6
|
+
type MutationDef,
|
|
7
|
+
type MutationDefMap,
|
|
8
|
+
type MutationDefRecord,
|
|
9
|
+
type RawSqlMutation,
|
|
10
|
+
rawSqlMutation,
|
|
11
|
+
} from './mutations.js'
|
|
4
12
|
import { systemTables } from './system-tables.js'
|
|
5
13
|
import type { TableDef } from './table-def.js'
|
|
6
14
|
|
|
7
|
-
export * from './action.js'
|
|
8
15
|
export * from './system-tables.js'
|
|
9
16
|
export * as DbSchema from './table-def.js'
|
|
10
17
|
export * as ParseUtils from './parse-utils.js'
|
|
18
|
+
export * from './mutations.js'
|
|
11
19
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
20
|
+
export type LiveStoreSchema<
|
|
21
|
+
TDbSchema extends SqliteDsl.DbSchema = SqliteDsl.DbSchema,
|
|
22
|
+
TMutationsDefRecord extends MutationDefRecord = MutationDefRecord,
|
|
23
|
+
> = {
|
|
15
24
|
/** Only used on type-level */
|
|
16
25
|
readonly _DbSchemaType: TDbSchema
|
|
26
|
+
/** Only used on type-level */
|
|
27
|
+
readonly _MutationDefMapType: TMutationsDefRecord
|
|
17
28
|
|
|
18
29
|
readonly tables: Map<string, TableDef>
|
|
19
|
-
readonly
|
|
30
|
+
readonly mutations: MutationDefMap
|
|
20
31
|
}
|
|
21
32
|
|
|
22
33
|
export type InputSchema = {
|
|
23
|
-
tables: Record<string, TableDef> | ReadonlyArray<TableDef>
|
|
24
|
-
|
|
34
|
+
readonly tables: Record<string, TableDef> | ReadonlyArray<TableDef>
|
|
35
|
+
readonly mutations?: ReadonlyArray<MutationDef.Any> | Record<string, MutationDef.Any>
|
|
25
36
|
}
|
|
26
37
|
|
|
27
38
|
export const makeSchema = <TInputSchema extends InputSchema>(
|
|
28
|
-
/** Note when using the object-notation for tables, the object keys are ignored and not used as table names */
|
|
39
|
+
/** Note when using the object-notation for tables/mutations, the object keys are ignored and not used as table/mutation names */
|
|
29
40
|
schema: TInputSchema,
|
|
30
|
-
): LiveStoreSchema<
|
|
41
|
+
): LiveStoreSchema<
|
|
42
|
+
DbSchemaFromInputSchemaTables<TInputSchema['tables']>,
|
|
43
|
+
MutationDefRecordFromInputSchemaMutations<TInputSchema['mutations']>
|
|
44
|
+
> => {
|
|
31
45
|
const inputTables: ReadonlyArray<TableDef> = Array.isArray(schema.tables)
|
|
32
46
|
? schema.tables
|
|
33
47
|
: // TODO validate that table names are unique in this case
|
|
@@ -44,10 +58,25 @@ export const makeSchema = <TInputSchema extends InputSchema>(
|
|
|
44
58
|
tables.set(tableDef.sqliteDef.name, tableDef)
|
|
45
59
|
}
|
|
46
60
|
|
|
61
|
+
const mutations: MutationDefMap = new Map()
|
|
62
|
+
|
|
63
|
+
if (isReadonlyArray(schema.mutations)) {
|
|
64
|
+
for (const mutation of schema.mutations) {
|
|
65
|
+
mutations.set(mutation.name, mutation)
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
for (const [name, mutation] of Object.entries(schema.mutations ?? {})) {
|
|
69
|
+
mutations.set(name, mutation)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
mutations.set('livestore.RawSql', rawSqlMutation)
|
|
74
|
+
|
|
47
75
|
return {
|
|
48
76
|
_DbSchemaType: Symbol('livestore.DbSchemaType') as any,
|
|
77
|
+
_MutationDefMapType: Symbol('livestore.MutationDefMapType') as any,
|
|
49
78
|
tables,
|
|
50
|
-
|
|
79
|
+
mutations,
|
|
51
80
|
} satisfies LiveStoreSchema
|
|
52
81
|
}
|
|
53
82
|
|
|
@@ -62,3 +91,10 @@ export type DbSchemaFromInputSchemaTables<TTables extends InputSchema['tables']>
|
|
|
62
91
|
: TTables extends Record<string, TableDef>
|
|
63
92
|
? { [K in keyof TTables as TTables[K]['sqliteDef']['name']]: TTables[K]['sqliteDef'] }
|
|
64
93
|
: never
|
|
94
|
+
|
|
95
|
+
export type MutationDefRecordFromInputSchemaMutations<TMutations extends InputSchema['mutations']> =
|
|
96
|
+
TMutations extends ReadonlyArray<MutationDef.Any>
|
|
97
|
+
? { [K in TMutations[number] as K['name']]: K } & { 'livestore.RawSql': RawSqlMutation }
|
|
98
|
+
: TMutations extends { [name: string]: MutationDef.Any }
|
|
99
|
+
? { [K in keyof TMutations as TMutations[K]['name']]: TMutations[K] } & { 'livestore.RawSql': RawSqlMutation }
|
|
100
|
+
: never
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { BindValues } from '@livestore/sql-queries'
|
|
2
|
+
import { cuid } from '@livestore/utils/cuid'
|
|
3
|
+
import { Schema } from '@livestore/utils/effect'
|
|
4
|
+
|
|
5
|
+
import type { LiveStoreSchema } from './index.js'
|
|
6
|
+
|
|
7
|
+
export type MutationDefMap = Map<string | 'livestore.RawSql', MutationDef.Any>
|
|
8
|
+
export type MutationDefRecord = {
|
|
9
|
+
'livestore.RawSql': RawSqlMutation
|
|
10
|
+
[name: string]: MutationDef.Any
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type InternalMutationSchema<TRecord extends MutationDefRecord = MutationDefRecord> = {
|
|
14
|
+
_DefRecord: TRecord
|
|
15
|
+
|
|
16
|
+
map: Map<keyof TRecord, TRecord[keyof TRecord]>
|
|
17
|
+
schemaHashMap: Map<keyof TRecord, number>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type MutationDef<TName extends string, TFrom, TTo> = {
|
|
21
|
+
name: TName
|
|
22
|
+
schema: Schema.Schema<never, TFrom, TTo>
|
|
23
|
+
sql:
|
|
24
|
+
| string
|
|
25
|
+
| ((args: TTo) =>
|
|
26
|
+
| string
|
|
27
|
+
| {
|
|
28
|
+
sql: string
|
|
29
|
+
/** Note args need to be manually encoded to `BindValues` when returning this argument */
|
|
30
|
+
bindValues: BindValues
|
|
31
|
+
writeTables?: ReadonlySet<string>
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
/** Helper function to construct mutation event */
|
|
35
|
+
(args: TTo): { mutation: TName; args: TTo; id: string }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export namespace MutationDef {
|
|
39
|
+
export type Any = MutationDef<string, any, any>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// TODO possibly also allow for mutation event subsumption behaviour
|
|
43
|
+
export const defineMutation = <TName extends string, TFrom, TTo>(
|
|
44
|
+
name: TName,
|
|
45
|
+
schema: Schema.Schema<never, TFrom, TTo>,
|
|
46
|
+
sql: string | ((args: TTo) => string | { sql: string; bindValues: BindValues; writeTables?: ReadonlySet<string> }),
|
|
47
|
+
): MutationDef<TName, TFrom, TTo> => {
|
|
48
|
+
const makeEvent = (args: TTo) => ({ mutation: name, args, id: cuid() })
|
|
49
|
+
|
|
50
|
+
Object.defineProperty(makeEvent, 'name', { value: name })
|
|
51
|
+
Object.defineProperty(makeEvent, 'schema', { value: schema })
|
|
52
|
+
Object.defineProperty(makeEvent, 'sql', { value: sql })
|
|
53
|
+
|
|
54
|
+
return makeEvent as MutationDef<TName, TFrom, TTo>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const makeMutationDefRecord = <TInputRecord extends Record<string, MutationDef.Any>>(
|
|
58
|
+
inputRecord: TInputRecord,
|
|
59
|
+
): {
|
|
60
|
+
[K in TInputRecord[keyof TInputRecord]['name']]: Extract<TInputRecord[keyof TInputRecord], { name: K }>
|
|
61
|
+
} => {
|
|
62
|
+
const result: any = {}
|
|
63
|
+
|
|
64
|
+
for (const [name, def] of Object.entries(inputRecord)) {
|
|
65
|
+
result[name] = def
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
result['livestore.RawSql'] = rawSqlMutation
|
|
69
|
+
|
|
70
|
+
return result
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const rawSqlMutation = defineMutation(
|
|
74
|
+
'livestore.RawSql',
|
|
75
|
+
Schema.struct({
|
|
76
|
+
sql: Schema.string,
|
|
77
|
+
bindValues: Schema.optional(Schema.record(Schema.string, Schema.any)),
|
|
78
|
+
writeTables: Schema.optional(Schema.readonlySet(Schema.string)),
|
|
79
|
+
}),
|
|
80
|
+
({ sql, bindValues, writeTables }) => ({ sql, bindValues: bindValues ?? {}, writeTables }),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
export type RawSqlMutation = typeof rawSqlMutation
|
|
84
|
+
export type RawSqlMutationEvent = ReturnType<typeof rawSqlMutation>
|
|
85
|
+
|
|
86
|
+
export type MutationEvent<TMutationsDef extends MutationDef.Any> = {
|
|
87
|
+
mutation: TMutationsDef['name']
|
|
88
|
+
args: Schema.Schema.To<TMutationsDef['schema']>
|
|
89
|
+
id: string
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export namespace MutationEvent {
|
|
93
|
+
export type Any = MutationEvent<MutationDef.Any>
|
|
94
|
+
|
|
95
|
+
export type ForSchema<TSchema extends LiveStoreSchema> = {
|
|
96
|
+
[K in keyof TSchema['_MutationDefMapType']]: MutationEvent<TSchema['_MutationDefMapType'][K]>
|
|
97
|
+
}[keyof TSchema['_MutationDefMapType']]
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export type MutationEventSchema<TMutationsDefRecord extends MutationDefRecord> = Schema.Schema<
|
|
101
|
+
never,
|
|
102
|
+
{
|
|
103
|
+
[K in keyof TMutationsDefRecord]: {
|
|
104
|
+
mutation: K
|
|
105
|
+
args: Schema.Schema.From<TMutationsDefRecord[K]['schema']>
|
|
106
|
+
id: string
|
|
107
|
+
}
|
|
108
|
+
}[keyof TMutationsDefRecord],
|
|
109
|
+
{
|
|
110
|
+
[K in keyof TMutationsDefRecord]: {
|
|
111
|
+
mutation: K
|
|
112
|
+
args: Schema.Schema.To<TMutationsDefRecord[K]['schema']>
|
|
113
|
+
id: string
|
|
114
|
+
}
|
|
115
|
+
}[keyof TMutationsDefRecord]
|
|
116
|
+
>
|
|
117
|
+
|
|
118
|
+
export const makeMutationEventSchema = <TMutationsDefRecord extends MutationDefRecord>(
|
|
119
|
+
mutationDefRecord: TMutationsDefRecord,
|
|
120
|
+
): MutationEventSchema<TMutationsDefRecord> =>
|
|
121
|
+
Schema.union(
|
|
122
|
+
...Object.values(mutationDefRecord).map((def) =>
|
|
123
|
+
Schema.struct({
|
|
124
|
+
mutation: Schema.literal(def.name),
|
|
125
|
+
args: def.schema,
|
|
126
|
+
id: Schema.string,
|
|
127
|
+
}),
|
|
128
|
+
),
|
|
129
|
+
) as any
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { shouldNeverHappen } from '@livestore/utils'
|
|
2
2
|
import type { ReadonlyArray } from '@livestore/utils/effect'
|
|
3
3
|
import { pipe, ReadonlyRecord, Schema, TreeFormatter } from '@livestore/utils/effect'
|
|
4
|
-
import { SqliteDsl
|
|
4
|
+
import { SqliteDsl } from 'effect-db-schema' // eslint-disable-line
|
|
5
5
|
|
|
6
6
|
import { type FromColumns, type FromTable, getDefaultValuesDecoded, type TableDef } from './table-def.js'
|
|
7
7
|
|
package/src/schema/table-def.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { SqliteAst, SqliteDsl } from 'effect-db-schema'
|
|
|
5
5
|
|
|
6
6
|
export const { blob, boolean, column, datetime, integer, isColumnDefinition, json, real, text } = SqliteDsl
|
|
7
7
|
|
|
8
|
-
export { type SqliteDsl
|
|
8
|
+
export { type SqliteDsl } from 'effect-db-schema'
|
|
9
9
|
|
|
10
10
|
import { dynamicallyRegisteredTables } from '../global-state.js'
|
|
11
11
|
|
|
@@ -156,16 +156,25 @@ export const tableIsSingleton = <TTableDef extends TableDef>(
|
|
|
156
156
|
tableDef: TTableDef,
|
|
157
157
|
): tableDef is TTableDef & { options: { isSingleton: true } } => tableDef.options.isSingleton === true
|
|
158
158
|
|
|
159
|
-
export const getDefaultValuesEncoded = <TTableDef extends TableDef>(
|
|
159
|
+
export const getDefaultValuesEncoded = <TTableDef extends TableDef>(
|
|
160
|
+
tableDef: TTableDef,
|
|
161
|
+
fallbackValues?: Record<string, any>,
|
|
162
|
+
) =>
|
|
160
163
|
pipe(
|
|
161
164
|
tableDef.sqliteDef.columns,
|
|
162
|
-
ReadonlyRecord.filter((
|
|
165
|
+
ReadonlyRecord.filter((col, key) => {
|
|
166
|
+
if (fallbackValues?.[key] !== undefined) return true
|
|
167
|
+
if (key === 'id') return false
|
|
168
|
+
return col!.default._tag === 'None' || SqliteDsl.isSqlDefaultValue(col!.default.value) === false
|
|
169
|
+
}),
|
|
163
170
|
ReadonlyRecord.map((column, columnName) =>
|
|
164
|
-
|
|
165
|
-
? column!.
|
|
166
|
-
?
|
|
167
|
-
|
|
168
|
-
|
|
171
|
+
fallbackValues?.[columnName] === undefined
|
|
172
|
+
? column!.default._tag === 'None'
|
|
173
|
+
? column!.nullable === true
|
|
174
|
+
? null
|
|
175
|
+
: shouldNeverHappen(`Column ${columnName} has no default value and is not nullable`)
|
|
176
|
+
: Schema.encodeSync(column!.schema)(column!.default.value)
|
|
177
|
+
: fallbackValues[columnName],
|
|
169
178
|
),
|
|
170
179
|
)
|
|
171
180
|
|
|
@@ -173,6 +182,9 @@ export const getDefaultValuesDecoded = <TTableDef extends TableDef>(tableDef: TT
|
|
|
173
182
|
pipe(
|
|
174
183
|
tableDef.sqliteDef.columns,
|
|
175
184
|
ReadonlyRecord.filter((_, key) => key !== 'id'),
|
|
185
|
+
ReadonlyRecord.filter(
|
|
186
|
+
(col) => col!.default._tag === 'None' || SqliteDsl.isSqlDefaultValue(col!.default.value) === false,
|
|
187
|
+
),
|
|
176
188
|
ReadonlyRecord.map((column, columnName) =>
|
|
177
189
|
column!.default._tag === 'None'
|
|
178
190
|
? column!.nullable === true
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type * as otel from '@opentelemetry/api'
|
|
2
2
|
|
|
3
|
+
import type { MutationEvent } from '../../index.js'
|
|
3
4
|
import type { PreparedBindValues } from '../../utils/util.js'
|
|
4
5
|
import type { Storage, StorageOtelProps } from '../index.js'
|
|
5
6
|
|
|
@@ -17,5 +18,11 @@ export class InMemoryStorage implements Storage {
|
|
|
17
18
|
|
|
18
19
|
execute = (_query: string, _bindValues?: PreparedBindValues): void => {}
|
|
19
20
|
|
|
21
|
+
mutate = (_mutationEventEncoded: MutationEvent.Any, _parentSpan?: otel.Span | undefined) => {}
|
|
22
|
+
|
|
20
23
|
getPersistedData = async (): Promise<Uint8Array> => new Uint8Array()
|
|
24
|
+
|
|
25
|
+
getMutationLogData = async (): Promise<Uint8Array> => new Uint8Array()
|
|
26
|
+
|
|
27
|
+
dangerouslyReset = async () => {}
|
|
21
28
|
}
|
package/src/storage/index.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import type * as otel from '@opentelemetry/api'
|
|
10
10
|
|
|
11
|
+
import type { MutationEvent } from '../index.js'
|
|
11
12
|
import type { PreparedBindValues } from '../utils/util.js'
|
|
12
13
|
|
|
13
14
|
export type StorageInit = (otelProps: StorageOtelProps) => Promise<Storage> | Storage
|
|
@@ -16,8 +17,15 @@ export interface Storage {
|
|
|
16
17
|
// TODO consider transferables for `bindValues` (e.g. Uint8Array values)
|
|
17
18
|
execute(query: string, bindValues?: PreparedBindValues, parentSpan?: otel.Span): void
|
|
18
19
|
|
|
20
|
+
// TODO consider transferables for `bindValues` (e.g. Uint8Array values)
|
|
21
|
+
mutate(mutationEventEncoded: MutationEvent.Any, parentSpan?: otel.Span): void
|
|
22
|
+
|
|
19
23
|
/** Return a snapshot of persisted data from the storage */
|
|
20
24
|
getPersistedData(parentSpan?: otel.Span): Promise<Uint8Array>
|
|
25
|
+
|
|
26
|
+
getMutationLogData(parentSpan?: otel.Span): Promise<Uint8Array>
|
|
27
|
+
|
|
28
|
+
dangerouslyReset(): Promise<void>
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
export type StorageType = 'tauri' | 'web' | 'web-in-memory'
|
|
@@ -2,6 +2,7 @@ import { getTraceParentHeader } from '@livestore/utils'
|
|
|
2
2
|
import type * as otel from '@opentelemetry/api'
|
|
3
3
|
import { invoke } from '@tauri-apps/api'
|
|
4
4
|
|
|
5
|
+
import type { MutationEvent } from '../../schema/mutations.js'
|
|
5
6
|
import type { PreparedBindValues } from '../../utils/util.js'
|
|
6
7
|
import { prepareBindValues } from '../../utils/util.js'
|
|
7
8
|
import type { Storage, StorageOtelProps } from '../index.js'
|
|
@@ -38,6 +39,8 @@ export class TauriStorage implements Storage {
|
|
|
38
39
|
})
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
mutate = (_mutationEventEncoded: MutationEvent.Any, _parentSpan?: otel.Span | undefined) => {}
|
|
43
|
+
|
|
41
44
|
getPersistedData = async (parentSpan?: otel.Span): Promise<Uint8Array> => {
|
|
42
45
|
const headers = new Headers()
|
|
43
46
|
headers.set('traceparent', getTraceParentHeader(parentSpan ?? this.parentSpan))
|
|
@@ -47,6 +50,13 @@ export class TauriStorage implements Storage {
|
|
|
47
50
|
)
|
|
48
51
|
}
|
|
49
52
|
|
|
53
|
+
// TODO
|
|
54
|
+
getMutationLogData = async (): Promise<Uint8Array> => new Uint8Array()
|
|
55
|
+
|
|
56
|
+
dangerouslyReset = async () => {
|
|
57
|
+
// TODO
|
|
58
|
+
}
|
|
59
|
+
|
|
50
60
|
private getOtelData = (parentSpan?: otel.Span) => getOtelData_(parentSpan ?? this.parentSpan)!
|
|
51
61
|
}
|
|
52
62
|
|
package/src/storage/utils/idb.ts
CHANGED
|
@@ -68,4 +68,18 @@ export class IDB {
|
|
|
68
68
|
}
|
|
69
69
|
})
|
|
70
70
|
}
|
|
71
|
+
|
|
72
|
+
public async deleteDb(): Promise<void> {
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
const deleteRequest = indexedDB.deleteDatabase(this.dbName)
|
|
75
|
+
|
|
76
|
+
deleteRequest.onsuccess = () => {
|
|
77
|
+
resolve()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
deleteRequest.onerror = () => {
|
|
81
|
+
reject(new Error('Failed to delete database.'))
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
}
|
|
71
85
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { MutationEvent } from '../../schema/mutations.js'
|
|
2
|
+
import type { PreparedBindValues } from '../../utils/util.js'
|
|
3
|
+
|
|
4
|
+
export type ExecutionBacklogItem =
|
|
5
|
+
| { _tag: 'execute'; query: string; bindValues?: PreparedBindValues }
|
|
6
|
+
| { _tag: 'mutate'; mutationEventEncoded: MutationEvent.Any }
|