effect-qb 0.15.0 → 0.17.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 (88) hide show
  1. package/dist/mysql.js +1957 -595
  2. package/dist/postgres/metadata.js +2507 -182
  3. package/dist/postgres.js +9587 -8201
  4. package/dist/sqlite.js +8360 -0
  5. package/package.json +7 -2
  6. package/src/internal/column-state.ts +7 -0
  7. package/src/internal/column.ts +22 -0
  8. package/src/internal/derived-table.ts +29 -3
  9. package/src/internal/dialect.ts +14 -1
  10. package/src/internal/dsl-mutation-runtime.ts +173 -4
  11. package/src/internal/dsl-plan-runtime.ts +165 -20
  12. package/src/internal/dsl-query-runtime.ts +60 -6
  13. package/src/internal/dsl-transaction-ddl-runtime.ts +72 -2
  14. package/src/internal/executor.ts +62 -13
  15. package/src/internal/expression-ast.ts +3 -2
  16. package/src/internal/grouping-key.ts +141 -1
  17. package/src/internal/implication-runtime.ts +2 -1
  18. package/src/internal/json/types.ts +155 -40
  19. package/src/internal/predicate/analysis.ts +103 -1
  20. package/src/internal/predicate/atom.ts +7 -0
  21. package/src/internal/predicate/context.ts +170 -17
  22. package/src/internal/predicate/key.ts +64 -2
  23. package/src/internal/predicate/normalize.ts +115 -34
  24. package/src/internal/predicate/runtime.ts +144 -13
  25. package/src/internal/query.ts +563 -103
  26. package/src/internal/renderer.ts +39 -2
  27. package/src/internal/runtime/driver-value-mapping.ts +244 -0
  28. package/src/internal/runtime/normalize.ts +62 -38
  29. package/src/internal/runtime/schema.ts +5 -3
  30. package/src/internal/runtime/value.ts +153 -30
  31. package/src/internal/scalar.ts +11 -0
  32. package/src/internal/table-options.ts +108 -1
  33. package/src/internal/table.ts +87 -29
  34. package/src/mysql/column.ts +19 -2
  35. package/src/mysql/datatypes/index.ts +21 -0
  36. package/src/mysql/errors/catalog.ts +5 -5
  37. package/src/mysql/errors/normalize.ts +2 -2
  38. package/src/mysql/executor.ts +20 -5
  39. package/src/mysql/internal/dialect.ts +12 -6
  40. package/src/mysql/internal/dsl.ts +995 -263
  41. package/src/mysql/internal/renderer.ts +13 -3
  42. package/src/mysql/internal/sql-expression-renderer.ts +530 -128
  43. package/src/mysql/query.ts +9 -2
  44. package/src/mysql/renderer.ts +7 -2
  45. package/src/mysql/table.ts +38 -12
  46. package/src/postgres/cast.ts +22 -7
  47. package/src/postgres/column.ts +5 -2
  48. package/src/postgres/errors/normalize.ts +2 -2
  49. package/src/postgres/executor.ts +68 -10
  50. package/src/postgres/function/core.ts +19 -1
  51. package/src/postgres/internal/dialect.ts +12 -6
  52. package/src/postgres/internal/dsl.ts +958 -288
  53. package/src/postgres/internal/renderer.ts +13 -3
  54. package/src/postgres/internal/schema-ddl.ts +2 -1
  55. package/src/postgres/internal/schema-model.ts +6 -3
  56. package/src/postgres/internal/sql-expression-renderer.ts +477 -96
  57. package/src/postgres/json.ts +57 -17
  58. package/src/postgres/query.ts +9 -2
  59. package/src/postgres/renderer.ts +7 -2
  60. package/src/postgres/schema-management.ts +91 -4
  61. package/src/postgres/schema.ts +1 -1
  62. package/src/postgres/table.ts +189 -53
  63. package/src/postgres/type.ts +4 -0
  64. package/src/sqlite/column.ts +128 -0
  65. package/src/sqlite/datatypes/index.ts +79 -0
  66. package/src/sqlite/datatypes/spec.ts +98 -0
  67. package/src/sqlite/errors/catalog.ts +103 -0
  68. package/src/sqlite/errors/fields.ts +19 -0
  69. package/src/sqlite/errors/index.ts +19 -0
  70. package/src/sqlite/errors/normalize.ts +229 -0
  71. package/src/sqlite/errors/requirements.ts +71 -0
  72. package/src/sqlite/errors/types.ts +29 -0
  73. package/src/sqlite/executor.ts +227 -0
  74. package/src/sqlite/function/aggregate.ts +2 -0
  75. package/src/sqlite/function/core.ts +2 -0
  76. package/src/sqlite/function/index.ts +19 -0
  77. package/src/sqlite/function/string.ts +2 -0
  78. package/src/sqlite/function/temporal.ts +100 -0
  79. package/src/sqlite/function/window.ts +2 -0
  80. package/src/sqlite/internal/dialect.ts +37 -0
  81. package/src/sqlite/internal/dsl.ts +6926 -0
  82. package/src/sqlite/internal/renderer.ts +47 -0
  83. package/src/sqlite/internal/sql-expression-renderer.ts +1821 -0
  84. package/src/sqlite/json.ts +2 -0
  85. package/src/sqlite/query.ts +196 -0
  86. package/src/sqlite/renderer.ts +24 -0
  87. package/src/sqlite/table.ts +183 -0
  88. package/src/sqlite.ts +22 -0
