@livestore/livestore 0.0.38 → 0.0.39-dev.3

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.
Files changed (117) hide show
  1. package/README.md +3 -4
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/react/fixture.d.ts +97 -3
  4. package/dist/__tests__/react/fixture.d.ts.map +1 -1
  5. package/dist/__tests__/react/fixture.js +10 -7
  6. package/dist/__tests__/react/fixture.js.map +1 -1
  7. package/dist/__tests__/react/useQuery.test.js +12 -23
  8. package/dist/__tests__/react/useQuery.test.js.map +1 -1
  9. package/dist/__tests__/react/useRow.test.js +4 -4
  10. package/dist/__tests__/react/useRow.test.js.map +1 -1
  11. package/dist/__tests__/reactiveQueries/sql.test.js +32 -35
  12. package/dist/__tests__/reactiveQueries/sql.test.js.map +1 -1
  13. package/dist/effect/LiveStore.d.ts +3 -2
  14. package/dist/effect/LiveStore.d.ts.map +1 -1
  15. package/dist/effect/LiveStore.js.map +1 -1
  16. package/dist/index.d.ts +6 -6
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +3 -3
  19. package/dist/index.js.map +1 -1
  20. package/dist/migrations.js +2 -2
  21. package/dist/migrations.js.map +1 -1
  22. package/dist/mutations.d.ts +3 -1
  23. package/dist/mutations.d.ts.map +1 -1
  24. package/dist/mutations.js +2 -2
  25. package/dist/mutations.js.map +1 -1
  26. package/dist/react/LiveStoreContext.d.ts +2 -2
  27. package/dist/react/LiveStoreContext.d.ts.map +1 -1
  28. package/dist/react/LiveStoreContext.js.map +1 -1
  29. package/dist/react/index.d.ts +1 -0
  30. package/dist/react/index.d.ts.map +1 -1
  31. package/dist/react/index.js +1 -0
  32. package/dist/react/index.js.map +1 -1
  33. package/dist/react/useAtom.d.ts +5 -0
  34. package/dist/react/useAtom.d.ts.map +1 -0
  35. package/dist/react/useAtom.js +16 -0
  36. package/dist/react/useAtom.js.map +1 -0
  37. package/dist/react/useQuery.d.ts +3 -3
  38. package/dist/react/useQuery.d.ts.map +1 -1
  39. package/dist/react/useQuery.js.map +1 -1
  40. package/dist/react/useRow.d.ts +5 -5
  41. package/dist/react/useRow.d.ts.map +1 -1
  42. package/dist/react/useRow.js +16 -30
  43. package/dist/react/useRow.js.map +1 -1
  44. package/dist/react/useTemporaryQuery.d.ts +3 -3
  45. package/dist/react/useTemporaryQuery.d.ts.map +1 -1
  46. package/dist/react/useTemporaryQuery.js.map +1 -1
  47. package/dist/reactiveQueries/base-class.d.ts +13 -4
  48. package/dist/reactiveQueries/base-class.d.ts.map +1 -1
  49. package/dist/reactiveQueries/base-class.js +1 -0
  50. package/dist/reactiveQueries/base-class.js.map +1 -1
  51. package/dist/reactiveQueries/graphql.d.ts +5 -5
  52. package/dist/reactiveQueries/graphql.d.ts.map +1 -1
  53. package/dist/reactiveQueries/graphql.js +11 -10
  54. package/dist/reactiveQueries/graphql.js.map +1 -1
  55. package/dist/reactiveQueries/js.d.ts +10 -7
  56. package/dist/reactiveQueries/js.d.ts.map +1 -1
  57. package/dist/reactiveQueries/js.js +19 -11
  58. package/dist/reactiveQueries/js.js.map +1 -1
  59. package/dist/reactiveQueries/sql.d.ts +21 -15
  60. package/dist/reactiveQueries/sql.d.ts.map +1 -1
  61. package/dist/reactiveQueries/sql.js +61 -28
  62. package/dist/reactiveQueries/sql.js.map +1 -1
  63. package/dist/row-query.d.ts +22 -21
  64. package/dist/row-query.d.ts.map +1 -1
  65. package/dist/row-query.js +62 -47
  66. package/dist/row-query.js.map +1 -1
  67. package/dist/schema/index.d.ts +3 -2
  68. package/dist/schema/index.d.ts.map +1 -1
  69. package/dist/schema/index.js +3 -2
  70. package/dist/schema/index.js.map +1 -1
  71. package/dist/schema/parse-utils.d.ts +9 -0
  72. package/dist/schema/parse-utils.d.ts.map +1 -0
  73. package/dist/schema/parse-utils.js +47 -0
  74. package/dist/schema/parse-utils.js.map +1 -0
  75. package/dist/schema/system-tables.d.ts +24 -8
  76. package/dist/schema/system-tables.d.ts.map +1 -1
  77. package/dist/schema/table-def.d.ts +32 -7
  78. package/dist/schema/table-def.d.ts.map +1 -1
  79. package/dist/schema/table-def.js +18 -6
  80. package/dist/schema/table-def.js.map +1 -1
  81. package/dist/store.d.ts +4 -8
  82. package/dist/store.d.ts.map +1 -1
  83. package/dist/store.js +7 -8
  84. package/dist/store.js.map +1 -1
  85. package/dist/update-path.d.ts +52 -0
  86. package/dist/update-path.d.ts.map +1 -0
  87. package/dist/update-path.js +33 -0
  88. package/dist/update-path.js.map +1 -0
  89. package/dist/utils/util.d.ts +1 -0
  90. package/dist/utils/util.d.ts.map +1 -1
  91. package/dist/utils/util.js.map +1 -1
  92. package/package.json +4 -4
  93. package/src/__tests__/react/fixture.tsx +13 -7
  94. package/src/__tests__/react/useQuery.test.tsx +12 -29
  95. package/src/__tests__/react/useRow.test.tsx +5 -7
  96. package/src/__tests__/reactiveQueries/sql.test.ts +33 -35
  97. package/src/effect/LiveStore.ts +3 -2
  98. package/src/index.ts +6 -6
  99. package/src/migrations.ts +2 -2
  100. package/src/mutations.ts +8 -3
  101. package/src/react/LiveStoreContext.ts +3 -2
  102. package/src/react/index.ts +1 -0
  103. package/src/react/useAtom.ts +25 -0
  104. package/src/react/useQuery.ts +7 -7
  105. package/src/react/useRow.ts +27 -47
  106. package/src/react/useTemporaryQuery.ts +4 -6
  107. package/src/reactiveQueries/base-class.ts +20 -4
  108. package/src/reactiveQueries/graphql.ts +16 -14
  109. package/src/reactiveQueries/js.ts +36 -15
  110. package/src/reactiveQueries/sql.ts +87 -37
  111. package/src/row-query.ts +155 -113
  112. package/src/schema/index.ts +5 -4
  113. package/src/schema/parse-utils.ts +84 -0
  114. package/src/schema/table-def.ts +80 -12
  115. package/src/store.ts +14 -29
  116. package/src/update-path.ts +102 -0
  117. package/src/utils/util.ts +2 -0
