@livestore/livestore 0.0.39 → 0.0.40
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} +15 -7
- 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 +7 -5
- 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 +1 -1
- package/dist/schema/table-def.d.ts.map +1 -1
- package/dist/schema/table-def.js +2 -2
- 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} +28 -22
- 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 +11 -9
- 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 +7 -1
- 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
|
@@ -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
|
@@ -138,20 +138,22 @@ const insertRowWithDefaultValuesOrIgnore = ({
|
|
|
138
138
|
otelContext: otel.Context
|
|
139
139
|
defaultValues: Partial<RowResult<TableDef>> | undefined
|
|
140
140
|
}) => {
|
|
141
|
-
const columnNames = Object.keys(table.sqliteDef.columns)
|
|
142
|
-
const columnValues = columnNames.map((name) => `$${name}`).join(', ')
|
|
143
|
-
|
|
144
|
-
const tableName = table.sqliteDef.name
|
|
145
|
-
const insertQuery = sql`insert into ${tableName} (${columnNames.join(
|
|
146
|
-
', ',
|
|
147
|
-
)}) select ${columnValues} where not exists(select 1 from ${tableName} where id = '${id}')`
|
|
148
|
-
|
|
149
141
|
const defaultValues = pipe(
|
|
150
142
|
getDefaultValuesEncoded(table),
|
|
151
143
|
ReadonlyRecord.map((val, columnName) => explicitDefaultValues?.[columnName] ?? val),
|
|
152
144
|
)
|
|
153
145
|
|
|
154
|
-
|
|
146
|
+
const defaultColumnNames = [...Object.keys(defaultValues), 'id']
|
|
147
|
+
const columnValues = defaultColumnNames.map((name) => `$${name}`).join(', ')
|
|
148
|
+
|
|
149
|
+
const tableName = table.sqliteDef.name
|
|
150
|
+
const insertQuery = sql`insert into ${tableName} (${defaultColumnNames.join(
|
|
151
|
+
', ',
|
|
152
|
+
)}) select ${columnValues} where not exists(select 1 from ${tableName} where id = '${id}')`
|
|
153
|
+
|
|
154
|
+
db.execute(insertQuery, prepareBindValues({ ...defaultValues, id }, insertQuery), new Set([tableName]), {
|
|
155
|
+
otelContext,
|
|
156
|
+
})
|
|
155
157
|
}
|
|
156
158
|
|
|
157
159
|
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
|
|
|
@@ -160,6 +160,9 @@ export const getDefaultValuesEncoded = <TTableDef extends TableDef>(tableDef: TT
|
|
|
160
160
|
pipe(
|
|
161
161
|
tableDef.sqliteDef.columns,
|
|
162
162
|
ReadonlyRecord.filter((_, key) => key !== 'id'),
|
|
163
|
+
ReadonlyRecord.filter(
|
|
164
|
+
(col) => col!.default._tag === 'None' || SqliteDsl.isSqlDefaultValue(col!.default.value) === false,
|
|
165
|
+
),
|
|
163
166
|
ReadonlyRecord.map((column, columnName) =>
|
|
164
167
|
column!.default._tag === 'None'
|
|
165
168
|
? column!.nullable === true
|
|
@@ -173,6 +176,9 @@ export const getDefaultValuesDecoded = <TTableDef extends TableDef>(tableDef: TT
|
|
|
173
176
|
pipe(
|
|
174
177
|
tableDef.sqliteDef.columns,
|
|
175
178
|
ReadonlyRecord.filter((_, key) => key !== 'id'),
|
|
179
|
+
ReadonlyRecord.filter(
|
|
180
|
+
(col) => col!.default._tag === 'None' || SqliteDsl.isSqlDefaultValue(col!.default.value) === false,
|
|
181
|
+
),
|
|
176
182
|
ReadonlyRecord.map((column, columnName) =>
|
|
177
183
|
column!.default._tag === 'None'
|
|
178
184
|
? 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 }
|