@livestore/livestore 0.0.38 → 0.0.39-dev.2
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 +3 -4
- package/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/react/fixture.d.ts +97 -3
- package/dist/__tests__/react/fixture.d.ts.map +1 -1
- package/dist/__tests__/react/fixture.js +10 -7
- package/dist/__tests__/react/fixture.js.map +1 -1
- package/dist/__tests__/react/useQuery.test.js +12 -23
- package/dist/__tests__/react/useQuery.test.js.map +1 -1
- package/dist/__tests__/react/useRow.test.js +4 -4
- package/dist/__tests__/react/useRow.test.js.map +1 -1
- package/dist/__tests__/reactiveQueries/sql.test.js +32 -35
- package/dist/__tests__/reactiveQueries/sql.test.js.map +1 -1
- package/dist/effect/LiveStore.d.ts +3 -2
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/migrations.js +2 -2
- package/dist/migrations.js.map +1 -1
- package/dist/mutations.d.ts +3 -1
- package/dist/mutations.d.ts.map +1 -1
- package/dist/mutations.js +2 -2
- package/dist/mutations.js.map +1 -1
- package/dist/react/LiveStoreContext.d.ts +2 -2
- package/dist/react/LiveStoreContext.d.ts.map +1 -1
- package/dist/react/LiveStoreContext.js.map +1 -1
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/useAtom.d.ts +5 -0
- package/dist/react/useAtom.d.ts.map +1 -0
- package/dist/react/useAtom.js +16 -0
- package/dist/react/useAtom.js.map +1 -0
- package/dist/react/useQuery.d.ts +3 -3
- package/dist/react/useQuery.d.ts.map +1 -1
- package/dist/react/useQuery.js.map +1 -1
- package/dist/react/useRow.d.ts +5 -5
- package/dist/react/useRow.d.ts.map +1 -1
- package/dist/react/useRow.js +16 -30
- package/dist/react/useRow.js.map +1 -1
- package/dist/react/useTemporaryQuery.d.ts +3 -3
- package/dist/react/useTemporaryQuery.d.ts.map +1 -1
- package/dist/react/useTemporaryQuery.js.map +1 -1
- package/dist/reactiveQueries/base-class.d.ts +12 -4
- 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 +5 -5
- package/dist/reactiveQueries/graphql.d.ts.map +1 -1
- package/dist/reactiveQueries/graphql.js +11 -10
- package/dist/reactiveQueries/graphql.js.map +1 -1
- package/dist/reactiveQueries/js.d.ts +10 -7
- package/dist/reactiveQueries/js.d.ts.map +1 -1
- package/dist/reactiveQueries/js.js +19 -11
- package/dist/reactiveQueries/js.js.map +1 -1
- package/dist/reactiveQueries/sql.d.ts +21 -15
- package/dist/reactiveQueries/sql.d.ts.map +1 -1
- package/dist/reactiveQueries/sql.js +50 -28
- package/dist/reactiveQueries/sql.js.map +1 -1
- package/dist/row-query.d.ts +22 -21
- package/dist/row-query.d.ts.map +1 -1
- package/dist/row-query.js +62 -47
- package/dist/row-query.js.map +1 -1
- package/dist/schema/index.d.ts +3 -2
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +3 -2
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/parse-utils.d.ts +6 -0
- package/dist/schema/parse-utils.d.ts.map +1 -0
- package/dist/schema/parse-utils.js +59 -0
- package/dist/schema/parse-utils.js.map +1 -0
- package/dist/schema/system-tables.d.ts +24 -8
- package/dist/schema/system-tables.d.ts.map +1 -1
- package/dist/schema/table-def.d.ts +32 -7
- package/dist/schema/table-def.d.ts.map +1 -1
- package/dist/schema/table-def.js +18 -6
- package/dist/schema/table-def.js.map +1 -1
- package/dist/store.d.ts +4 -8
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +7 -8
- package/dist/store.js.map +1 -1
- package/dist/update-path.d.ts +52 -0
- package/dist/update-path.d.ts.map +1 -0
- package/dist/update-path.js +33 -0
- package/dist/update-path.js.map +1 -0
- package/dist/utils/util.d.ts +1 -0
- package/dist/utils/util.d.ts.map +1 -1
- package/dist/utils/util.js.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/react/fixture.tsx +13 -7
- package/src/__tests__/react/useQuery.test.tsx +12 -29
- package/src/__tests__/react/useRow.test.tsx +5 -7
- package/src/__tests__/reactiveQueries/sql.test.ts +33 -35
- package/src/effect/LiveStore.ts +3 -2
- package/src/index.ts +6 -6
- package/src/migrations.ts +2 -2
- package/src/mutations.ts +8 -3
- package/src/react/LiveStoreContext.ts +3 -2
- package/src/react/index.ts +1 -0
- package/src/react/useAtom.ts +25 -0
- package/src/react/useQuery.ts +7 -7
- package/src/react/useRow.ts +27 -47
- package/src/react/useTemporaryQuery.ts +4 -6
- package/src/reactiveQueries/base-class.ts +18 -4
- package/src/reactiveQueries/graphql.ts +16 -14
- package/src/reactiveQueries/js.ts +36 -15
- package/src/reactiveQueries/sql.ts +77 -37
- package/src/row-query.ts +155 -113
- package/src/schema/index.ts +5 -4
- package/src/schema/parse-utils.ts +84 -0
- package/src/schema/table-def.ts +80 -12
- package/src/store.ts +14 -29
- package/src/update-path.ts +102 -0
- package/src/utils/util.ts +2 -0
package/src/schema/table-def.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { shouldNeverHappen } from '@livestore/utils'
|
|
2
|
-
import { ReadonlyRecord, Schema } from '@livestore/utils/effect'
|
|
2
|
+
import { pipe, ReadonlyRecord, Schema } from '@livestore/utils/effect'
|
|
3
3
|
import type { Nullable, PrettifyFlat } from 'effect-db-schema'
|
|
4
4
|
import { SqliteAst, SqliteDsl } from 'effect-db-schema'
|
|
5
5
|
|
|
@@ -12,15 +12,52 @@ import { dynamicallyRegisteredTables } from '../global-state.js'
|
|
|
12
12
|
export type StateType = 'singleton' | 'dynamic'
|
|
13
13
|
|
|
14
14
|
export type DefaultSqliteTableDef = SqliteDsl.TableDefinition<string, SqliteDsl.Columns>
|
|
15
|
+
export type DefaultSqliteTableDefConstrained = SqliteDsl.TableDefinition<string, SqliteDsl.ConstraintColumns>
|
|
16
|
+
|
|
17
|
+
// export type TableDefConstraint<
|
|
18
|
+
// TSqliteDef extends DefaultSqliteTableDef = DefaultSqliteTableDef,
|
|
19
|
+
// TIsSingleColumn extends boolean = boolean,
|
|
20
|
+
// TOptions extends TableOptions = TableOptions,
|
|
21
|
+
// > = TableDefBase<TSqliteDef, TIsSingleColumn, TOptions> & { schema: Schema.Schema<any, any> }
|
|
22
|
+
|
|
23
|
+
// /**
|
|
24
|
+
// * NOTE in the past we used to have a single `TableDef` but there are some TS issues when indroducing
|
|
25
|
+
// * `schema: SqliteDsl.StructSchemaForColumns<TSqliteDef>` so we split it into two types
|
|
26
|
+
// * and only use `TableDefConstraint` in some places
|
|
27
|
+
// */
|
|
28
|
+
// export type TableDefBase<
|
|
29
|
+
// TSqliteDef extends DefaultSqliteTableDef = DefaultSqliteTableDef,
|
|
30
|
+
// TIsSingleColumn extends boolean = boolean,
|
|
31
|
+
// TOptions extends TableOptions = TableOptions,
|
|
32
|
+
// > = {
|
|
33
|
+
// sqliteDef: TSqliteDef
|
|
34
|
+
// // schema: SqliteDsl.StructSchemaForColumns<TSqliteDef>
|
|
35
|
+
// // schema: any;
|
|
36
|
+
// isSingleColumn: TIsSingleColumn
|
|
37
|
+
// options: TOptions
|
|
38
|
+
// }
|
|
15
39
|
|
|
16
40
|
export type TableDef<
|
|
17
|
-
|
|
41
|
+
TSqliteDef extends DefaultSqliteTableDef = DefaultSqliteTableDefConstrained,
|
|
18
42
|
TIsSingleColumn extends boolean = boolean,
|
|
19
43
|
TOptions extends TableOptions = TableOptions,
|
|
44
|
+
// NOTE we're not using `SqliteDsl.StructSchemaForColumns<TSqliteDef['columns']>`
|
|
45
|
+
// as we don't want the alias type for users to show up
|
|
46
|
+
TSchema = Schema.Schema<
|
|
47
|
+
SqliteDsl.AnyIfConstained<
|
|
48
|
+
TSqliteDef['columns'],
|
|
49
|
+
{ readonly [K in keyof TSqliteDef['columns']]: Schema.Schema.From<TSqliteDef['columns'][K]['schema']> }
|
|
50
|
+
>,
|
|
51
|
+
SqliteDsl.AnyIfConstained<
|
|
52
|
+
TSqliteDef['columns'],
|
|
53
|
+
{ readonly [K in keyof TSqliteDef['columns']]: Schema.Schema.To<TSqliteDef['columns'][K]['schema']> }
|
|
54
|
+
>
|
|
55
|
+
>,
|
|
20
56
|
> = {
|
|
21
|
-
|
|
57
|
+
sqliteDef: TSqliteDef
|
|
22
58
|
isSingleColumn: TIsSingleColumn
|
|
23
59
|
options: TOptions
|
|
60
|
+
schema: TSchema
|
|
24
61
|
}
|
|
25
62
|
|
|
26
63
|
export type TableOptionsInput = Partial<TableOptions & { indexes: SqliteDsl.Index[] }>
|
|
@@ -85,10 +122,10 @@ export const table = <
|
|
|
85
122
|
}
|
|
86
123
|
}
|
|
87
124
|
|
|
88
|
-
const
|
|
125
|
+
const sqliteDef = SqliteDsl.table(tablePath, columns, options?.indexes ?? [])
|
|
89
126
|
|
|
90
127
|
if (options_.isSingleton) {
|
|
91
|
-
for (const column of
|
|
128
|
+
for (const column of sqliteDef.ast.columns) {
|
|
92
129
|
if (column.nullable === false && column.default._tag === 'None') {
|
|
93
130
|
shouldNeverHappen(
|
|
94
131
|
`When creating a singleton table, each column must be either nullable or have a default value. Column '${column.name}' is neither.`,
|
|
@@ -99,11 +136,12 @@ export const table = <
|
|
|
99
136
|
|
|
100
137
|
const isSingleColumn = SqliteDsl.isColumnDefinition(columnOrColumns) === true
|
|
101
138
|
|
|
102
|
-
const
|
|
139
|
+
const schema = SqliteDsl.structSchemaForTable(sqliteDef)
|
|
140
|
+
const tableDef = { sqliteDef, isSingleColumn, options: options_, schema } satisfies TableDef
|
|
103
141
|
|
|
104
142
|
if (dynamicallyRegisteredTables.has(tablePath)) {
|
|
105
|
-
if (SqliteAst.hash(dynamicallyRegisteredTables.get(tablePath)!.
|
|
106
|
-
console.error('previous tableDef', dynamicallyRegisteredTables.get(tablePath), 'new tableDef',
|
|
143
|
+
if (SqliteAst.hash(dynamicallyRegisteredTables.get(tablePath)!.sqliteDef.ast) !== SqliteAst.hash(sqliteDef.ast)) {
|
|
144
|
+
console.error('previous tableDef', dynamicallyRegisteredTables.get(tablePath), 'new tableDef', sqliteDef.ast)
|
|
107
145
|
shouldNeverHappen(`Table with name "${name}" was already previously defined with a different definition`)
|
|
108
146
|
}
|
|
109
147
|
} else {
|
|
@@ -113,6 +151,36 @@ export const table = <
|
|
|
113
151
|
return tableDef as any
|
|
114
152
|
}
|
|
115
153
|
|
|
154
|
+
export const tableIsSingleton = <TTableDef extends TableDef>(
|
|
155
|
+
tableDef: TTableDef,
|
|
156
|
+
): tableDef is TTableDef & { options: { isSingleton: true } } => tableDef.options.isSingleton === true
|
|
157
|
+
|
|
158
|
+
export const getDefaultValuesEncoded = <TTableDef extends TableDef>(tableDef: TTableDef) =>
|
|
159
|
+
pipe(
|
|
160
|
+
tableDef.sqliteDef.columns,
|
|
161
|
+
ReadonlyRecord.filter((_, key) => key !== 'id'),
|
|
162
|
+
ReadonlyRecord.map((column, columnName) =>
|
|
163
|
+
column!.default._tag === 'None'
|
|
164
|
+
? column!.nullable === true
|
|
165
|
+
? null
|
|
166
|
+
: shouldNeverHappen(`Column ${columnName} has no default value and is not nullable`)
|
|
167
|
+
: Schema.encodeSync(column!.schema)(column!.default.value),
|
|
168
|
+
),
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
export const getDefaultValuesDecoded = <TTableDef extends TableDef>(tableDef: TTableDef) =>
|
|
172
|
+
pipe(
|
|
173
|
+
tableDef.sqliteDef.columns,
|
|
174
|
+
ReadonlyRecord.filter((_, key) => key !== 'id'),
|
|
175
|
+
ReadonlyRecord.map((column, columnName) =>
|
|
176
|
+
column!.default._tag === 'None'
|
|
177
|
+
? column!.nullable === true
|
|
178
|
+
? null
|
|
179
|
+
: shouldNeverHappen(`Column ${columnName} has no default value and is not nullable`)
|
|
180
|
+
: Schema.validateSync(column!.schema)(column!.default.value),
|
|
181
|
+
),
|
|
182
|
+
)
|
|
183
|
+
|
|
116
184
|
type WithId<TColumns extends SqliteDsl.Columns, TOptions extends TableOptions> = TColumns &
|
|
117
185
|
(TOptions['disableAutomaticIdColumn'] extends true
|
|
118
186
|
? {}
|
|
@@ -138,15 +206,15 @@ export namespace FromTable {
|
|
|
138
206
|
>
|
|
139
207
|
|
|
140
208
|
export type NullableColumnNames<TTableDef extends TableDef> = FromColumns.NullableColumnNames<
|
|
141
|
-
TTableDef['
|
|
209
|
+
TTableDef['sqliteDef']['columns']
|
|
142
210
|
>
|
|
143
211
|
|
|
144
212
|
export type Columns<TTableDef extends TableDef> = {
|
|
145
|
-
[K in keyof TTableDef['
|
|
213
|
+
[K in keyof TTableDef['sqliteDef']['columns']]: TTableDef['sqliteDef']['columns'][K]['columnType']
|
|
146
214
|
}
|
|
147
215
|
|
|
148
216
|
export type RowEncodeNonNullable<TTableDef extends TableDef> = {
|
|
149
|
-
[K in keyof TTableDef['
|
|
217
|
+
[K in keyof TTableDef['sqliteDef']['columns']]: Schema.Schema.From<TTableDef['sqliteDef']['columns'][K]['schema']>
|
|
150
218
|
}
|
|
151
219
|
|
|
152
220
|
export type RowEncoded<TTableDef extends TableDef> = PrettifyFlat<
|
|
@@ -155,7 +223,7 @@ export namespace FromTable {
|
|
|
155
223
|
>
|
|
156
224
|
|
|
157
225
|
export type RowDecodedAll<TTableDef extends TableDef> = {
|
|
158
|
-
[K in keyof TTableDef['
|
|
226
|
+
[K in keyof TTableDef['sqliteDef']['columns']]: Schema.Schema.To<TTableDef['sqliteDef']['columns'][K]['schema']>
|
|
159
227
|
}
|
|
160
228
|
}
|
|
161
229
|
|
package/src/store.ts
CHANGED
|
@@ -11,35 +11,19 @@ import { InMemoryDatabase } from './inMemoryDatabase.js'
|
|
|
11
11
|
import { migrateDb } from './migrations.js'
|
|
12
12
|
import type { StackInfo } from './react/utils/stack-info.js'
|
|
13
13
|
import type { DebugRefreshReasonBase, ReactiveGraph, Ref } from './reactive.js'
|
|
14
|
-
import type { DbContext, DbGraph,
|
|
15
|
-
import type { LiveStoreGraphQLQuery } from './reactiveQueries/graphql.js'
|
|
16
|
-
import type { LiveStoreJSQuery } from './reactiveQueries/js.js'
|
|
17
|
-
import type { LiveStoreSQLQuery } from './reactiveQueries/sql.js'
|
|
14
|
+
import type { DbContext, DbGraph, LiveQuery } from './reactiveQueries/base-class.js'
|
|
18
15
|
import type { ActionDefinition, GetActionArgs, LiveStoreSchema, SQLWriteStatement } from './schema/index.js'
|
|
19
16
|
import type { Storage, StorageInit } from './storage/index.js'
|
|
20
17
|
import { getDurationMsFromSpan } from './utils/otel.js'
|
|
21
18
|
import type { ParamsObject } from './utils/util.js'
|
|
22
19
|
import { isPromise, prepareBindValues, sql } from './utils/util.js'
|
|
23
20
|
|
|
24
|
-
export type LiveStoreQuery<TResult extends Record<string, any> = any> =
|
|
25
|
-
| LiveStoreSQLQuery<TResult>
|
|
26
|
-
| LiveStoreJSQuery<TResult>
|
|
27
|
-
| LiveStoreGraphQLQuery<TResult, any, any>
|
|
28
|
-
|
|
29
21
|
export type BaseGraphQLContext = {
|
|
30
22
|
queriedTables: Set<string>
|
|
31
23
|
/** Needed by Pothos Otel plugin for resolver tracing to work */
|
|
32
24
|
otelContext?: otel.Context
|
|
33
25
|
}
|
|
34
26
|
|
|
35
|
-
export type QueryResult<TQuery> = TQuery extends LiveStoreSQLQuery<infer R>
|
|
36
|
-
? ReadonlyArray<Readonly<R>>
|
|
37
|
-
: TQuery extends LiveStoreJSQuery<infer S>
|
|
38
|
-
? Readonly<S>
|
|
39
|
-
: TQuery extends LiveStoreGraphQLQuery<infer Result, any, any>
|
|
40
|
-
? Readonly<Result>
|
|
41
|
-
: never
|
|
42
|
-
|
|
43
27
|
export type GraphQLOptions<TContext> = {
|
|
44
28
|
schema: GraphQLSchema
|
|
45
29
|
makeContext: (db: InMemoryDatabase, tracer: otel.Tracer) => TContext
|
|
@@ -120,7 +104,7 @@ export class Store<TGraphQLContext extends BaseGraphQLContext = BaseGraphQLConte
|
|
|
120
104
|
tableRefs: { [key: string]: Ref<null, DbContext, RefreshReason> }
|
|
121
105
|
|
|
122
106
|
/** RC-based set to see which queries are currently subscribed to */
|
|
123
|
-
activeQueries: ReferenceCountedSet<
|
|
107
|
+
activeQueries: ReferenceCountedSet<LiveQuery<any>>
|
|
124
108
|
storage?: Storage
|
|
125
109
|
|
|
126
110
|
private constructor({
|
|
@@ -160,7 +144,7 @@ export class Store<TGraphQLContext extends BaseGraphQLContext = BaseGraphQLConte
|
|
|
160
144
|
const allTableNames = new Set([
|
|
161
145
|
...this.schema.tables.keys(),
|
|
162
146
|
// TODO activate dynamic tables
|
|
163
|
-
...Array.from(dynamicallyRegisteredTables.values()).map((_) => _.
|
|
147
|
+
...Array.from(dynamicallyRegisteredTables.values()).map((_) => _.sqliteDef.name),
|
|
164
148
|
])
|
|
165
149
|
const existingTableRefs = new Map(
|
|
166
150
|
Array.from(this.graph.atoms.values())
|
|
@@ -168,13 +152,7 @@ export class Store<TGraphQLContext extends BaseGraphQLContext = BaseGraphQLConte
|
|
|
168
152
|
.map((_) => [_.label!.slice('tableRef:'.length), _] as const),
|
|
169
153
|
)
|
|
170
154
|
for (const tableName of allTableNames) {
|
|
171
|
-
this.tableRefs[tableName] =
|
|
172
|
-
existingTableRefs.get(tableName) ??
|
|
173
|
-
this.graph.makeRef(null, {
|
|
174
|
-
equal: () => false,
|
|
175
|
-
label: `tableRef:${tableName}`,
|
|
176
|
-
meta: { liveStoreRefType: 'table' },
|
|
177
|
-
})
|
|
155
|
+
this.tableRefs[tableName] = existingTableRefs.get(tableName) ?? this.makeTableRef(tableName)
|
|
178
156
|
}
|
|
179
157
|
|
|
180
158
|
if (graphQLOptions) {
|
|
@@ -202,7 +180,7 @@ export class Store<TGraphQLContext extends BaseGraphQLContext = BaseGraphQLConte
|
|
|
202
180
|
* Returns a function to cancel the subscription.
|
|
203
181
|
*/
|
|
204
182
|
subscribe = <TResult>(
|
|
205
|
-
query:
|
|
183
|
+
query: LiveQuery<TResult, any>,
|
|
206
184
|
onNewValue: (value: TResult) => void,
|
|
207
185
|
onUnsubsubscribe?: () => void,
|
|
208
186
|
options?: { label?: string; otelContext?: otel.Context; skipInitialRun?: boolean } | undefined,
|
|
@@ -217,7 +195,7 @@ export class Store<TGraphQLContext extends BaseGraphQLContext = BaseGraphQLConte
|
|
|
217
195
|
const label = `subscribe:${options?.label}`
|
|
218
196
|
const effect = this.graph.makeEffect((get) => onNewValue(get(query.results$)), { label })
|
|
219
197
|
|
|
220
|
-
this.activeQueries.add(query as
|
|
198
|
+
this.activeQueries.add(query as LiveQuery<TResult>)
|
|
221
199
|
|
|
222
200
|
// Running effect right away to get initial value (unless `skipInitialRun` is set)
|
|
223
201
|
if (options?.skipInitialRun !== true) {
|
|
@@ -227,7 +205,7 @@ export class Store<TGraphQLContext extends BaseGraphQLContext = BaseGraphQLConte
|
|
|
227
205
|
const unsubscribe = () => {
|
|
228
206
|
try {
|
|
229
207
|
this.graph.destroyNode(effect)
|
|
230
|
-
this.activeQueries.remove(query as
|
|
208
|
+
this.activeQueries.remove(query as LiveQuery<TResult>)
|
|
231
209
|
onUnsubsubscribe?.()
|
|
232
210
|
} finally {
|
|
233
211
|
span.end()
|
|
@@ -538,6 +516,13 @@ export class Store<TGraphQLContext extends BaseGraphQLContext = BaseGraphQLConte
|
|
|
538
516
|
select = (query: string, params: ParamsObject = {}) => {
|
|
539
517
|
return this.inMemoryDB.select(query, { bindValues: prepareBindValues(params, query) })
|
|
540
518
|
}
|
|
519
|
+
|
|
520
|
+
makeTableRef = (tableName: string) =>
|
|
521
|
+
this.graph.makeRef(null, {
|
|
522
|
+
equal: () => false,
|
|
523
|
+
label: `tableRef:${tableName}`,
|
|
524
|
+
meta: { liveStoreRefType: 'table' },
|
|
525
|
+
})
|
|
541
526
|
}
|
|
542
527
|
|
|
543
528
|
/** Create a new LiveStore Store */
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { notYetImplemented, shouldNeverHappen } from '@livestore/utils'
|
|
2
|
+
import { Schema } from '@livestore/utils/effect'
|
|
3
|
+
|
|
4
|
+
import type { FromTable, TableDef } from './schema/table-def.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A description of a path to update a value either ...
|
|
8
|
+
* - as a whole row
|
|
9
|
+
* - or a single column value
|
|
10
|
+
* - or a sub value in a JSON column
|
|
11
|
+
*/
|
|
12
|
+
export type UpdatePathDesc<TTableDef extends TableDef = TableDef> =
|
|
13
|
+
| UpdatePathDescNone
|
|
14
|
+
| UpdatePathDescRow<TTableDef>
|
|
15
|
+
| UpdatePathDescColJsonValue<TTableDef, GetJsonColumn<TTableDef>>
|
|
16
|
+
| UpdatePathDescCol<TTableDef, keyof TTableDef['sqliteDef']['columns']>
|
|
17
|
+
|
|
18
|
+
export type UpdatePathDescNone = {
|
|
19
|
+
_tag: 'None'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type UpdatePathDescRow<TTableDef extends TableDef> = {
|
|
23
|
+
_tag: 'Row'
|
|
24
|
+
table: TTableDef
|
|
25
|
+
id: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type UpdatePathDescCol<TTableDef extends TableDef, TColName extends keyof TTableDef['sqliteDef']['columns']> = {
|
|
29
|
+
_tag: 'Col'
|
|
30
|
+
table: TTableDef
|
|
31
|
+
id: string
|
|
32
|
+
column: TColName
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type UpdatePathDescColJsonValue<TTableDef extends TableDef, TColName extends GetJsonColumn<TTableDef>> = {
|
|
36
|
+
_tag: 'ColJsonValue'
|
|
37
|
+
table: TTableDef
|
|
38
|
+
id: string
|
|
39
|
+
column: TColName
|
|
40
|
+
/**
|
|
41
|
+
* example: `$.tabs[3].items[2]` (`$` referring to the column value)
|
|
42
|
+
*/
|
|
43
|
+
jsonPath: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type GetJsonColumn<TTableDef extends TableDef> = keyof {
|
|
47
|
+
[ColName in keyof TTableDef['sqliteDef']['columns'] as TTableDef['sqliteDef']['columns'][ColName]['columnType'] extends 'text'
|
|
48
|
+
? ColName
|
|
49
|
+
: never]: {}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// type GetObjValues<TObj extends {}> = TObj[keyof TObj]
|
|
53
|
+
|
|
54
|
+
export type UpdateValueForPath<TPath extends UpdatePathDesc> = TPath extends { _tag: 'Row' }
|
|
55
|
+
? Partial<FromTable.RowDecodedAll<TPath['table']>>
|
|
56
|
+
: TPath extends { _tag: 'Col' }
|
|
57
|
+
? Schema.Schema.To<TPath['table']['sqliteDef']['columns'][TPath['column']]['schema']>
|
|
58
|
+
: TPath extends { _tag: 'ColJsonValue' }
|
|
59
|
+
? { TODO: true }
|
|
60
|
+
: never
|
|
61
|
+
|
|
62
|
+
export const storeEventForUpdatePath = <TPath extends UpdatePathDesc>(
|
|
63
|
+
updatePath: TPath,
|
|
64
|
+
value: UpdateValueForPath<TPath>,
|
|
65
|
+
): StoreEvent => {
|
|
66
|
+
if (updatePath._tag === 'ColJsonValue' || updatePath._tag === 'None') {
|
|
67
|
+
return notYetImplemented('TODO')
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const sqliteTableDef = updatePath.table.sqliteDef
|
|
71
|
+
const id = updatePath.id
|
|
72
|
+
|
|
73
|
+
const { columnNames, bindValues } = (() => {
|
|
74
|
+
if (updatePath._tag === 'Row') {
|
|
75
|
+
const columnNames = Object.keys(value)
|
|
76
|
+
|
|
77
|
+
const partialStructSchema = updatePath.table.schema.pipe(Schema.pick(...columnNames))
|
|
78
|
+
|
|
79
|
+
// const columnNames = Object.keys(value)
|
|
80
|
+
const bindValues = Schema.encodeSync(partialStructSchema)(value)
|
|
81
|
+
return { columnNames, bindValues }
|
|
82
|
+
} else if (updatePath._tag === 'Col') {
|
|
83
|
+
const columnName = updatePath.column
|
|
84
|
+
const columnSchema =
|
|
85
|
+
sqliteTableDef.columns[columnName]?.schema ?? shouldNeverHappen(`Column ${columnName} not found`)
|
|
86
|
+
const bindValues = { [columnName]: Schema.encodeSync(columnSchema)(value) }
|
|
87
|
+
return { columnNames: [columnName], bindValues }
|
|
88
|
+
} else {
|
|
89
|
+
return shouldNeverHappen()
|
|
90
|
+
}
|
|
91
|
+
})()
|
|
92
|
+
|
|
93
|
+
const updateClause = columnNames.map((columnName) => `${columnName} = $${columnName}`).join(', ')
|
|
94
|
+
|
|
95
|
+
const whereClause = `where id = '${id}'`
|
|
96
|
+
const sql = `UPDATE ${sqliteTableDef.name} SET ${updateClause} ${whereClause}`
|
|
97
|
+
const writeTables = new Set<string>([updatePath.table.sqliteDef.name])
|
|
98
|
+
|
|
99
|
+
return { eventType: 'livestore.RawSql', args: { sql, bindValues, writeTables } }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
type StoreEvent = { eventType: string; args: any }
|
package/src/utils/util.ts
CHANGED