@livestore/common 0.0.0-snapshot-3ea7644e665c5c8292d2309fb7f837b9146af912 → 0.0.0-snapshot-ba25981b6de87a90976fc39e1c2551844d384a05
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/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/fixture.d.ts +34 -46
- package/dist/__tests__/fixture.d.ts.map +1 -1
- package/dist/adapter-types.d.ts +1 -1
- package/dist/adapter-types.js +1 -1
- package/dist/derived-mutations.d.ts +4 -4
- package/dist/derived-mutations.d.ts.map +1 -1
- package/dist/derived-mutations.js.map +1 -1
- package/dist/devtools/devtools-messages.d.ts +45 -45
- package/dist/devtools/devtools-messages.js +1 -1
- package/dist/devtools/devtools-messages.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/mutation.d.ts +1 -1
- package/dist/mutation.d.ts.map +1 -1
- package/dist/mutation.js +1 -6
- package/dist/mutation.js.map +1 -1
- package/dist/query-info.d.ts +39 -29
- package/dist/query-info.d.ts.map +1 -1
- package/dist/query-info.js +35 -4
- package/dist/query-info.js.map +1 -1
- package/dist/schema/index.d.ts +2 -2
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +0 -1
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/schema-helpers.d.ts +2 -2
- package/dist/schema/schema-helpers.d.ts.map +1 -1
- package/dist/schema/system-tables.d.ts +204 -246
- package/dist/schema/system-tables.d.ts.map +1 -1
- package/dist/schema/table-def.d.ts +24 -45
- package/dist/schema/table-def.d.ts.map +1 -1
- package/dist/schema/table-def.js +1 -10
- package/dist/schema/table-def.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +3 -3
- package/src/adapter-types.ts +1 -1
- package/src/derived-mutations.ts +8 -4
- package/src/devtools/devtools-messages.ts +1 -1
- package/src/index.ts +0 -1
- package/src/mutation.ts +2 -9
- package/src/query-info.ts +93 -66
- package/src/schema/index.ts +2 -4
- package/src/schema/schema-helpers.ts +2 -2
- package/src/schema/table-def.ts +68 -99
- package/src/version.ts +1 -1
- package/dist/query-builder/api.d.ts +0 -190
- package/dist/query-builder/api.d.ts.map +0 -1
- package/dist/query-builder/api.js +0 -8
- package/dist/query-builder/api.js.map +0 -1
- package/dist/query-builder/impl.d.ts +0 -12
- package/dist/query-builder/impl.d.ts.map +0 -1
- package/dist/query-builder/impl.js +0 -226
- package/dist/query-builder/impl.js.map +0 -1
- package/dist/query-builder/impl.test.d.ts +0 -2
- package/dist/query-builder/impl.test.d.ts.map +0 -1
- package/dist/query-builder/impl.test.js +0 -183
- package/dist/query-builder/impl.test.js.map +0 -1
- package/dist/query-builder/mod.d.ts +0 -10
- package/dist/query-builder/mod.d.ts.map +0 -1
- package/dist/query-builder/mod.js +0 -10
- package/dist/query-builder/mod.js.map +0 -1
- package/src/query-builder/api.ts +0 -288
- package/src/query-builder/impl.test.ts +0 -205
- package/src/query-builder/impl.ts +0 -268
- package/src/query-builder/mod.ts +0 -10
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
import { Option, Predicate, Schema } from '@livestore/utils/effect'
|
|
2
|
-
|
|
3
|
-
import type { QueryInfo } from '../query-info.js'
|
|
4
|
-
import type { DbSchema } from '../schema/index.js'
|
|
5
|
-
import type { QueryBuilder, QueryBuilderAst } from './api.js'
|
|
6
|
-
import { QueryBuilderAstSymbol, TypeId } from './api.js'
|
|
7
|
-
|
|
8
|
-
export const makeQueryBuilder = <TResult, TTableDef extends DbSchema.TableDefBase>(
|
|
9
|
-
tableDef: TTableDef,
|
|
10
|
-
ast: QueryBuilderAst = emptyAst(tableDef),
|
|
11
|
-
): QueryBuilder<TResult, TTableDef, never, QueryInfo.None> => {
|
|
12
|
-
const api = {
|
|
13
|
-
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
|
|
14
|
-
select() {
|
|
15
|
-
assertQueryBuilderAst(ast)
|
|
16
|
-
|
|
17
|
-
// eslint-disable-next-line prefer-rest-params
|
|
18
|
-
const params = [...arguments]
|
|
19
|
-
|
|
20
|
-
if (params.length === 2 && typeof params[0] === 'string' && typeof params[1] === 'object') {
|
|
21
|
-
const [col, options] = params as any as [string, { pluck: boolean }]
|
|
22
|
-
return makeQueryBuilder(tableDef, {
|
|
23
|
-
...ast,
|
|
24
|
-
resultSchemaSingle: options.pluck ? ast.resultSchemaSingle.pipe(Schema.pluck(col)) : ast.resultSchemaSingle,
|
|
25
|
-
select: { columns: [col] },
|
|
26
|
-
})
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const columns = params as unknown as ReadonlyArray<string>
|
|
30
|
-
|
|
31
|
-
return makeQueryBuilder(tableDef, {
|
|
32
|
-
...ast,
|
|
33
|
-
resultSchemaSingle:
|
|
34
|
-
columns.length === 0 ? ast.resultSchemaSingle : ast.resultSchemaSingle.pipe(Schema.pick(...columns)),
|
|
35
|
-
select: { columns },
|
|
36
|
-
}) as any
|
|
37
|
-
},
|
|
38
|
-
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
|
|
39
|
-
where() {
|
|
40
|
-
if (isRowQuery(ast)) return invalidQueryBuilder()
|
|
41
|
-
|
|
42
|
-
if (arguments.length === 1) {
|
|
43
|
-
// eslint-disable-next-line prefer-rest-params
|
|
44
|
-
const params = arguments[0]
|
|
45
|
-
const newOps = Object.entries(params)
|
|
46
|
-
.filter(([, value]) => value !== undefined)
|
|
47
|
-
.map<QueryBuilderAst.Where>(([col, value]) =>
|
|
48
|
-
Predicate.hasProperty(value, 'op') && Predicate.hasProperty(value, 'value')
|
|
49
|
-
? ({ col, op: value.op, value: value.value } as any)
|
|
50
|
-
: { col, op: '=', value },
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
return makeQueryBuilder(tableDef, {
|
|
54
|
-
...ast,
|
|
55
|
-
where: [...ast.where, ...newOps],
|
|
56
|
-
}) as any
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// eslint-disable-next-line prefer-rest-params
|
|
60
|
-
const [col, opOrValue, valueOrUndefined] = arguments
|
|
61
|
-
const op = valueOrUndefined === undefined ? '=' : opOrValue
|
|
62
|
-
const value = valueOrUndefined === undefined ? opOrValue : valueOrUndefined
|
|
63
|
-
return makeQueryBuilder(tableDef, {
|
|
64
|
-
...ast,
|
|
65
|
-
where: [...ast.where, { col, op, value }],
|
|
66
|
-
})
|
|
67
|
-
},
|
|
68
|
-
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
|
|
69
|
-
orderBy() {
|
|
70
|
-
assertQueryBuilderAst(ast)
|
|
71
|
-
|
|
72
|
-
if (arguments.length === 0 || arguments.length > 2) return invalidQueryBuilder()
|
|
73
|
-
|
|
74
|
-
if (arguments.length === 1) {
|
|
75
|
-
// eslint-disable-next-line prefer-rest-params
|
|
76
|
-
const params = arguments[0] as QueryBuilder.OrderByParams<TTableDef>
|
|
77
|
-
return makeQueryBuilder(tableDef, {
|
|
78
|
-
...ast,
|
|
79
|
-
orderBy: [...ast.orderBy, ...params],
|
|
80
|
-
})
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// eslint-disable-next-line prefer-rest-params
|
|
84
|
-
const [col, direction] = arguments as any as [keyof TTableDef['sqliteDef']['columns'] & string, 'asc' | 'desc']
|
|
85
|
-
|
|
86
|
-
return makeQueryBuilder(tableDef, {
|
|
87
|
-
...ast,
|
|
88
|
-
orderBy: [...ast.orderBy, { col, direction }],
|
|
89
|
-
}) as any
|
|
90
|
-
},
|
|
91
|
-
limit: (limit) => {
|
|
92
|
-
assertQueryBuilderAst(ast)
|
|
93
|
-
|
|
94
|
-
return makeQueryBuilder(tableDef, { ...ast, limit: Option.some(limit) })
|
|
95
|
-
},
|
|
96
|
-
offset: (offset) => {
|
|
97
|
-
assertQueryBuilderAst(ast)
|
|
98
|
-
|
|
99
|
-
return makeQueryBuilder(tableDef, { ...ast, offset: Option.some(offset) })
|
|
100
|
-
},
|
|
101
|
-
count: () => {
|
|
102
|
-
if (isRowQuery(ast)) return invalidQueryBuilder()
|
|
103
|
-
|
|
104
|
-
return makeQueryBuilder(tableDef, {
|
|
105
|
-
...ast,
|
|
106
|
-
resultSchema: Schema.Struct({ count: Schema.Number }).pipe(
|
|
107
|
-
Schema.pluck('count'),
|
|
108
|
-
Schema.Array,
|
|
109
|
-
Schema.headOrElse(),
|
|
110
|
-
),
|
|
111
|
-
_tag: 'CountQuery',
|
|
112
|
-
})
|
|
113
|
-
},
|
|
114
|
-
first: (options) => {
|
|
115
|
-
assertQueryBuilderAst(ast)
|
|
116
|
-
|
|
117
|
-
if (ast.limit._tag === 'Some') return invalidQueryBuilder(`.first() can't be called after .limit()`)
|
|
118
|
-
|
|
119
|
-
return makeQueryBuilder(tableDef, {
|
|
120
|
-
...ast,
|
|
121
|
-
limit: Option.some(1),
|
|
122
|
-
pickFirst: options?.fallback ? { fallback: options.fallback } : false,
|
|
123
|
-
})
|
|
124
|
-
},
|
|
125
|
-
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
|
|
126
|
-
row() {
|
|
127
|
-
// eslint-disable-next-line prefer-rest-params
|
|
128
|
-
const params = [...arguments]
|
|
129
|
-
|
|
130
|
-
let id: string
|
|
131
|
-
|
|
132
|
-
if (tableDef.options.isSingleton) {
|
|
133
|
-
id = tableDef.sqliteDef.columns.id!.default.pipe(Option.getOrThrow)
|
|
134
|
-
} else {
|
|
135
|
-
id = params[0] as string
|
|
136
|
-
if (id === undefined) {
|
|
137
|
-
invalidQueryBuilder(`Id missing for row query on non-singleton table ${tableDef.sqliteDef.name}`)
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// TODO validate all required columns are present and values are matching the schema
|
|
142
|
-
const insertValues: Record<string, unknown> = params[1]?.insertValues ?? {}
|
|
143
|
-
|
|
144
|
-
return makeQueryBuilder(tableDef, {
|
|
145
|
-
_tag: 'RowQuery',
|
|
146
|
-
id,
|
|
147
|
-
tableDef,
|
|
148
|
-
insertValues,
|
|
149
|
-
}) as any
|
|
150
|
-
},
|
|
151
|
-
} satisfies QueryBuilder.ApiFull<TResult, TTableDef, never, QueryInfo.None>
|
|
152
|
-
|
|
153
|
-
return {
|
|
154
|
-
[TypeId]: TypeId,
|
|
155
|
-
[QueryBuilderAstSymbol]: ast,
|
|
156
|
-
asSql: () => astToSql(ast),
|
|
157
|
-
toString: () => astToSql(ast).query,
|
|
158
|
-
...api,
|
|
159
|
-
} satisfies QueryBuilder<TResult, TTableDef>
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const emptyAst = (tableDef: DbSchema.TableDefBase) =>
|
|
163
|
-
({
|
|
164
|
-
_tag: 'SelectQuery',
|
|
165
|
-
columns: [],
|
|
166
|
-
pickFirst: false,
|
|
167
|
-
select: { columns: [] },
|
|
168
|
-
orderBy: [],
|
|
169
|
-
offset: Option.none(),
|
|
170
|
-
limit: Option.none(),
|
|
171
|
-
tableDef,
|
|
172
|
-
where: [],
|
|
173
|
-
resultSchemaSingle: tableDef.schema,
|
|
174
|
-
}) satisfies QueryBuilderAst
|
|
175
|
-
|
|
176
|
-
const astToSql = (ast: QueryBuilderAst) => {
|
|
177
|
-
if (isRowQuery(ast)) {
|
|
178
|
-
// TODO
|
|
179
|
-
return { query: `SELECT * FROM '${ast.tableDef.sqliteDef.name}' WHERE id = ?`, bindValues: [ast.id as TODO] }
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const bindValues: unknown[] = []
|
|
183
|
-
|
|
184
|
-
// TODO bind values
|
|
185
|
-
const whereStmt =
|
|
186
|
-
ast.where.length > 0
|
|
187
|
-
? `WHERE ${ast.where
|
|
188
|
-
.map(({ col, op, value }) => {
|
|
189
|
-
if (value === null) {
|
|
190
|
-
if (op !== '=' && op !== '!=') {
|
|
191
|
-
throw new Error(`Unsupported operator for NULL value: ${op}`)
|
|
192
|
-
}
|
|
193
|
-
const opStmt = op === '=' ? 'IS' : 'IS NOT'
|
|
194
|
-
return `${col} ${opStmt} NULL`
|
|
195
|
-
} else {
|
|
196
|
-
const colDef = ast.tableDef.sqliteDef.columns[col]
|
|
197
|
-
if (colDef === undefined) {
|
|
198
|
-
throw new Error(`Column ${col} not found`)
|
|
199
|
-
}
|
|
200
|
-
const encodedValue = Schema.encodeSync(colDef.schema)(value)
|
|
201
|
-
bindValues.push(encodedValue)
|
|
202
|
-
return `${col} ${op} ?`
|
|
203
|
-
}
|
|
204
|
-
})
|
|
205
|
-
.join(' AND ')}`
|
|
206
|
-
: ''
|
|
207
|
-
|
|
208
|
-
if (ast._tag === 'CountQuery') {
|
|
209
|
-
const selectFromStmt = `SELECT COUNT(*) as count FROM '${ast.tableDef.sqliteDef.name}'`
|
|
210
|
-
const query = [selectFromStmt, whereStmt].filter((_) => _.length > 0).join(' ')
|
|
211
|
-
return { query, bindValues }
|
|
212
|
-
}
|
|
213
|
-
const columnsStmt = ast.select.columns.length === 0 ? '*' : ast.select.columns.join(', ')
|
|
214
|
-
const selectStmt = `SELECT ${columnsStmt}`
|
|
215
|
-
const fromStmt = `FROM '${ast.tableDef.sqliteDef.name}'`
|
|
216
|
-
|
|
217
|
-
const orderByStmt =
|
|
218
|
-
ast.orderBy.length > 0
|
|
219
|
-
? `ORDER BY ${ast.orderBy.map(({ col, direction }) => `${col} ${direction}`).join(', ')}`
|
|
220
|
-
: ''
|
|
221
|
-
|
|
222
|
-
const limitStmt = ast.limit._tag === 'Some' ? `LIMIT ?` : ''
|
|
223
|
-
if (ast.limit._tag === 'Some') bindValues.push(ast.limit.value)
|
|
224
|
-
|
|
225
|
-
const offsetStmt = ast.offset._tag === 'Some' ? `OFFSET ?` : ''
|
|
226
|
-
if (ast.offset._tag === 'Some') bindValues.push(ast.offset.value)
|
|
227
|
-
|
|
228
|
-
const query = [selectStmt, fromStmt, whereStmt, orderByStmt, offsetStmt, limitStmt]
|
|
229
|
-
.map((_) => _.trim())
|
|
230
|
-
.filter((_) => _.length > 0)
|
|
231
|
-
.join(' ')
|
|
232
|
-
|
|
233
|
-
// TODO bind values
|
|
234
|
-
return { query, bindValues }
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
|
|
238
|
-
function assertQueryBuilderAst(ast: QueryBuilderAst): asserts ast is QueryBuilderAst.SelectQuery {
|
|
239
|
-
if (ast._tag !== 'SelectQuery') {
|
|
240
|
-
throw new Error('Expected SelectQuery but got ' + ast._tag)
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const isRowQuery = (ast: QueryBuilderAst): ast is QueryBuilderAst.RowQuery => ast._tag === 'RowQuery'
|
|
245
|
-
|
|
246
|
-
export const invalidQueryBuilder = (msg?: string) => {
|
|
247
|
-
throw new Error('Invalid query builder' + (msg ? `: ${msg}` : ''))
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
export const getResultSchema = (qb: QueryBuilder<any, any, any>) => {
|
|
251
|
-
const queryAst = qb[QueryBuilderAstSymbol]
|
|
252
|
-
if (queryAst._tag === 'SelectQuery') {
|
|
253
|
-
const arraySchema = Schema.Array(queryAst.resultSchemaSingle)
|
|
254
|
-
if (queryAst.pickFirst !== false) {
|
|
255
|
-
return arraySchema.pipe(Schema.headOrElse(queryAst.pickFirst.fallback))
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return arraySchema
|
|
259
|
-
} else if (queryAst._tag === 'CountQuery') {
|
|
260
|
-
return Schema.Struct({ count: Schema.Number }).pipe(Schema.pluck('count'), Schema.Array, Schema.headOrElse())
|
|
261
|
-
} else {
|
|
262
|
-
if (queryAst.tableDef.options.isSingleColumn) {
|
|
263
|
-
return queryAst.tableDef.schema.pipe(Schema.pluck('value'), Schema.Array, Schema.headOrElse())
|
|
264
|
-
} else {
|
|
265
|
-
return queryAst.tableDef.schema.pipe(Schema.Array, Schema.headOrElse())
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
package/src/query-builder/mod.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export * from './api.js'
|
|
2
|
-
export * from './impl.js'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Design decisions:
|
|
6
|
-
*
|
|
7
|
-
* - Close abstraction to SQLite to provide a simple & convenient API with predictable behaviour
|
|
8
|
-
* - Use table schema definitions to parse, map & validate query results
|
|
9
|
-
* - Implementation detail: Separate type-level & AST-based runtime implementation
|
|
10
|
-
*/
|