@livestore/common 0.0.0-snapshot-909cdd1ac2fd591945c2be2b0f53e14d87f3c9d4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/__tests__/fixture.d.ts +72 -0
  3. package/dist/__tests__/fixture.d.ts.map +1 -0
  4. package/dist/__tests__/fixture.js +16 -0
  5. package/dist/__tests__/fixture.js.map +1 -0
  6. package/dist/adapter-types.d.ts +202 -0
  7. package/dist/adapter-types.d.ts.map +1 -0
  8. package/dist/adapter-types.js +49 -0
  9. package/dist/adapter-types.js.map +1 -0
  10. package/dist/bounded-collections.d.ts +36 -0
  11. package/dist/bounded-collections.d.ts.map +1 -0
  12. package/dist/bounded-collections.js +98 -0
  13. package/dist/bounded-collections.js.map +1 -0
  14. package/dist/debug-info.d.ts +122 -0
  15. package/dist/debug-info.d.ts.map +1 -0
  16. package/dist/debug-info.js +47 -0
  17. package/dist/debug-info.js.map +1 -0
  18. package/dist/derived-mutations.d.ts +109 -0
  19. package/dist/derived-mutations.d.ts.map +1 -0
  20. package/dist/derived-mutations.js +54 -0
  21. package/dist/derived-mutations.js.map +1 -0
  22. package/dist/derived-mutations.test.d.ts +2 -0
  23. package/dist/derived-mutations.test.d.ts.map +1 -0
  24. package/dist/derived-mutations.test.js +93 -0
  25. package/dist/derived-mutations.test.js.map +1 -0
  26. package/dist/devtools/devtools-bridge.d.ts +12 -0
  27. package/dist/devtools/devtools-bridge.d.ts.map +1 -0
  28. package/dist/devtools/devtools-bridge.js +2 -0
  29. package/dist/devtools/devtools-bridge.js.map +1 -0
  30. package/dist/devtools/devtools-messages.d.ts +705 -0
  31. package/dist/devtools/devtools-messages.d.ts.map +1 -0
  32. package/dist/devtools/devtools-messages.js +178 -0
  33. package/dist/devtools/devtools-messages.js.map +1 -0
  34. package/dist/devtools/devtools-window-message.d.ts +29 -0
  35. package/dist/devtools/devtools-window-message.d.ts.map +1 -0
  36. package/dist/devtools/devtools-window-message.js +33 -0
  37. package/dist/devtools/devtools-window-message.js.map +1 -0
  38. package/dist/devtools/index.d.ts +42 -0
  39. package/dist/devtools/index.d.ts.map +1 -0
  40. package/dist/devtools/index.js +49 -0
  41. package/dist/devtools/index.js.map +1 -0
  42. package/dist/index.d.ts +19 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +15 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/init-singleton-tables.d.ts +4 -0
  47. package/dist/init-singleton-tables.d.ts.map +1 -0
  48. package/dist/init-singleton-tables.js +16 -0
  49. package/dist/init-singleton-tables.js.map +1 -0
  50. package/dist/mutation.d.ts +13 -0
  51. package/dist/mutation.d.ts.map +1 -0
  52. package/dist/mutation.js +43 -0
  53. package/dist/mutation.js.map +1 -0
  54. package/dist/query-info.d.ts +47 -0
  55. package/dist/query-info.d.ts.map +1 -0
  56. package/dist/query-info.js +38 -0
  57. package/dist/query-info.js.map +1 -0
  58. package/dist/rehydrate-from-mutationlog.d.ts +14 -0
  59. package/dist/rehydrate-from-mutationlog.d.ts.map +1 -0
  60. package/dist/rehydrate-from-mutationlog.js +72 -0
  61. package/dist/rehydrate-from-mutationlog.js.map +1 -0
  62. package/dist/schema/index.d.ts +60 -0
  63. package/dist/schema/index.d.ts.map +1 -0
  64. package/dist/schema/index.js +66 -0
  65. package/dist/schema/index.js.map +1 -0
  66. package/dist/schema/mutations.d.ts +227 -0
  67. package/dist/schema/mutations.d.ts.map +1 -0
  68. package/dist/schema/mutations.js +68 -0
  69. package/dist/schema/mutations.js.map +1 -0
  70. package/dist/schema/schema-helpers.d.ts +4 -0
  71. package/dist/schema/schema-helpers.d.ts.map +1 -0
  72. package/dist/schema/schema-helpers.js +30 -0
  73. package/dist/schema/schema-helpers.js.map +1 -0
  74. package/dist/schema/system-tables.d.ts +331 -0
  75. package/dist/schema/system-tables.d.ts.map +1 -0
  76. package/dist/schema/system-tables.js +46 -0
  77. package/dist/schema/system-tables.js.map +1 -0
  78. package/dist/schema/table-def.d.ts +135 -0
  79. package/dist/schema/table-def.d.ts.map +1 -0
  80. package/dist/schema/table-def.js +70 -0
  81. package/dist/schema/table-def.js.map +1 -0
  82. package/dist/schema-management/common.d.ts +13 -0
  83. package/dist/schema-management/common.d.ts.map +1 -0
  84. package/dist/schema-management/common.js +25 -0
  85. package/dist/schema-management/common.js.map +1 -0
  86. package/dist/schema-management/migrations.d.ts +23 -0
  87. package/dist/schema-management/migrations.d.ts.map +1 -0
  88. package/dist/schema-management/migrations.js +116 -0
  89. package/dist/schema-management/migrations.js.map +1 -0
  90. package/dist/schema-management/validate-mutation-defs.d.ts +8 -0
  91. package/dist/schema-management/validate-mutation-defs.d.ts.map +1 -0
  92. package/dist/schema-management/validate-mutation-defs.js +39 -0
  93. package/dist/schema-management/validate-mutation-defs.js.map +1 -0
  94. package/dist/sql-queries/index.d.ts +4 -0
  95. package/dist/sql-queries/index.d.ts.map +1 -0
  96. package/dist/sql-queries/index.js +4 -0
  97. package/dist/sql-queries/index.js.map +1 -0
  98. package/dist/sql-queries/misc.d.ts +2 -0
  99. package/dist/sql-queries/misc.d.ts.map +1 -0
  100. package/dist/sql-queries/misc.js +2 -0
  101. package/dist/sql-queries/misc.js.map +1 -0
  102. package/dist/sql-queries/sql-queries.d.ts +72 -0
  103. package/dist/sql-queries/sql-queries.d.ts.map +1 -0
  104. package/dist/sql-queries/sql-queries.js +191 -0
  105. package/dist/sql-queries/sql-queries.js.map +1 -0
  106. package/dist/sql-queries/sql-query-builder.d.ts +47 -0
  107. package/dist/sql-queries/sql-query-builder.d.ts.map +1 -0
  108. package/dist/sql-queries/sql-query-builder.js +60 -0
  109. package/dist/sql-queries/sql-query-builder.js.map +1 -0
  110. package/dist/sql-queries/types.d.ts +50 -0
  111. package/dist/sql-queries/types.d.ts.map +1 -0
  112. package/dist/sql-queries/types.js +5 -0
  113. package/dist/sql-queries/types.js.map +1 -0
  114. package/dist/sync/index.d.ts +2 -0
  115. package/dist/sync/index.d.ts.map +1 -0
  116. package/dist/sync/index.js +2 -0
  117. package/dist/sync/index.js.map +1 -0
  118. package/dist/sync/next/compact-events.d.ts +15 -0
  119. package/dist/sync/next/compact-events.d.ts.map +1 -0
  120. package/dist/sync/next/compact-events.js +176 -0
  121. package/dist/sync/next/compact-events.js.map +1 -0
  122. package/dist/sync/next/facts.d.ts +37 -0
  123. package/dist/sync/next/facts.d.ts.map +1 -0
  124. package/dist/sync/next/facts.js +156 -0
  125. package/dist/sync/next/facts.js.map +1 -0
  126. package/dist/sync/next/graphology.d.ts +8 -0
  127. package/dist/sync/next/graphology.d.ts.map +1 -0
  128. package/dist/sync/next/graphology.js +36 -0
  129. package/dist/sync/next/graphology.js.map +1 -0
  130. package/dist/sync/next/graphology_.d.ts +3 -0
  131. package/dist/sync/next/graphology_.d.ts.map +1 -0
  132. package/dist/sync/next/graphology_.js +3 -0
  133. package/dist/sync/next/graphology_.js.map +1 -0
  134. package/dist/sync/next/history-dag.d.ts +30 -0
  135. package/dist/sync/next/history-dag.d.ts.map +1 -0
  136. package/dist/sync/next/history-dag.js +69 -0
  137. package/dist/sync/next/history-dag.js.map +1 -0
  138. package/dist/sync/next/mod.d.ts +5 -0
  139. package/dist/sync/next/mod.d.ts.map +1 -0
  140. package/dist/sync/next/mod.js +5 -0
  141. package/dist/sync/next/mod.js.map +1 -0
  142. package/dist/sync/next/rebase-events.d.ts +27 -0
  143. package/dist/sync/next/rebase-events.d.ts.map +1 -0
  144. package/dist/sync/next/rebase-events.js +41 -0
  145. package/dist/sync/next/rebase-events.js.map +1 -0
  146. package/dist/sync/next/test/compact-events.calculator.test.d.ts +2 -0
  147. package/dist/sync/next/test/compact-events.calculator.test.d.ts.map +1 -0
  148. package/dist/sync/next/test/compact-events.calculator.test.js +101 -0
  149. package/dist/sync/next/test/compact-events.calculator.test.js.map +1 -0
  150. package/dist/sync/next/test/compact-events.test.d.ts +2 -0
  151. package/dist/sync/next/test/compact-events.test.d.ts.map +1 -0
  152. package/dist/sync/next/test/compact-events.test.js +201 -0
  153. package/dist/sync/next/test/compact-events.test.js.map +1 -0
  154. package/dist/sync/next/test/mod.d.ts +2 -0
  155. package/dist/sync/next/test/mod.d.ts.map +1 -0
  156. package/dist/sync/next/test/mod.js +2 -0
  157. package/dist/sync/next/test/mod.js.map +1 -0
  158. package/dist/sync/next/test/mutation-fixtures.d.ts +73 -0
  159. package/dist/sync/next/test/mutation-fixtures.d.ts.map +1 -0
  160. package/dist/sync/next/test/mutation-fixtures.js +161 -0
  161. package/dist/sync/next/test/mutation-fixtures.js.map +1 -0
  162. package/dist/sync/sync.d.ts +45 -0
  163. package/dist/sync/sync.d.ts.map +1 -0
  164. package/dist/sync/sync.js +12 -0
  165. package/dist/sync/sync.js.map +1 -0
  166. package/dist/util.d.ts +25 -0
  167. package/dist/util.d.ts.map +1 -0
  168. package/dist/util.js +38 -0
  169. package/dist/util.js.map +1 -0
  170. package/dist/version.d.ts +10 -0
  171. package/dist/version.d.ts.map +1 -0
  172. package/dist/version.js +12 -0
  173. package/dist/version.js.map +1 -0
  174. package/package.json +61 -0
  175. package/src/__tests__/fixture.ts +23 -0
  176. package/src/adapter-types.ts +216 -0
  177. package/src/ambient.d.ts +3 -0
  178. package/src/bounded-collections.ts +121 -0
  179. package/src/debug-info.ts +76 -0
  180. package/src/derived-mutations.test.ts +101 -0
  181. package/src/derived-mutations.ts +170 -0
  182. package/src/devtools/devtools-bridge.ts +13 -0
  183. package/src/devtools/devtools-messages.ts +247 -0
  184. package/src/devtools/devtools-window-message.ts +27 -0
  185. package/src/devtools/index.ts +49 -0
  186. package/src/index.ts +20 -0
  187. package/src/init-singleton-tables.ts +24 -0
  188. package/src/mutation.ts +69 -0
  189. package/src/query-info.ts +104 -0
  190. package/src/rehydrate-from-mutationlog.ts +131 -0
  191. package/src/schema/index.ts +144 -0
  192. package/src/schema/mutations.ts +313 -0
  193. package/src/schema/schema-helpers.ts +49 -0
  194. package/src/schema/system-tables.ts +84 -0
  195. package/src/schema/table-def.ts +312 -0
  196. package/src/schema-management/common.ts +44 -0
  197. package/src/schema-management/migrations.ts +188 -0
  198. package/src/schema-management/validate-mutation-defs.ts +63 -0
  199. package/src/sql-queries/index.ts +3 -0
  200. package/src/sql-queries/misc.ts +2 -0
  201. package/src/sql-queries/sql-queries.ts +359 -0
  202. package/src/sql-queries/sql-query-builder.ts +135 -0
  203. package/src/sql-queries/types.ts +97 -0
  204. package/src/sync/index.ts +1 -0
  205. package/src/sync/next/ambient.d.ts +3 -0
  206. package/src/sync/next/compact-events.ts +218 -0
  207. package/src/sync/next/facts.ts +229 -0
  208. package/src/sync/next/graphology.ts +49 -0
  209. package/src/sync/next/graphology_.ts +2 -0
  210. package/src/sync/next/history-dag.ts +109 -0
  211. package/src/sync/next/mod.ts +4 -0
  212. package/src/sync/next/rebase-events.ts +97 -0
  213. package/src/sync/next/test/compact-events.calculator.test.ts +121 -0
  214. package/src/sync/next/test/compact-events.test.ts +232 -0
  215. package/src/sync/next/test/mod.ts +1 -0
  216. package/src/sync/next/test/mutation-fixtures.ts +230 -0
  217. package/src/sync/sync.ts +46 -0
  218. package/src/util.ts +56 -0
  219. package/src/version.ts +13 -0
  220. package/tsconfig.json +11 -0
