@remix-run/data-table 0.0.0 → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +298 -2
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/lib/adapter.d.ts +180 -0
- package/dist/lib/adapter.d.ts.map +1 -0
- package/dist/lib/adapter.js +1 -0
- package/dist/lib/database.d.ts +361 -0
- package/dist/lib/database.d.ts.map +1 -0
- package/dist/lib/database.js +1368 -0
- package/dist/lib/errors.d.ts +50 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +67 -0
- package/dist/lib/inflection.d.ts +3 -0
- package/dist/lib/inflection.d.ts.map +1 -0
- package/dist/lib/inflection.js +56 -0
- package/dist/lib/operators.d.ts +151 -0
- package/dist/lib/operators.d.ts.map +1 -0
- package/dist/lib/operators.js +218 -0
- package/dist/lib/references.d.ts +42 -0
- package/dist/lib/references.d.ts.map +1 -0
- package/dist/lib/references.js +33 -0
- package/dist/lib/sql.d.ts +28 -0
- package/dist/lib/sql.d.ts.map +1 -0
- package/dist/lib/sql.js +51 -0
- package/dist/lib/table.d.ts +254 -0
- package/dist/lib/table.d.ts.map +1 -0
- package/dist/lib/table.js +496 -0
- package/dist/lib/types.d.ts +4 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +1 -0
- package/package.json +41 -7
- package/src/index.ts +115 -0
- package/src/lib/adapter.ts +209 -0
- package/src/lib/database.ts +2458 -0
- package/src/lib/errors.ts +109 -0
- package/src/lib/inflection.ts +69 -0
- package/src/lib/operators.ts +433 -0
- package/src/lib/references.ts +79 -0
- package/src/lib/sql.ts +67 -0
- package/src/lib/table.ts +981 -0
- package/src/lib/types.ts +3 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error for all `data-table` failures.
|
|
3
|
+
*/
|
|
4
|
+
export class DataTableError extends Error {
|
|
5
|
+
code: string
|
|
6
|
+
metadata?: Record<string, unknown>
|
|
7
|
+
|
|
8
|
+
constructor(
|
|
9
|
+
message: string,
|
|
10
|
+
options?: {
|
|
11
|
+
code?: string
|
|
12
|
+
cause?: unknown
|
|
13
|
+
metadata?: Record<string, unknown>
|
|
14
|
+
},
|
|
15
|
+
) {
|
|
16
|
+
super(message, { cause: options?.cause })
|
|
17
|
+
this.name = 'DataTableError'
|
|
18
|
+
this.code = options?.code ?? 'DATA_TABLE_ERROR'
|
|
19
|
+
this.metadata = options?.metadata
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Thrown when input data fails schema validation.
|
|
25
|
+
*/
|
|
26
|
+
export class DataTableValidationError extends DataTableError {
|
|
27
|
+
issues: ReadonlyArray<unknown>
|
|
28
|
+
|
|
29
|
+
constructor(
|
|
30
|
+
message: string,
|
|
31
|
+
issues: ReadonlyArray<unknown>,
|
|
32
|
+
options?: {
|
|
33
|
+
cause?: unknown
|
|
34
|
+
metadata?: Record<string, unknown>
|
|
35
|
+
},
|
|
36
|
+
) {
|
|
37
|
+
super(message, {
|
|
38
|
+
code: 'DATA_TABLE_VALIDATION_ERROR',
|
|
39
|
+
cause: options?.cause,
|
|
40
|
+
metadata: options?.metadata,
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
this.name = 'DataTableValidationError'
|
|
44
|
+
this.issues = issues
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Thrown when a query is invalid for the current builder state.
|
|
50
|
+
*/
|
|
51
|
+
export class DataTableQueryError extends DataTableError {
|
|
52
|
+
constructor(
|
|
53
|
+
message: string,
|
|
54
|
+
options?: {
|
|
55
|
+
cause?: unknown
|
|
56
|
+
metadata?: Record<string, unknown>
|
|
57
|
+
},
|
|
58
|
+
) {
|
|
59
|
+
super(message, {
|
|
60
|
+
code: 'DATA_TABLE_QUERY_ERROR',
|
|
61
|
+
cause: options?.cause,
|
|
62
|
+
metadata: options?.metadata,
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
this.name = 'DataTableQueryError'
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Thrown when adapter execution fails.
|
|
71
|
+
*/
|
|
72
|
+
export class DataTableAdapterError extends DataTableError {
|
|
73
|
+
constructor(
|
|
74
|
+
message: string,
|
|
75
|
+
options?: {
|
|
76
|
+
cause?: unknown
|
|
77
|
+
metadata?: Record<string, unknown>
|
|
78
|
+
},
|
|
79
|
+
) {
|
|
80
|
+
super(message, {
|
|
81
|
+
code: 'DATA_TABLE_ADAPTER_ERROR',
|
|
82
|
+
cause: options?.cause,
|
|
83
|
+
metadata: options?.metadata,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
this.name = 'DataTableAdapterError'
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Thrown when a database constraint is violated.
|
|
92
|
+
*/
|
|
93
|
+
export class DataTableConstraintError extends DataTableError {
|
|
94
|
+
constructor(
|
|
95
|
+
message: string,
|
|
96
|
+
options?: {
|
|
97
|
+
cause?: unknown
|
|
98
|
+
metadata?: Record<string, unknown>
|
|
99
|
+
},
|
|
100
|
+
) {
|
|
101
|
+
super(message, {
|
|
102
|
+
code: 'DATA_TABLE_CONSTRAINT_ERROR',
|
|
103
|
+
cause: options?.cause,
|
|
104
|
+
metadata: options?.metadata,
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
this.name = 'DataTableConstraintError'
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const IRREGULAR_SINGULAR_FORMS: Record<string, string> = {
|
|
2
|
+
people: 'person',
|
|
3
|
+
men: 'man',
|
|
4
|
+
women: 'woman',
|
|
5
|
+
children: 'child',
|
|
6
|
+
teeth: 'tooth',
|
|
7
|
+
feet: 'foot',
|
|
8
|
+
geese: 'goose',
|
|
9
|
+
mice: 'mouse',
|
|
10
|
+
data: 'datum',
|
|
11
|
+
media: 'medium',
|
|
12
|
+
indices: 'index',
|
|
13
|
+
vertices: 'vertex',
|
|
14
|
+
analyses: 'analysis',
|
|
15
|
+
statuses: 'status',
|
|
16
|
+
categories: 'category',
|
|
17
|
+
companies: 'company',
|
|
18
|
+
addresses: 'address',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function singularize(word: string): string {
|
|
22
|
+
let lower = word.toLowerCase()
|
|
23
|
+
|
|
24
|
+
if (IRREGULAR_SINGULAR_FORMS[lower]) {
|
|
25
|
+
return preserveCase(word, IRREGULAR_SINGULAR_FORMS[lower])
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (lower.endsWith('ies') && lower.length > 3) {
|
|
29
|
+
return preserveCase(word, word.slice(0, -3) + 'y')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (lower.endsWith('sses') || lower.endsWith('shes') || lower.endsWith('ches')) {
|
|
33
|
+
return preserveCase(word, word.slice(0, -2))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (lower.endsWith('xes') || lower.endsWith('zes')) {
|
|
37
|
+
return preserveCase(word, word.slice(0, -2))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (lower.endsWith('s') && !lower.endsWith('ss')) {
|
|
41
|
+
return preserveCase(word, word.slice(0, -1))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return word
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function inferForeignKey(tableName: string): string {
|
|
48
|
+
let segments = tableName.split('_')
|
|
49
|
+
let tail = segments.pop() ?? tableName
|
|
50
|
+
let singularTail = singularize(tail)
|
|
51
|
+
|
|
52
|
+
if (segments.length === 0) {
|
|
53
|
+
return singularTail + '_id'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return [...segments, singularTail + '_id'].join('_')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function preserveCase(source: string, replacement: string): string {
|
|
60
|
+
if (source.length === 0) {
|
|
61
|
+
return replacement
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (source[0] === source[0].toUpperCase()) {
|
|
65
|
+
return replacement[0].toUpperCase() + replacement.slice(1)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return replacement
|
|
69
|
+
}
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
import { isColumnReference, normalizeColumnInput } from './references.ts'
|
|
2
|
+
import type { ColumnInput, ColumnReferenceLike, NormalizeColumnInput } from './references.ts'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Comparison operators supported by `comparison` predicates.
|
|
6
|
+
*/
|
|
7
|
+
export type ComparisonOperator =
|
|
8
|
+
| 'eq'
|
|
9
|
+
| 'ne'
|
|
10
|
+
| 'gt'
|
|
11
|
+
| 'gte'
|
|
12
|
+
| 'lt'
|
|
13
|
+
| 'lte'
|
|
14
|
+
| 'in'
|
|
15
|
+
| 'notIn'
|
|
16
|
+
| 'like'
|
|
17
|
+
| 'ilike'
|
|
18
|
+
|
|
19
|
+
type QualifiedColumnReference = `${string}.${string}`
|
|
20
|
+
|
|
21
|
+
type PredicateColumn<input extends string | ColumnReferenceLike> = NormalizeColumnInput<input> &
|
|
22
|
+
string
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Normalized predicate representation consumed by adapters.
|
|
26
|
+
*/
|
|
27
|
+
export type Predicate<column extends string = string> =
|
|
28
|
+
| {
|
|
29
|
+
type: 'comparison'
|
|
30
|
+
operator: ComparisonOperator
|
|
31
|
+
column: column
|
|
32
|
+
value: unknown
|
|
33
|
+
valueType: 'value'
|
|
34
|
+
}
|
|
35
|
+
| {
|
|
36
|
+
type: 'comparison'
|
|
37
|
+
operator: Exclude<ComparisonOperator, 'in' | 'notIn'>
|
|
38
|
+
column: column
|
|
39
|
+
value: column
|
|
40
|
+
valueType: 'column'
|
|
41
|
+
}
|
|
42
|
+
| {
|
|
43
|
+
type: 'between'
|
|
44
|
+
column: column
|
|
45
|
+
lower: unknown
|
|
46
|
+
upper: unknown
|
|
47
|
+
}
|
|
48
|
+
| {
|
|
49
|
+
type: 'null'
|
|
50
|
+
operator: 'isNull' | 'notNull'
|
|
51
|
+
column: column
|
|
52
|
+
}
|
|
53
|
+
| {
|
|
54
|
+
type: 'logical'
|
|
55
|
+
operator: 'and' | 'or'
|
|
56
|
+
predicates: Predicate<column>[]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type WhereObject<column extends string = string> = Partial<Record<column, unknown>>
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* User-facing where input accepted by `query.where()` and relation modifiers.
|
|
63
|
+
*/
|
|
64
|
+
export type WhereInput<column extends string = string> = Predicate<column> | WhereObject<column>
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Builds an equality predicate.
|
|
68
|
+
*/
|
|
69
|
+
export function eq<
|
|
70
|
+
left extends ColumnInput<QualifiedColumnReference>,
|
|
71
|
+
right extends ColumnInput<QualifiedColumnReference>,
|
|
72
|
+
>(
|
|
73
|
+
column: left,
|
|
74
|
+
value: right & (right extends `${string}@${string}` ? never : right),
|
|
75
|
+
): Predicate<PredicateColumn<left> | PredicateColumn<right>>
|
|
76
|
+
export function eq<column extends string | ColumnReferenceLike>(
|
|
77
|
+
column: column,
|
|
78
|
+
value: unknown,
|
|
79
|
+
): Predicate<PredicateColumn<column>>
|
|
80
|
+
export function eq(column: string | ColumnReferenceLike, value: unknown): Predicate<string> {
|
|
81
|
+
return createComparisonPredicate('eq', column, value)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Builds an inequality predicate.
|
|
86
|
+
*/
|
|
87
|
+
export function ne<
|
|
88
|
+
left extends ColumnInput<QualifiedColumnReference>,
|
|
89
|
+
right extends ColumnInput<QualifiedColumnReference>,
|
|
90
|
+
>(
|
|
91
|
+
column: left,
|
|
92
|
+
value: right & (right extends `${string}@${string}` ? never : right),
|
|
93
|
+
): Predicate<PredicateColumn<left> | PredicateColumn<right>>
|
|
94
|
+
export function ne<column extends string | ColumnReferenceLike>(
|
|
95
|
+
column: column,
|
|
96
|
+
value: unknown,
|
|
97
|
+
): Predicate<PredicateColumn<column>>
|
|
98
|
+
export function ne(column: string | ColumnReferenceLike, value: unknown): Predicate<string> {
|
|
99
|
+
return createComparisonPredicate('ne', column, value)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Builds a greater-than predicate.
|
|
104
|
+
*/
|
|
105
|
+
export function gt<
|
|
106
|
+
left extends ColumnInput<QualifiedColumnReference>,
|
|
107
|
+
right extends ColumnInput<QualifiedColumnReference>,
|
|
108
|
+
>(
|
|
109
|
+
column: left,
|
|
110
|
+
value: right & (right extends `${string}@${string}` ? never : right),
|
|
111
|
+
): Predicate<PredicateColumn<left> | PredicateColumn<right>>
|
|
112
|
+
export function gt<column extends string | ColumnReferenceLike>(
|
|
113
|
+
column: column,
|
|
114
|
+
value: unknown,
|
|
115
|
+
): Predicate<PredicateColumn<column>>
|
|
116
|
+
export function gt(column: string | ColumnReferenceLike, value: unknown): Predicate<string> {
|
|
117
|
+
return createComparisonPredicate('gt', column, value)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Builds a greater-than-or-equal predicate.
|
|
122
|
+
*/
|
|
123
|
+
export function gte<
|
|
124
|
+
left extends ColumnInput<QualifiedColumnReference>,
|
|
125
|
+
right extends ColumnInput<QualifiedColumnReference>,
|
|
126
|
+
>(
|
|
127
|
+
column: left,
|
|
128
|
+
value: right & (right extends `${string}@${string}` ? never : right),
|
|
129
|
+
): Predicate<PredicateColumn<left> | PredicateColumn<right>>
|
|
130
|
+
export function gte<column extends string | ColumnReferenceLike>(
|
|
131
|
+
column: column,
|
|
132
|
+
value: unknown,
|
|
133
|
+
): Predicate<PredicateColumn<column>>
|
|
134
|
+
export function gte(column: string | ColumnReferenceLike, value: unknown): Predicate<string> {
|
|
135
|
+
return createComparisonPredicate('gte', column, value)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Builds a less-than predicate.
|
|
140
|
+
*/
|
|
141
|
+
export function lt<
|
|
142
|
+
left extends ColumnInput<QualifiedColumnReference>,
|
|
143
|
+
right extends ColumnInput<QualifiedColumnReference>,
|
|
144
|
+
>(
|
|
145
|
+
column: left,
|
|
146
|
+
value: right & (right extends `${string}@${string}` ? never : right),
|
|
147
|
+
): Predicate<PredicateColumn<left> | PredicateColumn<right>>
|
|
148
|
+
export function lt<column extends string | ColumnReferenceLike>(
|
|
149
|
+
column: column,
|
|
150
|
+
value: unknown,
|
|
151
|
+
): Predicate<PredicateColumn<column>>
|
|
152
|
+
export function lt(column: string | ColumnReferenceLike, value: unknown): Predicate<string> {
|
|
153
|
+
return createComparisonPredicate('lt', column, value)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Builds a less-than-or-equal predicate.
|
|
158
|
+
*/
|
|
159
|
+
export function lte<
|
|
160
|
+
left extends ColumnInput<QualifiedColumnReference>,
|
|
161
|
+
right extends ColumnInput<QualifiedColumnReference>,
|
|
162
|
+
>(
|
|
163
|
+
column: left,
|
|
164
|
+
value: right & (right extends `${string}@${string}` ? never : right),
|
|
165
|
+
): Predicate<PredicateColumn<left> | PredicateColumn<right>>
|
|
166
|
+
export function lte<column extends string | ColumnReferenceLike>(
|
|
167
|
+
column: column,
|
|
168
|
+
value: unknown,
|
|
169
|
+
): Predicate<PredicateColumn<column>>
|
|
170
|
+
export function lte(column: string | ColumnReferenceLike, value: unknown): Predicate<string> {
|
|
171
|
+
return createComparisonPredicate('lte', column, value)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Builds an `IN` predicate.
|
|
176
|
+
* @param column Column to compare.
|
|
177
|
+
* @param values Candidate values.
|
|
178
|
+
* @returns An `in` comparison predicate.
|
|
179
|
+
*/
|
|
180
|
+
export function inList<column extends string | ColumnReferenceLike>(
|
|
181
|
+
column: column,
|
|
182
|
+
values: readonly unknown[],
|
|
183
|
+
): Predicate<PredicateColumn<column>> {
|
|
184
|
+
return {
|
|
185
|
+
type: 'comparison',
|
|
186
|
+
operator: 'in',
|
|
187
|
+
column: resolvePredicateColumn(column),
|
|
188
|
+
value: [...values],
|
|
189
|
+
valueType: 'value',
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Builds a `NOT IN` predicate.
|
|
195
|
+
* @param column Column to compare.
|
|
196
|
+
* @param values Candidate values.
|
|
197
|
+
* @returns A `notIn` comparison predicate.
|
|
198
|
+
*/
|
|
199
|
+
export function notInList<column extends string | ColumnReferenceLike>(
|
|
200
|
+
column: column,
|
|
201
|
+
values: readonly unknown[],
|
|
202
|
+
): Predicate<PredicateColumn<column>> {
|
|
203
|
+
return {
|
|
204
|
+
type: 'comparison',
|
|
205
|
+
operator: 'notIn',
|
|
206
|
+
column: resolvePredicateColumn(column),
|
|
207
|
+
value: [...values],
|
|
208
|
+
valueType: 'value',
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Builds a case-sensitive SQL `LIKE` predicate.
|
|
214
|
+
* @param column Column to compare.
|
|
215
|
+
* @param value Match pattern.
|
|
216
|
+
* @returns A `like` comparison predicate.
|
|
217
|
+
*/
|
|
218
|
+
export function like<column extends string | ColumnReferenceLike>(
|
|
219
|
+
column: column,
|
|
220
|
+
value: string,
|
|
221
|
+
): Predicate<PredicateColumn<column>> {
|
|
222
|
+
return {
|
|
223
|
+
type: 'comparison',
|
|
224
|
+
operator: 'like',
|
|
225
|
+
column: resolvePredicateColumn(column),
|
|
226
|
+
value,
|
|
227
|
+
valueType: 'value',
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Builds a case-insensitive SQL `LIKE` predicate.
|
|
233
|
+
* @param column Column to compare.
|
|
234
|
+
* @param value Match pattern.
|
|
235
|
+
* @returns An `ilike` comparison predicate.
|
|
236
|
+
*/
|
|
237
|
+
export function ilike<column extends string | ColumnReferenceLike>(
|
|
238
|
+
column: column,
|
|
239
|
+
value: string,
|
|
240
|
+
): Predicate<PredicateColumn<column>> {
|
|
241
|
+
return {
|
|
242
|
+
type: 'comparison',
|
|
243
|
+
operator: 'ilike',
|
|
244
|
+
column: resolvePredicateColumn(column),
|
|
245
|
+
value,
|
|
246
|
+
valueType: 'value',
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Builds a `BETWEEN` predicate.
|
|
252
|
+
* @param column Column to compare.
|
|
253
|
+
* @param lower Lower bound value.
|
|
254
|
+
* @param upper Upper bound value.
|
|
255
|
+
* @returns A `between` predicate.
|
|
256
|
+
*/
|
|
257
|
+
export function between<column extends string | ColumnReferenceLike>(
|
|
258
|
+
column: column,
|
|
259
|
+
lower: unknown,
|
|
260
|
+
upper: unknown,
|
|
261
|
+
): Predicate<PredicateColumn<column>> {
|
|
262
|
+
return {
|
|
263
|
+
type: 'between',
|
|
264
|
+
column: resolvePredicateColumn(column),
|
|
265
|
+
lower,
|
|
266
|
+
upper,
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Builds an `IS NULL` predicate.
|
|
272
|
+
* @param column Column to compare.
|
|
273
|
+
* @returns An `isNull` predicate.
|
|
274
|
+
*/
|
|
275
|
+
export function isNull<column extends string | ColumnReferenceLike>(
|
|
276
|
+
column: column,
|
|
277
|
+
): Predicate<PredicateColumn<column>> {
|
|
278
|
+
return { type: 'null', operator: 'isNull', column: resolvePredicateColumn(column) }
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Builds an `IS NOT NULL` predicate.
|
|
283
|
+
* @param column Column to compare.
|
|
284
|
+
* @returns A `notNull` predicate.
|
|
285
|
+
*/
|
|
286
|
+
export function notNull<column extends string | ColumnReferenceLike>(
|
|
287
|
+
column: column,
|
|
288
|
+
): Predicate<PredicateColumn<column>> {
|
|
289
|
+
return { type: 'null', operator: 'notNull', column: resolvePredicateColumn(column) }
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Combines predicates with logical `AND`.
|
|
294
|
+
* @param predicates Child predicates.
|
|
295
|
+
* @returns A logical `and` predicate.
|
|
296
|
+
*/
|
|
297
|
+
export function and<column extends string>(...predicates: Predicate<column>[]): Predicate<column> {
|
|
298
|
+
let filtered = predicates.filter(Boolean)
|
|
299
|
+
return { type: 'logical', operator: 'and', predicates: filtered }
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Combines predicates with logical `OR`.
|
|
304
|
+
* @param predicates Child predicates.
|
|
305
|
+
* @returns A logical `or` predicate.
|
|
306
|
+
*/
|
|
307
|
+
export function or<column extends string>(...predicates: Predicate<column>[]): Predicate<column> {
|
|
308
|
+
let filtered = predicates.filter(Boolean)
|
|
309
|
+
return { type: 'logical', operator: 'or', predicates: filtered }
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Returns `true` when a value is a normalized predicate object.
|
|
314
|
+
* @param value Value to inspect.
|
|
315
|
+
* @returns Whether the value is a predicate.
|
|
316
|
+
*/
|
|
317
|
+
export function isPredicate<column extends string = string>(
|
|
318
|
+
value: unknown,
|
|
319
|
+
): value is Predicate<column> {
|
|
320
|
+
if (typeof value !== 'object' || value === null) {
|
|
321
|
+
return false
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (!('type' in value)) {
|
|
325
|
+
return false
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
let input = value as { type?: unknown }
|
|
329
|
+
return (
|
|
330
|
+
input.type === 'comparison' ||
|
|
331
|
+
input.type === 'between' ||
|
|
332
|
+
input.type === 'null' ||
|
|
333
|
+
input.type === 'logical'
|
|
334
|
+
)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Normalizes object shorthand into a predicate tree.
|
|
339
|
+
* @param input Predicate object or shorthand where map.
|
|
340
|
+
* @returns A normalized predicate.
|
|
341
|
+
*/
|
|
342
|
+
export function normalizeWhereInput<column extends string>(
|
|
343
|
+
input: WhereInput<column>,
|
|
344
|
+
): Predicate<column> {
|
|
345
|
+
if (isPredicate(input)) {
|
|
346
|
+
return input
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
let keys = Object.keys(input) as column[]
|
|
350
|
+
let predicates = keys.map((column) => eq(column, input[column]) as Predicate<column>)
|
|
351
|
+
|
|
352
|
+
return and(...predicates)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Collects referenced columns from a predicate tree.
|
|
357
|
+
* @param predicate Predicate to inspect.
|
|
358
|
+
* @returns Referenced column names.
|
|
359
|
+
*/
|
|
360
|
+
export function getPredicateColumns(predicate: Predicate): string[] {
|
|
361
|
+
if (predicate.type === 'comparison') {
|
|
362
|
+
if (predicate.valueType === 'column') {
|
|
363
|
+
return [predicate.column, predicate.value]
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return [predicate.column]
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (predicate.type === 'between') {
|
|
370
|
+
return [predicate.column]
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (predicate.type === 'null') {
|
|
374
|
+
return [predicate.column]
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
let columns: string[] = []
|
|
378
|
+
|
|
379
|
+
for (let child of predicate.predicates) {
|
|
380
|
+
columns.push(...getPredicateColumns(child))
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return columns
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function isQualifiedColumnReference(value: unknown): value is QualifiedColumnReference {
|
|
387
|
+
if (typeof value !== 'string') {
|
|
388
|
+
return false
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return /^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)+$/.test(value)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function resolvePredicateColumn<input extends string | ColumnReferenceLike>(
|
|
395
|
+
column: input,
|
|
396
|
+
): PredicateColumn<input> {
|
|
397
|
+
return normalizeColumnInput(column) as PredicateColumn<input>
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function resolveComparisonValue(value: unknown): unknown {
|
|
401
|
+
if (isColumnReference(value)) {
|
|
402
|
+
return normalizeColumnInput(value)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return value
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function createComparisonPredicate(
|
|
409
|
+
operator: Exclude<ComparisonOperator, 'in' | 'notIn'>,
|
|
410
|
+
column: string | ColumnReferenceLike,
|
|
411
|
+
value: unknown,
|
|
412
|
+
): Predicate<string> {
|
|
413
|
+
let normalizedColumn = resolvePredicateColumn(column)
|
|
414
|
+
let normalizedValue = resolveComparisonValue(value)
|
|
415
|
+
|
|
416
|
+
if (isQualifiedColumnReference(normalizedColumn) && isQualifiedColumnReference(normalizedValue)) {
|
|
417
|
+
return {
|
|
418
|
+
type: 'comparison',
|
|
419
|
+
operator,
|
|
420
|
+
column: normalizedColumn,
|
|
421
|
+
value: normalizedValue,
|
|
422
|
+
valueType: 'column',
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return {
|
|
427
|
+
type: 'comparison',
|
|
428
|
+
operator,
|
|
429
|
+
column: normalizedColumn,
|
|
430
|
+
value: normalizedValue,
|
|
431
|
+
valueType: 'value',
|
|
432
|
+
}
|
|
433
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Symbol key used to store non-enumerable table metadata.
|
|
3
|
+
*/
|
|
4
|
+
export const tableMetadataKey = Symbol('data-table.tableMetadata')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Symbol key used to store non-enumerable column metadata.
|
|
8
|
+
*/
|
|
9
|
+
export const columnMetadataKey = Symbol('data-table.columnMetadata')
|
|
10
|
+
|
|
11
|
+
type UnknownTableMetadata<
|
|
12
|
+
name extends string = string,
|
|
13
|
+
columns extends Record<string, unknown> = Record<string, unknown>,
|
|
14
|
+
primaryKey extends readonly string[] = readonly string[],
|
|
15
|
+
timestamps = unknown,
|
|
16
|
+
> = {
|
|
17
|
+
name: name
|
|
18
|
+
columns: columns
|
|
19
|
+
primaryKey: primaryKey
|
|
20
|
+
timestamps: timestamps
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type TableMetadataLike<
|
|
24
|
+
name extends string = string,
|
|
25
|
+
columns extends Record<string, unknown> = Record<string, unknown>,
|
|
26
|
+
primaryKey extends readonly string[] = readonly string[],
|
|
27
|
+
timestamps = unknown,
|
|
28
|
+
> = {
|
|
29
|
+
[tableMetadataKey]: UnknownTableMetadata<name, columns, primaryKey, timestamps>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type ColumnReferenceLike<qualifiedName extends string = string> = {
|
|
33
|
+
kind: 'column'
|
|
34
|
+
[columnMetadataKey]: {
|
|
35
|
+
tableName: string
|
|
36
|
+
columnName: string
|
|
37
|
+
qualifiedName: qualifiedName
|
|
38
|
+
schema: unknown
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type ColumnInput<qualifiedName extends string = string> =
|
|
43
|
+
| qualifiedName
|
|
44
|
+
| ColumnReferenceLike<qualifiedName>
|
|
45
|
+
|
|
46
|
+
export type NormalizeColumnInput<input> =
|
|
47
|
+
input extends ColumnReferenceLike<infer qualifiedName> ? qualifiedName : input
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Returns `true` when a value is a `data-table` column reference.
|
|
51
|
+
* @param value Value to inspect.
|
|
52
|
+
* @returns Whether the value is a column reference object.
|
|
53
|
+
*/
|
|
54
|
+
export function isColumnReference(value: unknown): value is ColumnReferenceLike {
|
|
55
|
+
if (typeof value !== 'object' || value === null) {
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!('kind' in value) || (value as { kind?: unknown }).kind !== 'column') {
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return columnMetadataKey in (value as object)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Normalizes string/column-reference inputs to a qualified column name.
|
|
68
|
+
* @param input Column name or column reference.
|
|
69
|
+
* @returns The normalized qualified column name.
|
|
70
|
+
*/
|
|
71
|
+
export function normalizeColumnInput<input extends string | ColumnReferenceLike>(
|
|
72
|
+
input: input,
|
|
73
|
+
): NormalizeColumnInput<input> {
|
|
74
|
+
if (typeof input === 'string') {
|
|
75
|
+
return input as NormalizeColumnInput<input>
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return input[columnMetadataKey].qualifiedName as NormalizeColumnInput<input>
|
|
79
|
+
}
|