@@ -23,50 +23,173 @@ const brandString = <BrandName extends string>(
23
23
  Schema.brand(brand)
24
24
  ) as unknown as Schema.Schema<string & Brand.Brand<BrandName>>
25
25
 
26
- export const LocalDateStringSchema = brandString(
27
- /^\d{4}-\d{2}-\d{2}$/,
28
- "LocalDateString"
29
- )
26
+ export const localDatePattern = /^(\d{4})-(\d{2})-(\d{2})$/
30
27
 
31
- export const LocalTimeStringSchema = brandString(
32
- /^\d{2}:\d{2}:\d{2}(?:\.\d+)?$/,
33
- "LocalTimeString"
34
- )
28
+ export const isValidLocalDateString = (value: string): boolean => {
29
+ const match = localDatePattern.exec(value)
30
+ if (match === null) {
31
+ return false
32
+ }
33
+ const year = Number(match[1])
34
+ const month = Number(match[2])
35
+ const day = Number(match[3])
36
+ const parsed = new Date(Date.UTC(year, month - 1, day))
37
+ parsed.setUTCFullYear(year)
38
+ return parsed.getUTCFullYear() === year &&
39
+ parsed.getUTCMonth() === month - 1 &&
40
+ parsed.getUTCDate() === day
41
+ }
35
42
 
36
- export const OffsetTimeStringSchema = brandString(
37
- /^\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/,
38
- "OffsetTimeString"
39
- )
43
+ export const localTimePattern = /^(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?$/
40
44
 
41
- export const LocalDateTimeStringSchema = brandString(
42
- /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?$/,
43
- "LocalDateTimeString"
44
- )
45
+ export const isValidLocalTimeString = (value: string): boolean => {
46
+ const match = localTimePattern.exec(value)
47
+ if (match === null) {
48
+ return false
49
+ }
50
+ const hour = Number(match[1])
51
+ const minute = Number(match[2])
52
+ const second = Number(match[3])
53
+ return hour >= 0 && hour <= 23 &&
54
+ minute >= 0 && minute <= 59 &&
55
+ second >= 0 && second <= 59
56
+ }
45
57
 
46
- export const InstantStringSchema = brandString(
47
- /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/,
48
- "InstantString"
49
- )
58
+ const offsetPattern = /^(?:Z|[+-](\d{2}):(\d{2}))$/
59
+
60
+ const isValidOffset = (value: string): boolean => {
61
+ const match = offsetPattern.exec(value)
62
+ if (match === null) {
63
+ return false
64
+ }
65
+ if (value === "Z") {
66
+ return true
67
+ }
68
+ const hour = Number(match[1])
69
+ const minute = Number(match[2])
70
+ return hour >= 0 && hour <= 23 &&
71
+ minute >= 0 && minute <= 59
72
+ }
73
+
74
+ export const offsetTimePattern = /^(\d{2}:\d{2}:\d{2}(?:\.\d+)?)(Z|[+-]\d{2}:\d{2})$/
75
+
76
+ export const isValidOffsetTimeString = (value: string): boolean => {
77
+ const match = offsetTimePattern.exec(value)
78
+ return match !== null &&
79
+ isValidLocalTimeString(match[1]!) &&
80
+ isValidOffset(match[2]!)
81
+ }
82
+
83
+ export const localDateTimePattern = /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2}(?:\.\d+)?)$/
84
+
85
+ export const isValidLocalDateTimeString = (value: string): boolean => {
86
+ const match = localDateTimePattern.exec(value)
87
+ return match !== null &&
88
+ isValidLocalDateString(match[1]!) &&
89
+ isValidLocalTimeString(match[2]!)
90
+ }
91
+
92
+ export const instantPattern = /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2}(?:\.\d+)?)(Z|[+-]\d{2}:\d{2})$/
93
+
94
+ export const isValidInstantString = (value: string): boolean => {
95
+ const match = instantPattern.exec(value)
96
+ return match !== null &&
97
+ isValidLocalDateString(match[1]!) &&
98
+ isValidLocalTimeString(match[2]!) &&
99
+ isValidOffset(match[3]!)
100
+ }
101
+
102
+ export const LocalDateStringSchema = Schema.String.pipe(
103
+ Schema.pattern(localDatePattern),
104
+ Schema.filter(isValidLocalDateString),
105
+ Schema.brand("LocalDateString")
106
+ ) as unknown as Schema.Schema<LocalDateString>
107
+
108
+ export const LocalTimeStringSchema = Schema.String.pipe(
109
+ Schema.pattern(localTimePattern),
110
+ Schema.filter(isValidLocalTimeString),
111
+ Schema.brand("LocalTimeString")
112
+ ) as unknown as Schema.Schema<LocalTimeString>
113
+
114
+ export const OffsetTimeStringSchema = Schema.String.pipe(
115
+ Schema.pattern(offsetTimePattern),
116
+ Schema.filter(isValidOffsetTimeString),
117
+ Schema.brand("OffsetTimeString")
118
+ ) as unknown as Schema.Schema<OffsetTimeString>
119
+
120
+ export const LocalDateTimeStringSchema = Schema.String.pipe(
121
+ Schema.pattern(localDateTimePattern),
122
+ Schema.filter(isValidLocalDateTimeString),
123
+ Schema.brand("LocalDateTimeString")
124
+ ) as unknown as Schema.Schema<LocalDateTimeString>
125
+
126
+ export const InstantStringSchema = Schema.String.pipe(
127
+ Schema.pattern(instantPattern),
128
+ Schema.filter(isValidInstantString),
129
+ Schema.brand("InstantString")
130
+ ) as unknown as Schema.Schema<InstantString>
50
131
 
