@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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +298 -2
  3. package/dist/index.d.ts +11 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +5 -0
  6. package/dist/lib/adapter.d.ts +180 -0
  7. package/dist/lib/adapter.d.ts.map +1 -0
  8. package/dist/lib/adapter.js +1 -0
  9. package/dist/lib/database.d.ts +361 -0
  10. package/dist/lib/database.d.ts.map +1 -0
  11. package/dist/lib/database.js +1368 -0
  12. package/dist/lib/errors.d.ts +50 -0
  13. package/dist/lib/errors.d.ts.map +1 -0
  14. package/dist/lib/errors.js +67 -0
  15. package/dist/lib/inflection.d.ts +3 -0
  16. package/dist/lib/inflection.d.ts.map +1 -0
  17. package/dist/lib/inflection.js +56 -0
  18. package/dist/lib/operators.d.ts +151 -0
  19. package/dist/lib/operators.d.ts.map +1 -0
  20. package/dist/lib/operators.js +218 -0
  21. package/dist/lib/references.d.ts +42 -0
  22. package/dist/lib/references.d.ts.map +1 -0
  23. package/dist/lib/references.js +33 -0
  24. package/dist/lib/sql.d.ts +28 -0
  25. package/dist/lib/sql.d.ts.map +1 -0
  26. package/dist/lib/sql.js +51 -0
  27. package/dist/lib/table.d.ts +254 -0
  28. package/dist/lib/table.d.ts.map +1 -0
  29. package/dist/lib/table.js +496 -0
  30. package/dist/lib/types.d.ts +4 -0
  31. package/dist/lib/types.d.ts.map +1 -0
  32. package/dist/lib/types.js +1 -0
  33. package/package.json +41 -7
  34. package/src/index.ts +115 -0
  35. package/src/lib/adapter.ts +209 -0
  36. package/src/lib/database.ts +2458 -0
  37. package/src/lib/errors.ts +109 -0
  38. package/src/lib/inflection.ts +69 -0
  39. package/src/lib/operators.ts +433 -0
  40. package/src/lib/references.ts +79 -0
  41. package/src/lib/sql.ts +67 -0
  42. package/src/lib/table.ts +981 -0
  43. 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
+ }