@livestore/common 0.0.42-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/database.d.ts +32 -0
  3. package/dist/database.d.ts.map +1 -0
  4. package/dist/database.js +2 -0
  5. package/dist/database.js.map +1 -0
  6. package/dist/index.d.ts +4 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +4 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/schema/index.d.ts +42 -0
  11. package/dist/schema/index.d.ts.map +1 -0
  12. package/dist/schema/index.js +42 -0
  13. package/dist/schema/index.js.map +1 -0
  14. package/dist/schema/mutations.d.ts +81 -0
  15. package/dist/schema/mutations.d.ts.map +1 -0
  16. package/dist/schema/mutations.js +29 -0
  17. package/dist/schema/mutations.js.map +1 -0
  18. package/dist/schema/parse-utils.d.ts +6 -0
  19. package/dist/schema/parse-utils.d.ts.map +1 -0
  20. package/dist/schema/parse-utils.js +22 -0
  21. package/dist/schema/parse-utils.js.map +1 -0
  22. package/dist/schema/system-tables.d.ts +76 -0
  23. package/dist/schema/system-tables.d.ts.map +1 -0
  24. package/dist/schema/system-tables.js +12 -0
  25. package/dist/schema/system-tables.js.map +1 -0
  26. package/dist/schema/table-def.d.ts +100 -0
  27. package/dist/schema/table-def.d.ts.map +1 -0
  28. package/dist/schema/table-def.js +76 -0
  29. package/dist/schema/table-def.js.map +1 -0
  30. package/dist/sql-queries/index.d.ts +4 -0
  31. package/dist/sql-queries/index.d.ts.map +1 -0
  32. package/dist/sql-queries/index.js +4 -0
  33. package/dist/sql-queries/index.js.map +1 -0
  34. package/dist/sql-queries/misc.d.ts +2 -0
  35. package/dist/sql-queries/misc.d.ts.map +1 -0
  36. package/dist/sql-queries/misc.js +2 -0
  37. package/dist/sql-queries/misc.js.map +1 -0
  38. package/dist/sql-queries/sql-queries.d.ts +65 -0
  39. package/dist/sql-queries/sql-queries.d.ts.map +1 -0
  40. package/dist/sql-queries/sql-queries.js +181 -0
  41. package/dist/sql-queries/sql-queries.js.map +1 -0
  42. package/dist/sql-queries/sql-query-builder.d.ts +47 -0
  43. package/dist/sql-queries/sql-query-builder.d.ts.map +1 -0
  44. package/dist/sql-queries/sql-query-builder.js +60 -0
  45. package/dist/sql-queries/sql-query-builder.js.map +1 -0
  46. package/dist/sql-queries/types.d.ts +50 -0
  47. package/dist/sql-queries/types.d.ts.map +1 -0
  48. package/dist/sql-queries/types.js +5 -0
  49. package/dist/sql-queries/types.js.map +1 -0
  50. package/dist/util.d.ts +21 -0
  51. package/dist/util.d.ts.map +1 -0
  52. package/dist/util.js +33 -0
  53. package/dist/util.js.map +1 -0
  54. package/package.json +37 -0
  55. package/src/database.ts +37 -0
  56. package/src/index.ts +3 -0
  57. package/src/schema/index.ts +100 -0
  58. package/src/schema/mutations.ts +128 -0
  59. package/src/schema/parse-utils.ts +42 -0
  60. package/src/schema/system-tables.ts +22 -0
  61. package/src/schema/table-def.ts +274 -0
  62. package/src/sql-queries/index.ts +3 -0
  63. package/src/sql-queries/misc.ts +2 -0
  64. package/src/sql-queries/sql-queries.ts +335 -0
  65. package/src/sql-queries/sql-query-builder.ts +135 -0
  66. package/src/sql-queries/types.ts +97 -0
  67. package/src/util.ts +46 -0
  68. package/tsconfig.json +10 -0