51
132
  export const YearStringSchema = brandString(
52
133
  /^\d{4}$/,
53
134
  "YearString"
54
135
  )
55
136
 
56
- export const BigIntStringSchema = brandString(
57
- /^-?\d+$/,
58
- "BigIntString"
59
- )
137
+ export const canonicalizeBigIntString = (input: string): string => {
138
+ const trimmed = input.trim()
139
+ if (!/^-?\d+$/.test(trimmed)) {
140
+ throw new Error("Expected an integer-like bigint value")
141
+ }
142
+ return BigInt(trimmed).toString()
143
+ }
60
144
 
61
- export const DecimalStringSchema = brandString(
62
- /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/,
63
- "DecimalString"
64
- )
145
+ export const isCanonicalBigIntString = (value: string): boolean => {
146
+ try {
147
+ return canonicalizeBigIntString(value) === value
148
+ } catch {
149
+ return false
150
+ }
151
+ }
152
+
153
+ export const canonicalizeDecimalString = (input: string): string => {
154
+ const trimmed = input.trim()
155
+ const match = /^([+-]?)(\d+)(?:\.(\d+))?$/.exec(trimmed)
156
+ if (match === null) {
157
+ throw new Error("Expected a decimal string")
158
+ }
159
+ const sign = match[1] === "-" ? "-" : ""
160
+ const integer = match[2]!.replace(/^0+(?=\d)/, "") || "0"
161
+ const fraction = (match[3] ?? "").replace(/0+$/, "")
162
+ if (fraction.length === 0) {
163
+ if (integer === "0") {
164
+ return "0"
165
+ }
166
+ return `${sign}${integer}`
167
+ }
168
+ return `${sign}${integer}.${fraction}`
169
+ }
170
+
171
+ export const isCanonicalDecimalString = (value: string): boolean => {
172
+ try {
173
+ return canonicalizeDecimalString(value) === value
174
+ } catch {
175
+ return false
176
+ }
177
+ }
178
+
179
+ export const BigIntStringSchema = Schema.String.pipe(
180
+ Schema.filter(isCanonicalBigIntString),
181
+ Schema.brand("BigIntString")
182
+ ) as unknown as Schema.Schema<BigIntString>
183
+
184
+ export const DecimalStringSchema = Schema.String.pipe(
185
+ Schema.filter(isCanonicalDecimalString),
186
+ Schema.brand("DecimalString")
187
+ ) as unknown as Schema.Schema<DecimalString>
65
188
 