@@ -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
- TTableDef extends DefaultSqliteTableDef = DefaultSqliteTableDef,
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
- schema: TTableDef
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 schema = SqliteDsl.table(tablePath, columns, options?.indexes ?? [])
125
+ const sqliteDef = SqliteDsl.table(tablePath, columns, options?.indexes ?? [])
89
126
 
90
127
  if (options_.isSingleton) {
91
- for (const column of schema.ast.columns) {
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 tableDef = { schema, isSingleColumn, options: options_ }
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)!.schema.ast) !== SqliteAst.hash(schema.ast)) {
106
- console.error('previous tableDef', dynamicallyRegisteredTables.get(tablePath), 'new tableDef', schema.ast)
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['schema']['columns']
209
+ TTableDef['sqliteDef']['columns']
142
210
  >
143
211
 
144
212
  export type Columns<TTableDef extends TableDef> = {
145
- [K in keyof TTableDef['schema']['columns']]: TTableDef['schema']['columns'][K]['columnType']
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['schema']['columns']]: Schema.Schema.From<TTableDef['schema']['columns'][K]['schema']>
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['schema']['columns']]: Schema.Schema.To<TTableDef['schema']['columns'][K]['schema']>
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, ILiveStoreQuery } from './reactiveQueries/base-class.js'
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<LiveStoreQuery>
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((_) => _.schema.name),
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: ILiveStoreQuery<TResult>,
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 LiveStoreQuery)
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 LiveStoreQuery)
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
@@ -2,6 +2,8 @@
2
2
 
3
3
  import type { Brand } from '@livestore/utils/effect'
4
4
 
5
+ export type GetValForKey<T, K> = K extends keyof T ? T[K] : never
6
+
5
7
  export type ParamsObject = Record<string, SqlValue>
6
8
  export type SqlValue = string | number | Uint8Array | null
7
9