@@ -0,0 +1,335 @@
1
+ import { pipe, ReadonlyArray, Schema } from '@livestore/utils/effect'
2
+ import type { SqliteDsl } from 'effect-db-schema'
3
+
4
+ import { sql } from '../util.js'
5
+ import { objectEntries } from './misc.js'
6
+ import * as ClientTypes from './types.js'
7
+
8
+ export type BindValues = {
9
+ readonly [columnName: string]: any
10
+ }
11
+
12
+ export const findManyRows = <TColumns extends SqliteDsl.Columns>({
13
+ columns,
14
+ tableName,
15
+ where,
16
+ limit,
17
+ }: {
18
+ tableName: string
19
+ columns: TColumns
20
+ where: ClientTypes.WhereValuesForColumns<TColumns>
21
+ limit?: number
22
+ }): [string, BindValues] => {
23
+ const whereSql = buildWhereSql({ where })
24
+ const whereModifier = whereSql === '' ? '' : `WHERE ${whereSql}`
25
+ const limitModifier = limit ? `LIMIT ${limit}` : ''
26
+
27
+ const whereBindValues = makeBindValues({ columns, values: where, variablePrefix: 'where_', skipNil: true })
28
+
29
+ return [sql`SELECT * FROM ${tableName} ${whereModifier} ${limitModifier}`, whereBindValues]
30
+ }
31
+
32
+ export const countRows = <TColumns extends SqliteDsl.Columns>({
33
+ columns,
34
+ tableName,
35
+ where,
36
+ }: {
37
+ tableName: string
38
+ columns: TColumns
39
+ where: ClientTypes.WhereValuesForColumns<TColumns>
40
+ }): [string, BindValues] => {
41
+ const whereSql = buildWhereSql({ where })
42
+ const whereModifier = whereSql === '' ? '' : `WHERE ${whereSql}`
43
+
44
+ const whereBindValues = makeBindValues({ columns, values: where, variablePrefix: 'where_', skipNil: true })
45
+
46
+ return [sql`SELECT count(1) FROM ${tableName} ${whereModifier}`, whereBindValues]
47
+ }
48
+
49
+ export const insertRow = <TColumns extends SqliteDsl.Columns>({
50
+ tableName,
51
+ columns,
52
+ values,
53
+ options = { orReplace: false },
54
+ }: {
55
+ tableName: string
56
+ columns: TColumns
57
+ values: ClientTypes.DecodedValuesForColumns<TColumns>
58
+ options: { orReplace: boolean }
59
+ }): [string, BindValues] => {
60
+ const keysStr = Object.keys(values).join(', ')
61
+ const valuesStr = Object.keys(values)
62
+ .map((_) => `$${_}`)
63
+ .join(', ')
64
+
65
+ return [
66
+ sql`INSERT ${options.orReplace ? 'OR REPLACE' : ''} INTO ${tableName} (${keysStr}) VALUES (${valuesStr})`,
67
+ makeBindValues({ columns, values }),
68
+ ]
69
+ }
70
+
71
+ export const insertRows = <TColumns extends SqliteDsl.Columns>({
72
+ columns,
73
+ tableName,
74
+ valuesArray,
75
+ }: {
76
+ tableName: string
77
+ columns: TColumns
78
+ valuesArray: ClientTypes.DecodedValuesForColumns<TColumns>[]
79
+ }): [string, BindValues] => {
80
+ const keysStr = Object.keys(valuesArray[0]!).join(', ')
81
+
82
+ // NOTE consider batching for large arrays (https://sqlite.org/forum/info/f832398c19d30a4a)
83
+ const valuesStrs = valuesArray
84
+ .map((values, itemIndex) =>
85
+ Object.keys(values)
86
+ .map((_) => `$item_${itemIndex}_${_}`)
87
+ .join(', '),
88
+ )
89
+ .map((_) => `(${_})`)
90
+ .join(', ')
91
+
92
+ const bindValues = valuesArray.reduce(
93
+ (acc, values, itemIndex) => ({
94
+ ...acc,
95
+ ...makeBindValues({ columns, values, variablePrefix: `item_${itemIndex}_` }),
96
+ }),
97
+ {},
98
+ )
99
+
100
+ return [sql`INSERT INTO ${tableName} (${keysStr}) VALUES ${valuesStrs}`, bindValues]
101
+ }
102
+
103
+ export const insertOrIgnoreRow = <TColumns extends SqliteDsl.Columns>({
104
+ columns,
105
+ tableName,
106
+ values: values_,
107
+ returnRow,
108
+ }: {
109
+ tableName: string
110
+ columns: TColumns
111
+ values: ClientTypes.DecodedValuesForColumns<TColumns>
112
+ returnRow: boolean
113
+ }): [string, BindValues] => {
114
+ const values = filterUndefinedFields(values_)
115
+ const keysStr = Object.keys(values).join(', ')
116
+ const valuesStr = Object.keys(values)
117
+ .map((_) => `$${_}`)
118
+ .join(', ')
119
+
120
+ const bindValues = makeBindValues({ columns, values })
121
+ const returningStmt = returnRow ? 'RETURNING *' : ''
122
+
123
+ return [sql`INSERT OR IGNORE INTO ${tableName} (${keysStr}) VALUES (${valuesStr}) ${returningStmt}`, bindValues]
124
+ }
125
+
126
+ export const updateRows = <TColumns extends SqliteDsl.Columns>({
127
+ columns,
128
+ tableName,
129
+ updateValues: updateValues_,
130
+ where,
131
+ }: {
132
+ columns: TColumns
133
+ tableName: string
134
+ updateValues: Partial<ClientTypes.DecodedValuesForColumnsAll<TColumns>>
135
+ where: ClientTypes.WhereValuesForColumns<TColumns>
136
+ }): [string, BindValues] => {
137
+ const updateValues = filterUndefinedFields(updateValues_)
138
+
139
+ // TODO return an Option instead of `select 1` if there are no update values
140
+ if (Object.keys(updateValues).length === 0) {
141
+ return [sql`select 1`, {}]
142
+ }
143
+
144
+ const updateValueStr = Object.keys(updateValues)
145
+ .map((columnName) => `${columnName} = $update_${columnName}`)
146
+ .join(', ')
147
+
148
+ const bindValues = {
149
+ ...makeBindValues({ columns, values: updateValues, variablePrefix: 'update_' }),
150
+ ...makeBindValues({ columns, values: where, variablePrefix: 'where_', skipNil: true }),
151
+ }
152
+
153
+ const whereSql = buildWhereSql({ where })
154
+ const whereModifier = whereSql === '' ? '' : `WHERE ${whereSql}`
155
+
156
+ return [sql`UPDATE ${tableName} SET ${updateValueStr} ${whereModifier}`, bindValues]
157
+ }
158
+
159
+ export const deleteRows = <TColumns extends SqliteDsl.Columns>({
160
+ columns,
161
+ tableName,
162
+ where,
163
+ }: {
164
+ columns: TColumns
165
+ tableName: string
166
+ where: ClientTypes.WhereValuesForColumns<TColumns>
167
+ }): [string, BindValues] => {
168
+ const bindValues = {
169
+ ...makeBindValues({ columns, values: where, variablePrefix: 'where_', skipNil: true }),
170
+ }
171
+
172
+ const whereSql = buildWhereSql({ where })
173
+ const whereModifier = whereSql === '' ? '' : `WHERE ${whereSql}`
174
+
175
+ return [sql`DELETE FROM ${tableName} ${whereModifier}`, bindValues]
176
+ }
177
+
178
+ export const upsertRow = <TColumns extends SqliteDsl.Columns>({
179
+ tableName,
180
+ columns,
181
+ createValues: createValues_,
182
+ updateValues: updateValues_,
183
+ where,
184
+ }: {
185
+ tableName: string
186
+ columns: TColumns
187
+ createValues: ClientTypes.DecodedValuesForColumns<TColumns>
188
+ updateValues: Partial<ClientTypes.DecodedValuesForColumnsAll<TColumns>>
189
+ // TODO where VALUES are actually not used here. Maybe adjust API?
190
+ where: ClientTypes.WhereValuesForColumns<TColumns>
191
+ }): [string, BindValues] => {
192
+ const createValues = filterUndefinedFields(createValues_)
193
+ const updateValues = filterUndefinedFields(updateValues_)
194
+
195
+ const keysStr = Object.keys(createValues).join(', ')
196
+
197
+ const createValuesStr = Object.keys(createValues)
198
+ .map((_) => `$create_${_}`)
199
+ .join(', ')
200
+
201
+ const conflictStr = Object.keys(where).join(', ')
202
+
203
+ const updateValueStr = Object.keys(updateValues)
204
+ .map((columnName) => `${columnName} = $update_${columnName}`)
205
+ .join(', ')
206
+
207
+ const bindValues = {
208
+ ...makeBindValues({ columns, values: createValues, variablePrefix: 'create_' }),
209
+ ...makeBindValues({ columns, values: updateValues, variablePrefix: 'update_' }),
210
+ }
211
+
212
+ return [
213
+ sql`
214
+ INSERT INTO ${tableName} (${keysStr})
215
+ VALUES (${createValuesStr})
216
+ ON CONFLICT (${conflictStr}) DO UPDATE SET ${updateValueStr}
217
+ `,
218
+ bindValues,
219
+ ]
220
+ }
221
+
222
+ export const createTable = ({
223
+ table,
224
+ tableName,
225
+ }: {
226
+ table: SqliteDsl.TableDefinition<any, SqliteDsl.Columns>
227
+ tableName: string
228
+ }): string => {
229
+ const primaryKeys = Object.entries(table.columns)
230
+ .filter(([_, columnDef]) => columnDef.primaryKey)
231
+ .map(([columnName, _]) => columnName)
232
+ const columnDefStrs = Object.entries(table.columns).map(([columnName, columnDef]) => {
233
+ const nullModifier = columnDef.nullable === true ? '' : 'NOT NULL'
234
+ const defaultModifier = columnDef.default._tag === 'None' ? '' : `DEFAULT ${columnDef.default.value}`
235
+ return sql`${columnName} ${columnDef.columnType} ${nullModifier} ${defaultModifier}`
236
+ })
237
+
238
+ if (primaryKeys.length > 0) {
239
+ columnDefStrs.push(`PRIMARY KEY (${primaryKeys.join(', ')})`)
240
+ }
241
+
242
+ return sql`CREATE TABLE ${tableName} (${columnDefStrs.join(', ')});`
243
+ }
244
+
245
+ export const makeBindValues = <TColumns extends SqliteDsl.Columns, TKeys extends string>({
246
+ columns,
247
+ values,
248
+ variablePrefix = '',
249
+ skipNil,
250
+ }: {
251
+ columns: TColumns
252
+ values: Record<TKeys, any>
253
+ variablePrefix?: string
254
+ /** So far only used to prepare `where` statements */
255
+ skipNil?: boolean
256
+ }): Record<string, any> => {
257
+ const codecMap = pipe(
258
+ columns,
259
+ objectEntries,
260
+ ReadonlyArray.map(([columnName, columnDef]) => [
261
+ columnName,
262
+ (value: any) => {
263
+ if (columnDef.nullable === true && (value === null || value === undefined)) return null
264
+ const res = Schema.encodeEither(columnDef.schema)(value)
265
+ if (res._tag === 'Left') {
266
+ debugger
267
+ throw res.left
268
+ } else {
269
+ return res.right
270
+ }
271
+ },
272
+ ]),
273
+ Object.fromEntries,
274
+ )
275
+
276
+ return pipe(
277
+ Object.entries(values)
278
+ // NOTE null/undefined values are handled via explicit SQL syntax and don't need to be provided as bind values
279
+ .filter(([, value]) => skipNil !== true || (value !== null && value !== undefined))
280
+ .flatMap(([columnName, value]: [string, any]) => {
281
+ // remap complex where-values with `op`
282
+ if (typeof value === 'object' && value !== null && 'op' in value) {
283
+ switch (value.op) {
284
+ case 'in': {
285
+ return value.val.map((value: any, i: number) => [
286
+ `${variablePrefix}${columnName}_${i}`,
287
+ codecMap[columnName]!(value),
288
+ ])
289
+ }
290
+ case '=':
291
+ case '>':
292
+ case '<': {
293
+ return [[`${variablePrefix}${columnName}`, codecMap[columnName]!(value.val)]]
294
+ }
295
+ default: {
296
+ throw new Error(`Unknown op: ${value.op}`)
297
+ }
298
+ }
299
+ } else {
300
+ return [[`${variablePrefix}${columnName}`, codecMap[columnName]!(value)]]
301
+ }
302
+ }),
303
+ Object.fromEntries,
304
+ )
305
+ }
306
+
307
+ const buildWhereSql = <TColumns extends SqliteDsl.Columns>({
308
+ where,
309
+ }: {
310
+ where: ClientTypes.WhereValuesForColumns<TColumns>
311
+ }) => {
312
+ const getWhereOp = (columnName: string, value: ClientTypes.WhereValueForDecoded<any>) => {
313
+ if (value === null) {
314
+ return `IS NULL`
315
+ } else if (typeof value === 'object' && typeof value.op === 'string' && ClientTypes.isValidWhereOp(value.op)) {
316
+ return `${value.op} $where_${columnName}`
317
+ } else if (typeof value === 'object' && typeof value.op === 'string' && value.op === 'in') {
318
+ return `in (${value.val.map((_: any, i: number) => `$where_${columnName}_${i}`).join(', ')})`
319
+ } else {
320
+ return `= $where_${columnName}`
321
+ }
322
+ }
323
+
324
+ return pipe(
325
+ where,
326
+ objectEntries,
327
+ ReadonlyArray.map(([columnName, value]) => `${columnName} ${getWhereOp(columnName, value)}`),
328
+ ReadonlyArray.join(' AND '),
329
+ )
330
+ }
331
+
332
+ // TODO better typing
333
+ const filterUndefinedFields = <T extends Record<string, any>>(obj: T): T => {
334
+ return Object.fromEntries(Object.entries(obj).filter(([, value]) => value !== undefined)) as T
335
+ }
@@ -0,0 +1,135 @@
1
+ import type { SqliteDsl } from 'effect-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 { Schema } from '@livestore/utils/effect'
2
+ import type { Prettify, SqliteDsl } from 'effect-db-schema'
3
+
4
+ export type DecodedValuesForTableAll<TSchema extends SqliteDsl.DbSchema, TTableName extends keyof TSchema> = {
5
+ [K in keyof GetColumns<TSchema, TTableName>]: Schema.Schema.To<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.To<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.To<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.From<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]: {}
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 }
package/src/util.ts ADDED
@@ -0,0 +1,46 @@
1
+ /// <reference lib="es2022" />
2
+
3
+ import type { Brand } from '@livestore/utils/effect'
4
+
5
+ export type ParamsObject = Record<string, SqlValue>
6
+ export type SqlValue = string | number | Uint8Array | null
7
+
8
+ export type Bindable = SqlValue[] | ParamsObject
9
+
10
+ export type PreparedBindValues = Brand.Branded<Bindable, 'PreparedBindValues'>
11
+
12
+ /**
13
+ * This is a tag function for tagged literals.
14
+ * it lets us get syntax highlighting on SQL queries in VSCode, but
15
+ * doesn't do anything at runtime.
16
+ * Code copied from: https://esdiscuss.org/topic/string-identity-template-tag
17
+ */
18
+ export const sql = (template: TemplateStringsArray, ...args: unknown[]): string => {
19
+ let str = ''
20
+
21
+ for (const [i, arg] of args.entries()) {
22
+ str += template[i] + String(arg)
23
+ }
24
+
25
+ // eslint-disable-next-line unicorn/prefer-at
26
+ return str + template[template.length - 1]
27
+ }
28
+
29
+ /** Prepare bind values to send to SQLite
30
+ /* Add $ to the beginning of keys; which we use as our interpolation syntax
31
+ /* We also strip out any params that aren't used in the statement,
32
+ /* because rusqlite doesn't allow unused named params
33
+ /* TODO: Search for unused params via proper parsing, not string search
34
+ **/
35
+ export const prepareBindValues = (values: Bindable, statement: string): PreparedBindValues => {
36
+ if (Array.isArray(values)) return values as PreparedBindValues
37
+
38
+ const result: ParamsObject = {}
39
+ for (const [key, value] of Object.entries(values)) {
40
+ if (statement.includes(key)) {
41
+ result[`$${key}`] = value
42
+ }
43
+ }
44
+
45
+ return result as PreparedBindValues
46
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "tsBuildInfoFile": "./dist/.tsbuildinfo"
7
+ },
8
+ "include": ["./src"],
9
+ "references": [{ "path": "../../effect-db-schema" }, { "path": "../utils" }]
10
+ }