@@ -0,0 +1,359 @@
1
+ import type { SqliteDsl } from '@livestore/db-schema'
2
+ import { shouldNeverHappen } from '@livestore/utils'
3
+ import { pipe, ReadonlyArray, Schema, TreeFormatter } from '@livestore/utils/effect'
4
+
5
+ import { sql } from '../util.js'
6
+ import { objectEntries } from './misc.js'
7
+ import * as ClientTypes from './types.js'
8
+
9
+ export type BindValues = {
10
+ readonly [columnName: string]: any
11
+ }
12
+
13
+ export const findManyRows = <TColumns extends SqliteDsl.Columns>({
14
+ columns,
15
+ tableName,
16
+ where,
17
+ limit,
18
+ }: {
19
+ tableName: string
20
+ columns: TColumns
21
+ where: ClientTypes.WhereValuesForColumns<TColumns>
22
+ limit?: number
23
+ }): [string, BindValues] => {
24
+ const whereSql = buildWhereSql({ where })
25
+ const whereModifier = whereSql === '' ? '' : `WHERE ${whereSql}`
26
+ const limitModifier = limit ? `LIMIT ${limit}` : ''
27
+
28
+ const whereBindValues = makeBindValues({ columns, values: where, variablePrefix: 'where_', skipNil: true })
29
+
30
+ return [sql`SELECT * FROM ${tableName} ${whereModifier} ${limitModifier}`, whereBindValues]
31
+ }
32
+
33
+ export const countRows = <TColumns extends SqliteDsl.Columns>({
34
+ columns,
35
+ tableName,
36
+ where,
37
+ }: {
38
+ tableName: string
39
+ columns: TColumns
40
+ where: ClientTypes.WhereValuesForColumns<TColumns>
41
+ }): [string, BindValues] => {
42
+ const whereSql = buildWhereSql({ where })
43
+ const whereModifier = whereSql === '' ? '' : `WHERE ${whereSql}`
44
+
45
+ const whereBindValues = makeBindValues({ columns, values: where, variablePrefix: 'where_', skipNil: true })
46
+
47
+ return [sql`SELECT count(1) FROM ${tableName} ${whereModifier}`, whereBindValues]
48
+ }
49
+
50
+ export const insertRow = <TColumns extends SqliteDsl.Columns>({
51
+ tableName,
52
+ columns,
53
+ values,
54
+ options = { orReplace: false },
55
+ }: {
56
+ tableName: string
57
+ columns: TColumns
58
+ values: ClientTypes.DecodedValuesForColumns<TColumns>
59
+ options?: { orReplace: boolean }
60
+ }): [string, BindValues] => {
61
+ const stmt = insertRowPrepared({ tableName, columns, options })
62
+
63
+ return [stmt, makeBindValues({ columns, values })]
64
+ }
65
+
66
+ export const insertRowPrepared = <TColumns extends SqliteDsl.Columns>({
67
+ tableName,
68
+ columns,
69
+ options = { orReplace: false },
70
+ }: {
71
+ tableName: string
72
+ columns: TColumns
73
+ options?: { orReplace: boolean }
74
+ }): string => {
75
+ const keysStr = Object.keys(columns).join(', ')
76
+ const valuesStr = Object.keys(columns)
77
+ .map((key) => `$${key}`)
78
+ .join(', ')
79
+
80
+ return sql`INSERT ${options.orReplace ? 'OR REPLACE ' : ''}INTO ${tableName} (${keysStr}) VALUES (${valuesStr})`
81
+ }
82
+
83
+ export const insertRows = <TColumns extends SqliteDsl.Columns>({
84
+ columns,
85
+ tableName,
86
+ valuesArray,
87
+ }: {
88
+ tableName: string
89
+ columns: TColumns
90
+ valuesArray: ClientTypes.DecodedValuesForColumns<TColumns>[]
91
+ }): [string, BindValues] => {
92
+ const keysStr = Object.keys(valuesArray[0]!).join(', ')
93
+
94
+ // NOTE consider batching for large arrays (https://sqlite.org/forum/info/f832398c19d30a4a)
95
+ const valuesStrs = valuesArray
96
+ .map((values, itemIndex) =>
97
+ Object.keys(values)
98
+ .map((_) => `$item_${itemIndex}_${_}`)
99
+ .join(', '),
100
+ )
101
+ .map((_) => `(${_})`)
102
+ .join(', ')
103
+
104
+ const bindValues = valuesArray.reduce(
105
+ (acc, values, itemIndex) => ({
106
+ ...acc,
107
+ ...makeBindValues({ columns, values, variablePrefix: `item_${itemIndex}_` }),
108
+ }),
109
+ {},
110
+ )
111
+
112
+ return [sql`INSERT INTO ${tableName} (${keysStr}) VALUES ${valuesStrs}`, bindValues]
113
+ }
114
+
115
+ export const insertOrIgnoreRow = <TColumns extends SqliteDsl.Columns>({
116
+ columns,
117
+ tableName,
118
+ values: values_,
119
+ returnRow,
120
+ }: {
121
+ tableName: string
122
+ columns: TColumns
123
+ values: ClientTypes.DecodedValuesForColumns<TColumns>
124
+ returnRow: boolean
125
+ }): [string, BindValues] => {
126
+ const values = filterUndefinedFields(values_)
127
+ const keysStr = Object.keys(values).join(', ')
128
+ const valuesStr = Object.keys(values)
129
+ .map((_) => `$${_}`)
130
+ .join(', ')
131
+
132
+ const bindValues = makeBindValues({ columns, values })
133
+ const returningStmt = returnRow ? 'RETURNING *' : ''
134
+
135
+ return [sql`INSERT OR IGNORE INTO ${tableName} (${keysStr}) VALUES (${valuesStr}) ${returningStmt}`, bindValues]
136
+ }
137
+
138
+ export const updateRows = <TColumns extends SqliteDsl.Columns>({
139
+ columns,
140
+ tableName,
141
+ updateValues: updateValues_,
142
+ where,
143
+ }: {
144
+ columns: TColumns
145
+ tableName: string
146
+ updateValues: Partial<ClientTypes.DecodedValuesForColumnsAll<TColumns>>
147
+ where: ClientTypes.WhereValuesForColumns<TColumns>
148
+ }): [string, BindValues] => {
149
+ const updateValues = filterUndefinedFields(updateValues_)
150
+
151
+ // TODO return an Option instead of `select 1` if there are no update values
152
+ if (Object.keys(updateValues).length === 0) {
153
+ return [sql`select 1`, {}]
154
+ }
155
+
156
+ const updateValueStr = Object.keys(updateValues)
157
+ .map((columnName) => `${columnName} = $update_${columnName}`)
158
+ .join(', ')
159
+
160
+ const bindValues = {
161
+ ...makeBindValues({ columns, values: updateValues, variablePrefix: 'update_' }),
162
+ ...makeBindValues({ columns, values: where, variablePrefix: 'where_', skipNil: true }),
163
+ }
164
+
165
+ const whereSql = buildWhereSql({ where })
166
+ const whereModifier = whereSql === '' ? '' : `WHERE ${whereSql}`
167
+
168
+ return [sql`UPDATE ${tableName} SET ${updateValueStr} ${whereModifier}`, bindValues]
169
+ }
170
+
171
+ export const deleteRows = <TColumns extends SqliteDsl.Columns>({
172
+ columns,
173
+ tableName,
174
+ where,
175
+ }: {
176
+ columns: TColumns
177
+ tableName: string
178
+ where: ClientTypes.WhereValuesForColumns<TColumns>
179
+ }): [string, BindValues] => {
180
+ const bindValues = {
181
+ ...makeBindValues({ columns, values: where, variablePrefix: 'where_', skipNil: true }),
182
+ }
183
+
184
+ const whereSql = buildWhereSql({ where })
185
+ const whereModifier = whereSql === '' ? '' : `WHERE ${whereSql}`
186
+
187
+ return [sql`DELETE FROM ${tableName} ${whereModifier}`, bindValues]
188
+ }
189
+
190
+ export const upsertRow = <TColumns extends SqliteDsl.Columns>({
191
+ tableName,
192
+ columns,
193
+ createValues: createValues_,
194
+ updateValues: updateValues_,
195
+ where,
196
+ }: {
197
+ tableName: string
198
+ columns: TColumns
199
+ createValues: ClientTypes.DecodedValuesForColumns<TColumns>
200
+ updateValues: Partial<ClientTypes.DecodedValuesForColumnsAll<TColumns>>
201
+ // TODO where VALUES are actually not used here. Maybe adjust API?
202
+ where: ClientTypes.WhereValuesForColumns<TColumns>
203
+ }): [string, BindValues] => {
204
+ const createValues = filterUndefinedFields(createValues_)
205
+ const updateValues = filterUndefinedFields(updateValues_)
206
+
207
+ const keysStr = Object.keys(createValues).join(', ')
208
+
209
+ const createValuesStr = Object.keys(createValues)
210
+ .map((_) => `$create_${_}`)
211
+ .join(', ')
212
+
213
+ const conflictStr = Object.keys(where).join(', ')
214
+
215
+ const updateValueStr = Object.keys(updateValues)
216
+ .map((columnName) => `${columnName} = $update_${columnName}`)
217
+ .join(', ')
218
+
219
+ const bindValues = {
220
+ ...makeBindValues({ columns, values: createValues, variablePrefix: 'create_' }),
221
+ ...makeBindValues({ columns, values: updateValues, variablePrefix: 'update_' }),
222
+ }
223
+
224
+ return [
225
+ sql`
226
+ INSERT INTO ${tableName} (${keysStr})
227
+ VALUES (${createValuesStr})
228
+ ON CONFLICT (${conflictStr}) DO UPDATE SET ${updateValueStr}
229
+ `,
230
+ bindValues,
231
+ ]
232
+ }
233
+
234
+ export const createTable = ({
235
+ table,
236
+ tableName,
237
+ }: {
238
+ table: SqliteDsl.TableDefinition<any, SqliteDsl.Columns>
239
+ tableName: string
240
+ }): string => {
241
+ const primaryKeys = Object.entries(table.columns)
242
+ .filter(([_, columnDef]) => columnDef.primaryKey)
243
+ .map(([columnName, _]) => columnName)
244
+ const columnDefStrs = Object.entries(table.columns).map(([columnName, columnDef]) => {
245
+ const nullModifier = columnDef.nullable === true ? '' : 'NOT NULL'
246
+ const defaultModifier = columnDef.default._tag === 'None' ? '' : `DEFAULT ${columnDef.default.value}`
247
+ return sql`${columnName} ${columnDef.columnType} ${nullModifier} ${defaultModifier}`
248
+ })
249
+
250
+ if (primaryKeys.length > 0) {
251
+ columnDefStrs.push(`PRIMARY KEY (${primaryKeys.join(', ')})`)
252
+ }
253
+
254
+ return sql`CREATE TABLE ${tableName} (${columnDefStrs.join(', ')});`
255
+ }
256
+
257
+ export const makeBindValues = <TColumns extends SqliteDsl.Columns, TKeys extends keyof TColumns>({
258
+ columns,
259
+ values,
260
+ variablePrefix = '',
261
+ skipNil,
262
+ }: {
263
+ columns: TColumns
264
+ values: Partial<Record<TKeys, any>>
265
+ variablePrefix?: string
266
+ /** So far only used to prepare `where` statements */
267
+ skipNil?: boolean
268
+ }): Record<string, any> => {
269
+ const codecMap = pipe(
270
+ columns,
271
+ objectEntries,
272
+ ReadonlyArray.map(([columnName, columnDef]) => [
273
+ columnName,
274
+ (value: any) => {
275
+ if (columnDef.nullable === true && (value === null || value === undefined)) return null
276
+ const res = Schema.encodeEither(columnDef.schema)(value)
277
+ if (res._tag === 'Left') {
278
+ const parseErrorStr = TreeFormatter.formatErrorSync(res.left)
279
+ const expectedSchemaStr = String(columnDef.schema.ast)
280
+
281
+ console.error(
282
+ `\
283
+ Error making bind values for SQL query for column "${columnName}".
284
+
285
+ Expected schema: ${expectedSchemaStr}
286
+
287
+ Error: ${parseErrorStr}
288
+
289
+ Value:`,
290
+ value,
291
+ )
292
+ debugger
293
+ throw res.left
294
+ } else {
295
+ return res.right
296
+ }
297
+ },
298
+ ]),
299
+ Object.fromEntries,
300
+ )
301
+
302
+ return pipe(
303
+ Object.entries(values)
304
+ // NOTE null/undefined values are handled via explicit SQL syntax and don't need to be provided as bind values
305
+ .filter(([, value]) => skipNil !== true || (value !== null && value !== undefined))
306
+ .flatMap(([columnName, value]: [string, any]) => {
307
+ const codec = codecMap[columnName] ?? shouldNeverHappen(`No codec found for column "${columnName}"`)
308
+ // remap complex where-values with `op`
309
+ if (typeof value === 'object' && value !== null && 'op' in value) {
310
+ switch (value.op) {
311
+ case 'in': {
312
+ return value.val.map((value: any, i: number) => [`${variablePrefix}${columnName}_${i}`, codec(value)])
313
+ }
314
+ case '=':
315
+ case '>':
316
+ case '<': {
317
+ return [[`${variablePrefix}${columnName}`, codec(value.val)]]
318
+ }
319
+ default: {
320
+ throw new Error(`Unknown op: ${value.op}`)
321
+ }
322
+ }
323
+ } else {
324
+ return [[`${variablePrefix}${columnName}`, codec(value)]]
325
+ }
326
+ }),
327
+ Object.fromEntries,
328
+ )
329
+ }
330
+
331
+ const buildWhereSql = <TColumns extends SqliteDsl.Columns>({
332
+ where,
333
+ }: {
334
+ where: ClientTypes.WhereValuesForColumns<TColumns>
335
+ }) => {
336
+ const getWhereOp = (columnName: string, value: ClientTypes.WhereValueForDecoded<any>) => {
337
+ if (value === null) {
338
+ return `IS NULL`
339
+ } else if (typeof value === 'object' && typeof value.op === 'string' && ClientTypes.isValidWhereOp(value.op)) {
340
+ return `${value.op} $where_${columnName}`
341
+ } else if (typeof value === 'object' && typeof value.op === 'string' && value.op === 'in') {
342
+ return `in (${value.val.map((_: any, i: number) => `$where_${columnName}_${i}`).join(', ')})`
343
+ } else {
344
+ return `= $where_${columnName}`
345
+ }
346
+ }
347
+
348
+ return pipe(
349
+ where,
350
+ objectEntries,
351
+ ReadonlyArray.map(([columnName, value]) => `${columnName} ${getWhereOp(columnName, value)}`),
352
+ ReadonlyArray.join(' AND '),
353
+ )
354
+ }
355
+
356
+ // TODO better typing
357
+ const filterUndefinedFields = <T extends Record<string, any>>(obj: T): T => {
358
+ return Object.fromEntries(Object.entries(obj).filter(([, value]) => value !== undefined)) as T
359
+ }
@@ -0,0 +1,135 @@
1
+ import type { SqliteDsl } from '@livestore/db-schema'
2
+
3
+ import type { BindValues } from './sql-queries.js'
4
+ import * as SqlQueries from './sql-queries.js'
5
+ import type * as ClientTypes from './types.js'
6
+
7
+ export type SqlQuery = [stmt: string, bindValues: BindValues, tableName: string]
8
+
9
+ export const makeSqlQueryBuilder = <TSchema extends SqliteDsl.DbSchema>(schema: TSchema) => {
10
+ const findManyRows = <TTableName extends keyof TSchema & string>({
11
+ tableName,
12
+ where,
13
+ limit,
14
+ }: {
15
+ tableName: TTableName
16
+ where: ClientTypes.WhereValuesForTable<TSchema, TTableName>
17
+ limit?: number
18
+ }): [string, BindValues, TTableName] => {
19
+ const columns = schema[tableName]!.columns
20
+ const [stmt, bindValues] = SqlQueries.findManyRows({ columns, tableName, where, limit })
21
+ return [stmt, bindValues, tableName]
22
+ }
23
+
24
+ const countRows = <TTableName extends keyof TSchema & string>({
25
+ tableName,
26
+ where,
27
+ }: {
28
+ tableName: TTableName
29
+ where: ClientTypes.WhereValuesForTable<TSchema, TTableName>
30
+ }): [string, BindValues, TTableName] => {
31
+ const columns = schema[tableName]!.columns
32
+ const [stmt, bindValues] = SqlQueries.countRows({ columns, tableName, where })
33
+ return [stmt, bindValues, tableName]
34
+ }
35
+
36
+ const insertRow = <TTableName extends keyof TSchema & string>({
37
+ tableName,
38
+ values,
39
+ options = { orReplace: false },
40
+ }: {
41
+ tableName: TTableName
42
+ values: ClientTypes.DecodedValuesForTable<TSchema, TTableName>
43
+ options?: { orReplace: boolean }
44
+ }): [string, BindValues, TTableName] => {
45
+ const columns = schema[tableName]!.columns
46
+ const [stmt, bindValues] = SqlQueries.insertRow({ columns, tableName, values, options })
47
+ return [stmt, bindValues, tableName]
48
+ }
49
+
50
+ const insertRows = <TTableName extends keyof TSchema & string>({
51
+ tableName,
52
+ valuesArray,
53
+ }: {
54
+ tableName: TTableName
55
+ valuesArray: ClientTypes.DecodedValuesForTable<TSchema, TTableName>[]
56
+ }): [string, BindValues, TTableName] => {
57
+ const columns = schema[tableName]!.columns
58
+ const [stmt, bindValues] = SqlQueries.insertRows({ columns, tableName, valuesArray })
59
+ return [stmt, bindValues, tableName]
60
+ }
61
+
62
+ const insertOrIgnoreRow = <TTableName extends keyof TSchema & string>({
63
+ tableName,
64
+ values,
65
+ returnRow = false,
66
+ }: {
67
+ tableName: TTableName
68
+ values: ClientTypes.DecodedValuesForTable<TSchema, TTableName>
69
+ returnRow?: boolean
70
+ }): [string, BindValues, TTableName] => {
71
+ const columns = schema[tableName]!.columns
72
+ const [stmt, bindValues] = SqlQueries.insertOrIgnoreRow({ columns, tableName, values, returnRow })
73
+ return [stmt, bindValues, tableName]
74
+ }
75
+
76
+ const updateRows = <TTableName extends keyof TSchema & string>({
77
+ tableName,
78
+ updateValues,
79
+ where,
80
+ }: {
81
+ tableName: TTableName
82
+ updateValues: Partial<ClientTypes.DecodedValuesForTableAll<TSchema, TTableName>>
83
+ where: ClientTypes.WhereValuesForTable<TSchema, TTableName>
84
+ }): [string, BindValues, TTableName] => {
85
+ const columns = schema[tableName]!.columns
86
+ const [stmt, bindValues] = SqlQueries.updateRows({ columns, tableName, updateValues, where })
87
+ return [stmt, bindValues, tableName]
88
+ }
89
+
90
+ const deleteRows = <TTableName extends keyof TSchema & string>({
91
+ tableName,
92
+ where,
93
+ }: {
94
+ tableName: TTableName
95
+ where: ClientTypes.WhereValuesForTable<TSchema, TTableName>
96
+ }): [string, BindValues, TTableName] => {
97
+ const columns = schema[tableName]!.columns
98
+ const [stmt, bindValues] = SqlQueries.deleteRows({ columns, tableName, where })
99
+ return [stmt, bindValues, tableName]
100
+ }
101
+
102
+ const upsertRow = <TTableName extends keyof TSchema & string>({
103
+ tableName,
104
+ createValues,
105
+ updateValues,
106
+ where,
107
+ }: {
108
+ tableName: TTableName
109
+ createValues: ClientTypes.DecodedValuesForTable<TSchema, TTableName>
110
+ updateValues: Partial<ClientTypes.DecodedValuesForTableAll<TSchema, TTableName>>
111
+ // TODO where VALUES are actually not used here. Maybe adjust API?
112
+ where: ClientTypes.WhereValuesForTable<TSchema, TTableName>
113
+ }): [string, BindValues, TTableName] => {
114
+ const columns = schema[tableName]!.columns
115
+ const [stmt, bindValues] = SqlQueries.upsertRow({
116
+ columns,
117
+ tableName,
118
+ createValues: createValues as any, // TODO investigate why types don't match
119
+ updateValues,
120
+ where,
121
+ })
122
+ return [stmt, bindValues, tableName]
123
+ }
124
+
125
+ return {
126
+ findManyRows,
127
+ countRows,
128
+ insertRow,
129
+ insertRows,
130
+ insertOrIgnoreRow,
131
+ updateRows,
132
+ deleteRows,
133
+ upsertRow,
134
+ }
135
+ }
@@ -0,0 +1,97 @@
1
+ import type { Prettify, SqliteDsl } from '@livestore/db-schema'
2
+ import type { Schema } from '@livestore/utils/effect'
3
+
4
+ export type DecodedValuesForTableAll<TSchema extends SqliteDsl.DbSchema, TTableName extends keyof TSchema> = {
5
+ [K in keyof GetColumns<TSchema, TTableName>]: Schema.Schema.Type<GetColumn<TSchema, TTableName, K>['schema']>
6
+ }
7
+
8
+ export type DecodedValuesForTablePretty<
9
+ TSchema extends SqliteDsl.DbSchema,
10
+ TTableName extends keyof TSchema,
11
+ > = Prettify<DecodedValuesForTable<TSchema, TTableName>>
12
+
13
+ export type DecodedValuesForTable<TSchema extends SqliteDsl.DbSchema, TTableName extends keyof TSchema> = Partial<
14
+ Pick<DecodedValuesForTableAll<TSchema, TTableName>, GetNullableColumnNamesForTable<TSchema, TTableName>>
15
+ > &
16
+ Omit<DecodedValuesForTableAll<TSchema, TTableName>, GetNullableColumnNamesForTable<TSchema, TTableName>>
17
+
18
+ export type DecodedValuesForTableOrNull<
19
+ TSchema extends SqliteDsl.DbSchema,
20
+ TTableName extends keyof TSchema,
21
+ > = NullableObj<
22
+ Pick<DecodedValuesForTableAll<TSchema, TTableName>, GetNullableColumnNamesForTable<TSchema, TTableName>>
23
+ > &
24
+ Omit<DecodedValuesForTableAll<TSchema, TTableName>, GetNullableColumnNamesForTable<TSchema, TTableName>>
25
+
26
+ export type WhereValuesForTable<TSchema extends SqliteDsl.DbSchema, TTableName extends keyof TSchema> = PartialOrNull<{
27
+ [K in keyof DecodedValuesForTableAll<TSchema, TTableName>]: WhereValueForDecoded<
28
+ DecodedValuesForTableAll<TSchema, TTableName>[K]
29
+ >
30
+ }>
31
+
32
+ export type WhereValueForDecoded<TDecoded> = TDecoded | { op: WhereOp; val: TDecoded } | { op: 'in'; val: TDecoded[] }
33
+ export type WhereOp = '>' | '<' | '='
34
+
35
+ export const isValidWhereOp = (op: string): op is WhereOp => {
36
+ const validWhereOps = ['>', '<', '=']
37
+ return validWhereOps.includes(op)
38
+ }
39
+
40
+ export type EncodedValuesForTableAll<TSchema extends SqliteDsl.DbSchema, TTableName extends keyof TSchema> = {
41
+ [K in keyof GetColumns<TSchema, TTableName>]: Schema.Schema.Type<GetColumn<TSchema, TTableName, K>['schema']>
42
+ }
43
+
44
+ export type EncodedValuesForTable<TSchema extends SqliteDsl.DbSchema, TTableName extends keyof TSchema> = Partial<
45
+ Pick<EncodedValuesForTableAll<TSchema, TTableName>, GetNullableColumnNamesForTable<TSchema, TTableName>>
46
+ > &
47
+ Omit<EncodedValuesForTableAll<TSchema, TTableName>, GetNullableColumnNamesForTable<TSchema, TTableName>>
48
+
49
+ export type GetNullableColumnNamesForTable<
50
+ TSchema extends SqliteDsl.DbSchema,
51
+ TTableName extends keyof TSchema,
52
+ > = keyof {
53
+ [K in keyof GetColumns<TSchema, TTableName> as GetColumn<TSchema, TTableName, K>['nullable'] extends true
54
+ ? K
55
+ : never]: {}
56
+ }
57
+
58
+ export type GetColumns<
59
+ TSchema extends SqliteDsl.DbSchema,
60
+ TTableName extends keyof TSchema,
61
+ > = TSchema[TTableName]['columns']
62
+
63
+ export type GetColumn<
64
+ TSchema extends SqliteDsl.DbSchema,
65
+ TTableName extends keyof TSchema,
66
+ TColumnName extends keyof TSchema[TTableName]['columns'],
67
+ > = TSchema[TTableName]['columns'][TColumnName]
68
+
69
+ export type DecodedValuesForColumnsAll<TColumns extends SqliteDsl.Columns> = {
70
+ [K in keyof TColumns]: Schema.Schema.Type<TColumns[K]['schema']>
71
+ }
72
+
73
+ export type DecodedValuesForColumns<TColumns extends SqliteDsl.Columns> = Partial<
74
+ Pick<DecodedValuesForColumnsAll<TColumns>, GetNullableColumnNames<TColumns>>
75
+ > &
76
+ Omit<DecodedValuesForColumnsAll<TColumns>, GetNullableColumnNames<TColumns>>
77
+
78
+ export type EncodedValuesForColumnsAll<TColumns extends SqliteDsl.Columns> = {
79
+ [K in keyof TColumns]: Schema.Schema.Encoded<TColumns[K]['schema']>
80
+ }
81
+
82
+ export type EncodedValuesForColumns<TColumns extends SqliteDsl.Columns> = Partial<
83
+ Pick<EncodedValuesForColumnsAll<TColumns>, GetNullableColumnNames<TColumns>>
84
+ > &
85
+ Omit<EncodedValuesForColumnsAll<TColumns>, GetNullableColumnNames<TColumns>>
86
+
87
+ export type WhereValuesForColumns<TColumns extends SqliteDsl.Columns> = PartialOrNull<{
88
+ [K in keyof EncodedValuesForColumns<TColumns>]: WhereValueForDecoded<DecodedValuesForColumnsAll<TColumns>[K]>
89
+ }>
90
+
91
+ export type GetNullableColumnNames<TColumns extends SqliteDsl.Columns> = keyof {
92
+ [K in keyof TColumns as TColumns[K] extends SqliteDsl.ColumnDefinition<any, true> ? K : never]: unknown
93
+ }
94
+
95
+ export type PartialOrNull<T> = { [P in keyof T]?: T[P] | null }
96
+
97
+ export type NullableObj<T> = { [P in keyof T]: T[P] | null }
@@ -0,0 +1 @@
1
+ export * from './sync.js'
@@ -0,0 +1,3 @@
1
+ declare module 'graphology' {
2
+ export * from 'graphology-types'
3
+ }