66
189
  export const JsonValueSchema: Schema.Schema<JsonValue> = Schema.suspend(() =>
67
190
  Schema.Union(
68
191
  Schema.String,
69
- Schema.Number,
192
+ Schema.Number.pipe(Schema.finite()),
70
193
  Schema.Boolean,
71
194
  Schema.Null,
72
195
  Schema.Array(JsonValueSchema),
@@ -79,7 +202,7 @@ export const JsonValueSchema: Schema.Schema<JsonValue> = Schema.suspend(() =>
79
202
 
80
203
  export const JsonPrimitiveSchema: Schema.Schema<JsonPrimitive> = Schema.Union(
81
204
  Schema.String,
82
- Schema.Number,
205
+ Schema.Number.pipe(Schema.finite()),
83
206
  Schema.Boolean,
84
207
  Schema.Null
85
208
  )
@@ -51,6 +51,7 @@ export declare namespace DbType {
51
51
  readonly compareGroup?: string
52
52
  readonly castTargets?: readonly string[]
53
53
  readonly traits?: DatatypeTraits
54
+ readonly driverValueMapping?: DriverValueMapping
54
55
  }
55
56
 
56
57
  /** JSON-like database type. */
@@ -135,6 +136,15 @@ export declare namespace DbType {
135
136
  | Set<string, string>
136
137
  }
137
138
 
139
+ export interface DriverValueMapping {
140
+ readonly fromDriver?: (value: unknown, dbType: DbType.Any) => unknown
141
+ readonly toDriver?: (value: unknown, dbType: DbType.Any) => unknown
142
+ readonly selectSql?: (sql: string, dbType: DbType.Any) => string
143
+ readonly jsonSelectSql?: (sql: string, dbType: DbType.Any) => string
144
+ }
145
+
146
+ export type DriverValueMappings = Readonly<Record<string, DriverValueMapping | undefined>>
147
+
138
148
  /** Canonical static metadata stored on an expression. */
139
149
  export interface State<
140
150
  Runtime,
@@ -147,6 +157,7 @@ export interface State<
147
157
  readonly runtime: Runtime
148
158
  readonly dbType: Db
149
159
  readonly runtimeSchema?: Schema.Schema.Any
160
+ readonly driverValueMapping?: DriverValueMapping
150
161
  readonly nullability: Nullable
151
162
  readonly dialect: Dialect
152
163
  readonly kind: Kind
@@ -15,6 +15,30 @@ export type DdlExpressionLike = AnyExpression | AnySchemaExpression
15
15
 
16
16
  export type ReferentialAction = "noAction" | "restrict" | "cascade" | "setNull" | "setDefault"
17
17
 
18
+ const referentialActionError = "Foreign key action must be noAction, restrict, cascade, setNull, or setDefault"
19
+
20
+ export const renderReferentialAction = (action: unknown): string => {
21
+ switch (action) {
22
+ case "noAction":
23
+ return "no action"
24
+ case "restrict":
25
+ return "restrict"
26
+ case "cascade":
27
+ return "cascade"
28
+ case "setNull":
29
+ return "set null"
30
+ case "setDefault":
31
+ return "set default"
32
+ }
33
+ throw new Error(referentialActionError)
34
+ }
35
+
36
+ const validateReferentialAction = (action: unknown): void => {
37
+ if (action !== undefined) {
38
+ renderReferentialAction(action)
39
+ }
40
+ }
41
+
18
42
  export type IndexKeySpec =
19
43
  | {
20
44
  readonly kind: "column"
@@ -107,6 +131,18 @@ type TupleFromColumns<Columns> = Columns extends readonly [infer Head extends st
107
131
  ? readonly [Columns]
108
132
  : never
109
133
 
134
+ export type NonEmptyColumnInput<Columns extends string | readonly string[]> =
135
+ TupleFromColumns<Columns> extends never ? never : Columns
136
+
137
+ export type MatchingColumnArityInput<
138
+ Left extends string | readonly string[],
139
+ Right extends string | readonly string[]
140
+ > = TupleFromColumns<Left>["length"] extends TupleFromColumns<Right>["length"]
141
+ ? TupleFromColumns<Right>["length"] extends TupleFromColumns<Left>["length"]
142
+ ? unknown
143
+ : never
144
+ : never
145
+
110
146
  type AssertKnownColumns<Fields extends TableFieldMap, Columns extends readonly string[]> = Exclude<
111
147
  Columns[number],
112
148
  ColumnNameUnion<Fields>
@@ -114,6 +150,47 @@ type AssertKnownColumns<Fields extends TableFieldMap, Columns extends readonly s
114
150
  ? Columns
115
151
  : never
116
152
 
153
+ type IndexKeyColumnNames<Keys> = Keys extends readonly (infer Key)[]
154
+ ? Key extends { readonly kind: "column"; readonly column: infer Column extends string }
155
+ ? Column
156
+ : never
157
+ : never
158
+
159
+ type IndexOptionColumnNames<Spec> =
160
+ | (Spec extends { readonly columns: infer Columns extends readonly string[] } ? Columns[number] : never)
161
+ | (Spec extends { readonly include: infer Include extends readonly string[] } ? Include[number] : never)
162
+ | (Spec extends { readonly keys: infer Keys } ? IndexKeyColumnNames<Keys> : never)
163
+
164
+ type ForeignKeyReferencedColumnNames<Spec> = Spec extends { readonly references: () => infer Reference }
165
+ ? Reference extends { readonly columns: infer Columns extends readonly string[] }
166
+ ? Columns[number]
167
+ : never
168
+ : never
169
+
170
+ type ForeignKeyKnownReferencedColumnNames<Spec> = Spec extends { readonly references: () => infer Reference }
171
+ ? Reference extends { readonly knownColumns: infer KnownColumns extends readonly string[] }
172
+ ? KnownColumns[number]
173
+ : string
174
+ : string
175
+
176
+ type AssertKnownColumnNames<Fields extends TableFieldMap, Columns extends string> = [Columns] extends [never]
177
+ ? true
178
+ : string extends Columns
179
+ ? true
180
+ : Exclude<Columns, ColumnNameUnion<Fields>> extends never
181
+ ? true
182
+ : false
183
+
184
+ type AssertKnownReferenceColumnNames<KnownColumns extends string, Columns extends string> = [Columns] extends [never]
185
+ ? true
186
+ : string extends Columns
187
+ ? true
188
+ : string extends KnownColumns
189
+ ? true
190
+ : Exclude<Columns, KnownColumns> extends never
191
+ ? true
192
+ : false
193
+
117
194
  type AssertPrimaryKeyColumns<
118
195
  Fields extends TableFieldMap,
119
196
  Columns extends readonly string[]
@@ -159,6 +236,8 @@ export const collectInlineOptions = <Fields extends TableFieldMap>(
159
236
  })
160
237
  }
161
238
  if (column.metadata.references) {
239
+ validateReferentialAction(column.metadata.references.onUpdate)
240
+ validateReferentialAction(column.metadata.references.onDelete)
162
241
  const local = [columnName] as ColumnList
163
242
  options.push({
164
243
  kind: "foreignKey",
@@ -254,6 +333,8 @@ export const validateOptions = <Fields extends TableFieldMap>(
254
333
  }
255
334
  }
256
335
  if (option.kind === "foreignKey") {
336
+ validateReferentialAction(option.onUpdate)
337
+ validateReferentialAction(option.onDelete)
257
338
  const reference = option.references()
258
339
  if (reference.columns.length !== columns.length) {
259
340
  throw new Error(`Foreign key on table '${tableName}' must reference the same number of columns`)
@@ -278,7 +359,7 @@ export const validateOptions = <Fields extends TableFieldMap>(
278
359
  throw new Error(`Unknown index key column '${key.column}' on table '${tableName}'`)
279
360
  }
280
361
  }
281
- if (option.columns === undefined && (option.keys === undefined || option.keys.length === 0)) {
362
+ if (columns.length === 0 && (option.keys === undefined || option.keys.length === 0)) {
282
363
  throw new Error(`Index on table '${tableName}' requires at least one column or key`)
283
364
  }
284
365
  }
@@ -308,5 +389,31 @@ export type ValidatePrimaryKeyColumns<
308
389
  Columns extends readonly string[]
309
390
  > = AssertPrimaryKeyColumns<Fields, AssertKnownColumns<Fields, Columns>>
310
391
 
392
+ /** Compile-time validation that index columns, included columns, and column keys exist on the table. */
393
+ export type ValidateIndexOptionColumns<
394
+ Fields extends TableFieldMap,
395
+ Spec
396
+ > = AssertKnownColumnNames<Fields, IndexOptionColumnNames<Spec>> extends true ? Spec : never
397
+
398
+ /** Compile-time validation that foreign keys reference known local and target columns. */
399
+ export type ValidateForeignKeyOptionColumns<
400
+ Fields extends TableFieldMap,
401
+ Spec
402
+ > = Spec extends { readonly columns: infer Columns extends readonly string[] }
403
+ ? AssertKnownColumns<Fields, Columns> extends never
404
+ ? never
405
+ : AssertKnownReferenceColumnNames<
406
+ ForeignKeyKnownReferencedColumnNames<Spec>,
407
+ ForeignKeyReferencedColumnNames<Spec>
408
+ > extends true
409
+ ? Spec
410
+ : never
411
+ : AssertKnownReferenceColumnNames<
412
+ ForeignKeyKnownReferencedColumnNames<Spec>,
413
+ ForeignKeyReferencedColumnNames<Spec>
414
+ > extends true
415
+ ? Spec
416
+ : never
417
+
311
418
  /** Normalizes a public column input into the internal tuple form. */
312
419
  export type NormalizeColumns<Columns extends string | readonly string[]> = TupleFromColumns<Columns>
@@ -12,9 +12,13 @@ import {
12
12
  resolvePrimaryKeyColumns,
13
13
  type DdlExpressionLike,
14
14
  type IndexKeySpec,
15
+ type MatchingColumnArityInput,
16
+ type NonEmptyColumnInput,
15
17
  type NormalizeColumns,
16
18
  type ReferentialAction,
17
19
  type TableOptionSpec,
20
+ type ValidateForeignKeyOptionColumns,
21
+ type ValidateIndexOptionColumns,
18
22
  type ValidateKnownColumns,
19
23
  type ValidatePrimaryKeyColumns,
20
24
  validateOptions
@@ -44,18 +48,32 @@ type InlinePrimaryKeyKeys<Fields extends TableFieldMap> = Extract<{
44
48
  type TableDialect<Fields extends TableFieldMap> = Fields[keyof Fields][typeof import("./column-state.js").ColumnTypeId]["dbType"]["dialect"]
45
49
  type TableKind = "schema" | "alias"
46
50
  type DefaultSchemaName = "public"
47
- type ClassOptionSpec = Exclude<TableOptionSpec, { readonly kind: "primaryKey" }>
51
+ type FieldColumnName<Fields extends TableFieldMap> = Extract<keyof Fields, string>
52
+ type FieldColumnList<Fields extends TableFieldMap> = readonly [FieldColumnName<Fields>, ...FieldColumnName<Fields>[]]
53
+ type FieldIndexKeySpec<Fields extends TableFieldMap> =
54
+ | (Extract<IndexKeySpec, { readonly kind: "column" }> & { readonly column: FieldColumnName<Fields> })
55
+ | Extract<IndexKeySpec, { readonly kind: "expression" }>
56
+ type ClassOptionSpec<Fields extends TableFieldMap = TableFieldMap> =
57
+ | (Omit<Extract<TableOptionSpec, { readonly kind: "index" }>, "columns" | "include" | "keys"> & {
58
+ readonly columns?: FieldColumnList<Fields>
59
+ readonly include?: readonly FieldColumnName<Fields>[]
60
+ readonly keys?: readonly [FieldIndexKeySpec<Fields>, ...FieldIndexKeySpec<Fields>[]]
61
+ })
62
+ | (Omit<Extract<TableOptionSpec, { readonly kind: "unique" }>, "columns"> & {
63
+ readonly columns: FieldColumnList<Fields>
64
+ })
65
+ | (Omit<Extract<TableOptionSpec, { readonly kind: "foreignKey" }>, "columns"> & {
66
+ readonly columns: FieldColumnList<Fields>
67
+ })
68
+ | Extract<TableOptionSpec, { readonly kind: "check" }>
48
69
  interface TableOptionBuilderLike<
49
70
  Spec extends TableOptionSpec = TableOptionSpec
50
71
  > {
51
- (
52
- table: TableDefinition<any, any, any, "schema", any>
53
- ): TableDefinition<any, any, any, "schema", any>
54
72
  readonly option: Spec
55
73
  }
56
74
 
57
- type ClassTableOption = TableOptionBuilderLike<ClassOptionSpec>
58
- type ClassDeclaredTableOptions = readonly ClassTableOption[]
75
+ type ClassTableOption<Fields extends TableFieldMap> = TableOptionBuilderLike<ClassOptionSpec<Fields>>
76
+ type ClassDeclaredTableOptions<Fields extends TableFieldMap> = readonly ClassTableOption<Fields>[]
59
77
 
60
78
  type BuildPrimaryKey<
61
79
  Table extends TableDefinition<any, any, any, "schema", any>,
@@ -69,9 +87,13 @@ type OptionInputTable<
69
87
  Spec extends TableOptionSpec
70
88
  > = Spec extends { readonly kind: "primaryKey"; readonly columns: infer Columns extends readonly string[] }
71
89
  ? ValidatePrimaryKeyColumns<Table[typeof TypeId]["fields"], Columns> extends never ? never : Table
72
- : Spec extends { readonly columns: infer Columns extends readonly string[] }
73
- ? ValidateKnownColumns<Table[typeof TypeId]["fields"], Columns> extends never ? never : Table
74
- : Table
90
+ : Spec extends { readonly kind: "index" }
91
+ ? ValidateIndexOptionColumns<Table[typeof TypeId]["fields"], Spec> extends never ? never : Table
92
+ : Spec extends { readonly kind: "foreignKey" }
93
+ ? ValidateForeignKeyOptionColumns<Table[typeof TypeId]["fields"], Spec> extends never ? never : Table
94
+ : Spec extends { readonly columns: infer Columns extends readonly string[] }
95
+ ? ValidateKnownColumns<Table[typeof TypeId]["fields"], Columns> extends never ? never : Table
96
+ : Table
75
97
 
76
98
  type ApplyOption<
77
99
  Table extends TableDefinition<any, any, any, "schema", any>,
@@ -90,6 +112,26 @@ type ApplyOption<
90
112
  "schema"
91
113
  >
92
114
 
115
+ export type ValidateDeclaredOptions<
116
+ Table extends TableDefinition<any, any, any, "schema", any>,
117
+ Options extends DeclaredTableOptions
118
+ > = {
119
+ readonly [K in keyof Options]: Options[K] extends TableOptionBuilderLike<infer Spec>
120
+ ? OptionInputTable<Table, Spec> extends never ? never : Options[K]
121
+ : never
122
+ }
123
+
124
+ export type ApplyDeclaredOptions<
125
+ Table extends TableDefinition<any, any, any, "schema", any>,
126
+ Options extends DeclaredTableOptions
127
+ > = Options extends readonly [infer Head, ...infer Tail]
128
+ ? Head extends TableOptionBuilderLike<infer Spec>
129
+ ? Tail extends DeclaredTableOptions
130
+ ? ApplyDeclaredOptions<ApplyOption<Table, Spec>, Tail>
131
+ : ApplyOption<Table, Spec>
132
+ : Table
133
+ : Table
134
+
93
135
  export type MissingSelfGeneric = "Missing `Self` generic - use `class Self extends Table.Class<Self>(...) {}`"
94
136
 
95
137
  /** Bound columns keyed by field name for a particular table. */
@@ -132,16 +174,17 @@ export interface TableSchemaNamespace<SchemaName extends string> {
132
174
  readonly table: <
133
175
  Name extends string,
134
176
  Fields extends TableFieldMap,
177
+ const Options extends DeclaredTableOptions,
135
178
  PrimaryKeyColumns extends keyof Fields & string = InlinePrimaryKeyKeys<Fields>
136
179
  >(
137
180
  name: Name,
138
181
  fields: Fields,
139
- ...options: DeclaredTableOptions
140
- ) => TableDefinition<Name, Fields, PrimaryKeyColumns, "schema", SchemaName>
182
+ ...options: Options & ValidateDeclaredOptions<TableDefinition<Name, Fields, PrimaryKeyColumns, "schema", SchemaName>, Options>
183
+ ) => ApplyDeclaredOptions<TableDefinition<Name, Fields, PrimaryKeyColumns, "schema", SchemaName>, Options>
141
184
  }
142
185
 
143
186
  export type DeclaredTableOptions = readonly TableOptionBuilderLike[]
144
- export type { DdlExpressionLike, IndexKeySpec, NormalizeColumns, ReferentialAction } from "./table-options.js"
187
+ export type { DdlExpressionLike, IndexKeySpec, MatchingColumnArityInput, NonEmptyColumnInput, NormalizeColumns, ReferentialAction } from "./table-options.js"
145
188
 
146
189
  export type TableDefinition<
147
190
  Name extends string,
@@ -192,7 +235,7 @@ export type TableClassStatic<
192
235
  >
193
236
  readonly [OptionsSymbol]: readonly TableOptionSpec[]
194
237
  readonly [DeclaredOptionsSymbol]?: readonly TableOptionSpec[]
195
- readonly [options]?: ClassDeclaredTableOptions
238
+ readonly [options]?: ClassDeclaredTableOptions<Fields>
196
239
  readonly tableName: Name
197
240
  } & BoundColumns<Name, Fields> & Plan.RowSet<
198
241
  BoundColumns<Name, Fields>,
@@ -204,6 +247,14 @@ export type TableClassStatic<
204
247
  /** Minimal structural table-like contract used across helper APIs. */
205
248
  export type AnyTable = TableDefinition<any, any, any, any, any> | TableClassStatic<any, any, any, any>
206
249
 
250
+ type FieldsOfAnyTable<Table extends AnyTable> = Table extends TableDefinition<any, infer Fields extends TableFieldMap, any, any, any>
251
+ ? Fields
252
+ : Table extends TableClassStatic<any, infer Fields extends TableFieldMap, any, any>
253
+ ? Fields
254
+ : never
255
+
256
+ type ColumnNamesOfAnyTable<Table extends AnyTable> = Extract<keyof FieldsOfAnyTable<Table>, string>
257
+
207
258
  /** Public table-option builder type used by `Table.index`, `Table.primaryKey`, and friends. */
208
259
  export type TableOption<
209
260
  Spec extends TableOptionSpec = TableOptionSpec
@@ -341,7 +392,10 @@ const applyDeclaredOptions = <
341
392
  return table
342
393
  }
343
394
  return declaredOptions.reduce<TableDefinition<any, any, any, "schema", any>>(
344
- (current, option) => option(current),
395
+ (current, option) =>
396
+ (option as unknown as (
397
+ table: TableDefinition<any, any, any, "schema", any>
398
+ ) => TableDefinition<any, any, any, "schema", any>)(current),
345
399
  table
346
400
  ) as unknown as Table
347
401
  }
@@ -491,17 +545,17 @@ export function make<
491
545
  */
492
546
  export const schema = <SchemaName extends string>(
493
547
  schemaName: SchemaName
494
- ): TableSchemaNamespace<SchemaName> => ({
495
- schemaName,
496
- table: <
548
+ ): TableSchemaNamespace<SchemaName> => {
549
+ const table = <
497
550
  Name extends string,
498
551
  Fields extends TableFieldMap,
552
+ const Options extends DeclaredTableOptions,
499
553
  PrimaryKeyColumns extends keyof Fields & string = InlinePrimaryKeyKeys<Fields>
500
554
  >(
501
555
  name: Name,
502
556
  fields: Fields,
503
- ...options: DeclaredTableOptions
504
- ): TableDefinition<Name, Fields, PrimaryKeyColumns, "schema", SchemaName> =>
557
+ ...options: Options & ValidateDeclaredOptions<TableDefinition<Name, Fields, PrimaryKeyColumns, "schema", SchemaName>, Options>
558
+ ): ApplyDeclaredOptions<TableDefinition<Name, Fields, PrimaryKeyColumns, "schema", SchemaName>, Options> =>
505
559
  applyDeclaredOptions(
506
560
  makeTable(
507
561
  name,
@@ -512,9 +566,13 @@ export const schema = <SchemaName extends string>(
512
566
  schemaName,
513
567
  "explicit"
514
568
  ) as TableDefinition<Name, Fields, PrimaryKeyColumns, "schema", SchemaName>,
515
- options
516
- )
517
- })
569
+ options as unknown as Options
570
+ ) as ApplyDeclaredOptions<TableDefinition<Name, Fields, PrimaryKeyColumns, "schema", SchemaName>, Options>
571
+ return {
572
+ schemaName,
573
+ table
574
+ } as unknown as TableSchemaNamespace<SchemaName>
575
+ }
518
576
 
519
577
  /**
520
578
  * Creates an aliased source from an existing table definition.
@@ -664,7 +722,7 @@ export function Class<
664
722
  export const primaryKey = <
665
723
  const Columns extends string | readonly string[]
666
724
  >(
667
- columns: Columns
725
+ columns: Columns & NonEmptyColumnInput<Columns>
668
726
  ): TableOption<{
669
727
  readonly kind: "primaryKey"
670
728
  readonly columns: NormalizeColumns<Columns>
@@ -677,7 +735,7 @@ export const primaryKey = <
677
735
  export const unique = <
678
736
  const Columns extends string | readonly string[]
679
737
  >(
680
- columns: Columns
738
+ columns: Columns & NonEmptyColumnInput<Columns>
681
739
  ): TableOption<{
682
740
  readonly kind: "unique"
683
741
  readonly columns: NormalizeColumns<Columns>
@@ -690,7 +748,7 @@ export const unique = <
690
748
  export const index = <
691
749
  const Columns extends string | readonly string[]
692
750
  >(
693
- columns: Columns
751
+ columns: Columns & NonEmptyColumnInput<Columns>
694
752
  ): TableOption<{
695
753
  readonly kind: "index"
696
754
  readonly columns: NormalizeColumns<Columns>
@@ -705,9 +763,9 @@ export const foreignKey = <
705
763
  TargetTable extends AnyTable,
706
764
  const TargetColumns extends string | readonly string[]
707
765
  >(
708
- columns: LocalColumns,
766
+ columns: LocalColumns & NonEmptyColumnInput<LocalColumns>,
709
767
  target: () => TargetTable,
710
- referencedColumns: TargetColumns
768
+ referencedColumns: TargetColumns & NonEmptyColumnInput<TargetColumns> & MatchingColumnArityInput<LocalColumns, TargetColumns>
711
769
  ): TableOption<{
712
770
  readonly kind: "foreignKey"
713
771
  readonly columns: NormalizeColumns<LocalColumns>
@@ -715,7 +773,7 @@ export const foreignKey = <
715
773
  readonly tableName: string
716
774
  readonly schemaName?: string
717
775
  readonly columns: NormalizeColumns<TargetColumns>
718
- readonly knownColumns: readonly string[]
776
+ readonly knownColumns: readonly ColumnNamesOfAnyTable<TargetTable>[]
719
777
  }
720
778
  }> => makeOption({
721
779
  kind: "foreignKey",
@@ -724,7 +782,7 @@ export const foreignKey = <
724
782
  tableName: target()[TypeId].baseName,
725
783
  schemaName: target()[TypeId].schemaName,
726
784
  columns: normalizeColumnList(referencedColumns) as NormalizeColumns<TargetColumns>,
727
- knownColumns: Object.keys(target()[TypeId].fields)
785
+ knownColumns: Object.keys(target()[TypeId].fields) as unknown as readonly ColumnNamesOfAnyTable<TargetTable>[]
728
786
  })
729
787
  })
730
788
 
@@ -1,7 +1,7 @@
1
1
  import * as Schema from "effect/Schema"
2
2
 
3
3
  import * as BaseColumn from "../internal/column.js"
4
- import { makeColumnDefinition, type ColumnDefinition } from "../internal/column-state.js"
4
+ import { makeColumnDefinition, type AnyColumnDefinition, type ColumnDefinition } from "../internal/column-state.js"
5
5
  import type * as Expression from "../internal/scalar.js"
6
6
  import {
7
7
  DecimalStringSchema,
@@ -97,9 +97,26 @@ export const json = <SchemaType extends Schema.Schema.Any>(schema: SchemaType) =
97
97
  export const nullable = BaseColumn.nullable
98
98
  export const brand = BaseColumn.brand
99
99
  export const primaryKey = BaseColumn.primaryKey
100
- export const unique = BaseColumn.unique
100
+ type UniqueColumn<Column extends AnyColumnDefinition> = ReturnType<typeof BaseColumn.unique<Column>>
101
+
102
+ type MysqlUniqueOptions = {
103
+ readonly name?: string
104
+ readonly nullsNotDistinct?: never
105
+ readonly deferrable?: never
106
+ readonly initiallyDeferred?: never
107
+ }
108
+
109
+ type UniqueModifier = {
110
+ <Column extends AnyColumnDefinition>(column: Column): UniqueColumn<Column>
111
+ readonly options: <const Options extends MysqlUniqueOptions>(
112
+ options: Options
113
+ ) => <Column extends AnyColumnDefinition>(column: Column) => UniqueColumn<Column>
114
+ }
115
+
116
+ export const unique = BaseColumn.unique as UniqueModifier
101
117
  const default_ = BaseColumn.default_
102
118
  export const generated = BaseColumn.generated
119
+ export const driverValueMapping = BaseColumn.driverValueMapping
103
120
  export const references = BaseColumn.references
104
121
  export const schema = BaseColumn.schema
105
122
  export { default_ as default }