effect-qb 0.16.0 → 0.19.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 (128) hide show
  1. package/README.md +4 -0
  2. package/dist/index.js +8065 -0
  3. package/dist/mysql.js +4036 -2418
  4. package/dist/postgres/metadata.js +2536 -625
  5. package/dist/postgres.js +8248 -7857
  6. package/dist/sqlite.js +8854 -0
  7. package/dist/standard.js +8019 -0
  8. package/package.json +15 -3
  9. package/src/casing.ts +71 -0
  10. package/src/index.ts +2 -0
  11. package/src/internal/casing.ts +89 -0
  12. package/src/internal/column-state.ts +11 -6
  13. package/src/internal/column.ts +44 -7
  14. package/src/internal/datatypes/define.ts +2 -1
  15. package/src/internal/datatypes/enrich.ts +23 -0
  16. package/src/internal/datatypes/lookup.ts +14 -7
  17. package/src/internal/derived-table.ts +7 -13
  18. package/src/internal/dialect-renderers/mysql.ts +2046 -0
  19. package/src/{postgres/internal/sql-expression-renderer.ts → internal/dialect-renderers/postgres.ts} +867 -283
  20. package/src/{mysql/internal/sql-expression-renderer.ts → internal/dialect-renderers/sqlite.ts} +834 -358
  21. package/src/internal/dialect.ts +37 -0
  22. package/src/internal/dsl-mutation-runtime.ts +29 -10
  23. package/src/internal/dsl-plan-runtime.ts +41 -24
  24. package/src/internal/dsl-query-runtime.ts +11 -31
  25. package/src/internal/dsl-transaction-ddl-runtime.ts +61 -15
  26. package/src/internal/executor.ts +57 -15
  27. package/src/internal/expression-ast.ts +3 -2
  28. package/src/internal/grouping-key.ts +216 -9
  29. package/src/internal/implication-runtime.ts +3 -2
  30. package/src/internal/json/types.ts +155 -40
  31. package/src/internal/predicate/context.ts +14 -1
  32. package/src/internal/predicate/key.ts +19 -2
  33. package/src/internal/predicate/runtime.ts +30 -3
  34. package/src/internal/query.d.ts +38 -11
  35. package/src/internal/query.ts +315 -54
  36. package/src/internal/renderer.ts +51 -6
  37. package/src/internal/runtime/driver-value-mapping.ts +58 -0
  38. package/src/internal/runtime/normalize.ts +74 -43
  39. package/src/internal/runtime/schema.ts +5 -3
  40. package/src/internal/runtime/value.ts +153 -30
  41. package/src/internal/scalar.ts +6 -1
  42. package/src/internal/schema-derivation.d.ts +12 -61
  43. package/src/internal/schema-derivation.ts +90 -38
  44. package/src/internal/schema-expression.ts +2 -2
  45. package/src/internal/sql-expression-renderer.ts +19 -0
  46. package/src/internal/standard-dsl.ts +6885 -0
  47. package/src/internal/table-options.ts +229 -62
  48. package/src/internal/table.d.ts +33 -32
  49. package/src/internal/table.ts +469 -160
  50. package/src/mysql/column-extension.ts +3 -0
  51. package/src/mysql/column.ts +27 -12
  52. package/src/mysql/datatypes/index.ts +24 -2
  53. package/src/mysql/errors/catalog.ts +5 -5
  54. package/src/mysql/errors/normalize.ts +2 -2
  55. package/src/mysql/executor.ts +7 -5
  56. package/src/mysql/internal/dialect.ts +9 -4
  57. package/src/mysql/internal/dsl.ts +906 -324
  58. package/src/mysql/internal/renderer.ts +7 -2
  59. package/src/mysql/json.ts +37 -0
  60. package/src/mysql/query-extension.ts +16 -0
  61. package/src/mysql/query.ts +9 -2
  62. package/src/mysql/renderer.ts +31 -4
  63. package/src/mysql.ts +4 -12
  64. package/src/postgres/column-extension.ts +28 -0
  65. package/src/postgres/column.ts +9 -13
  66. package/src/postgres/datatypes/index.d.ts +2 -1
  67. package/src/postgres/datatypes/index.ts +3 -2
  68. package/src/postgres/errors/normalize.ts +2 -2
  69. package/src/postgres/executor.ts +55 -10
  70. package/src/postgres/function/core.ts +20 -4
  71. package/src/postgres/function/index.ts +1 -17
  72. package/src/postgres/internal/dialect.ts +9 -4
  73. package/src/postgres/internal/dsl.ts +850 -359
  74. package/src/postgres/internal/renderer.ts +7 -2
  75. package/src/postgres/internal/schema-ddl.ts +22 -9
  76. package/src/postgres/internal/schema-model.ts +244 -10
  77. package/src/postgres/json.ts +100 -24
  78. package/src/postgres/jsonb.ts +38 -0
  79. package/src/postgres/query-extension.ts +2 -0
  80. package/src/postgres/query.ts +9 -2
  81. package/src/postgres/renderer.ts +31 -4
  82. package/src/postgres/schema-management.ts +108 -16
  83. package/src/postgres/schema.ts +98 -15
  84. package/src/postgres/table.ts +203 -398
  85. package/src/postgres/type.ts +8 -7
  86. package/src/postgres.ts +9 -11
  87. package/src/sqlite/column-extension.ts +3 -0
  88. package/src/sqlite/column.ts +127 -0
  89. package/src/sqlite/datatypes/index.ts +80 -0
  90. package/src/sqlite/datatypes/spec.ts +98 -0
  91. package/src/sqlite/errors/catalog.ts +103 -0
  92. package/src/sqlite/errors/fields.ts +19 -0
  93. package/src/sqlite/errors/index.ts +19 -0
  94. package/src/sqlite/errors/normalize.ts +229 -0
  95. package/src/sqlite/errors/requirements.ts +71 -0
  96. package/src/sqlite/errors/types.ts +29 -0
  97. package/src/sqlite/executor.ts +229 -0
  98. package/src/sqlite/function/aggregate.ts +2 -0
  99. package/src/sqlite/function/core.ts +2 -0
  100. package/src/sqlite/function/index.ts +19 -0
  101. package/src/sqlite/function/string.ts +2 -0
  102. package/src/sqlite/function/temporal.ts +100 -0
  103. package/src/sqlite/function/window.ts +2 -0
  104. package/src/sqlite/internal/dialect.ts +42 -0
  105. package/src/sqlite/internal/dsl.ts +6979 -0
  106. package/src/sqlite/internal/renderer.ts +51 -0
  107. package/src/sqlite/json.ts +39 -0
  108. package/src/sqlite/query-extension.ts +2 -0
  109. package/src/sqlite/query.ts +196 -0
  110. package/src/sqlite/renderer.ts +51 -0
  111. package/src/sqlite.ts +14 -0
  112. package/src/standard/column.ts +163 -0
  113. package/src/standard/datatypes/index.ts +83 -0
  114. package/src/standard/datatypes/spec.ts +98 -0
  115. package/src/standard/dialect.ts +40 -0
  116. package/src/standard/function/aggregate.ts +2 -0
  117. package/src/standard/function/core.ts +2 -0
  118. package/src/standard/function/index.ts +18 -0
  119. package/src/standard/function/string.ts +2 -0
  120. package/src/standard/function/temporal.ts +78 -0
  121. package/src/standard/function/window.ts +2 -0
  122. package/src/standard/internal/renderer.ts +45 -0
  123. package/src/standard/query.ts +152 -0
  124. package/src/standard/renderer.ts +21 -0
  125. package/src/standard/table.ts +147 -0
  126. package/src/standard.ts +18 -0
  127. package/src/internal/aggregation-validation.ts +0 -57
  128. package/src/mysql/table.ts +0 -157
@@ -84,6 +84,26 @@ const findMapping = <Key extends MappingKey>(
84
84
  return undefined
85
85
  }
86
86
 
87
+ const isJsonDbType = (dbType: Expression.DbType.Any | undefined): boolean => {
88
+ if (dbType === undefined) {
89
+ return false
90
+ }
91
+ if ("base" in dbType) {
92
+ return isJsonDbType(dbType.base)
93
+ }
94
+ if (!("variant" in dbType)) {
95
+ return false
96
+ }
97
+ const variant = dbType.variant as string
98
+ return variant === "json" || variant === "jsonb"
99
+ }
100
+
101
+ const schemaAccepts = (
102
+ schema: Schema.Schema.Any | undefined,
103
+ value: unknown
104
+ ): boolean =>
105
+ schema !== undefined && (Schema.is(schema) as (candidate: unknown) => boolean)(value)
106
+
87
107
  const encodeWithSchema = (
88
108
  schema: Schema.Schema.Any | undefined,
89
109
  value: unknown
@@ -100,6 +120,32 @@ const encodeWithSchema = (
100
120
  }
101
121
  }
102
122
 
123
+ const normalizeJsonDriverString = (
124
+ value: string,
125
+ context: DriverValueContext
126
+ ): unknown | undefined => {
127
+ if (!isJsonDbType(context.dbType) || context.runtimeSchema === undefined) {
128
+ return undefined
129
+ }
130
+ try {
131
+ const parsed = JSON.parse(value)
132
+ if (value.trimStart().startsWith("\"") && schemaAccepts(context.runtimeSchema, parsed)) {
133
+ return parsed
134
+ }
135
+ if (schemaAccepts(context.runtimeSchema, value) && !schemaAccepts(context.runtimeSchema, parsed)) {
136
+ return value
137
+ }
138
+ } catch (error) {
139
+ if (error instanceof SyntaxError && schemaAccepts(context.runtimeSchema, value)) {
140
+ return value
141
+ }
142
+ if (!(error instanceof SyntaxError)) {
143
+ throw error
144
+ }
145
+ }
146
+ return undefined
147
+ }
148
+
103
149
  export const toDriverValue = (
104
150
  value: unknown,
105
151
  context: DriverValueContext
@@ -107,6 +153,9 @@ export const toDriverValue = (
107
153
  if (value === null) {
108
154
  return null
109
155
  }
156
+ if (value instanceof Date && Number.isNaN(value.getTime())) {
157
+ throw new Error("Expected a valid Date value")
158
+ }
110
159
  const dbType = context.dbType
111
160
  const encoded = encodeWithSchema(context.runtimeSchema, value)
112
161
  let current = encoded.value
@@ -114,6 +163,9 @@ export const toDriverValue = (
114
163
  if (custom !== undefined && dbType !== undefined) {
115
164
  return custom(current, dbType)
116
165
  }
166
+ if (encoded.encoded && typeof current === "string" && isJsonDbType(dbType)) {
167
+ return current
168
+ }
117
169
  return dbType === undefined || !encoded.encoded
118
170
  ? current
119
171
  : normalizeDbValue(dbType, current)
@@ -131,6 +183,12 @@ export const fromDriverValue = (
131
183
  if (custom !== undefined && dbType !== undefined) {
132
184
  return custom(value, dbType)
133
185
  }
186
+ if (typeof value === "string") {
187
+ const normalizedJsonString = normalizeJsonDriverString(value, context)
188
+ if (normalizedJsonString !== undefined) {
189
+ return normalizedJsonString
190
+ }
191
+ }
134
192
  return dbType === undefined
135
193
  ? value
136
194
  : normalizeDbValue(dbType, value)
@@ -1,13 +1,37 @@
1
1
  import type * as Expression from "../scalar.js"
2
2
  import type { RuntimeTag } from "../datatypes/shape.js"
3
+ import {
4
+ canonicalizeBigIntString,
5
+ canonicalizeDecimalString,
6
+ isValidInstantString,
7
+ isValidLocalDateString,
8
+ isValidLocalDateTimeString,
9
+ isValidLocalTimeString,
10
+ isValidOffsetTimeString
11
+ } from "./value.js"
3
12
 
4
13
  const isRecord = (value: unknown): value is Record<string, unknown> =>
5
14
  typeof value === "object" && value !== null && !Array.isArray(value)
6
15
 
16
+ const isPlainRecord = (value: unknown): value is Record<string, unknown> => {
17
+ if (!isRecord(value)) {
18
+ return false
19
+ }
20
+ const prototype = Object.getPrototypeOf(value)
21
+ return prototype === Object.prototype || prototype === null
22
+ }
23
+
7
24
  const pad = (value: number, width = 2): string => value.toString().padStart(width, "0")
8
25
 
26
+ const validDate = (value: Date): Date => {
27
+ if (Number.isNaN(value.getTime())) {
28
+ throw new Error("Expected a valid Date value")
29
+ }
30
+ return value
31
+ }
32
+
9
33
  const formatLocalDate = (value: Date): string =>
10
- `${value.getUTCFullYear()}-${pad(value.getUTCMonth() + 1)}-${pad(value.getUTCDate())}`
34
+ `${pad(value.getUTCFullYear(), 4)}-${pad(value.getUTCMonth() + 1)}-${pad(value.getUTCDate())}`
11
35
 
12
36
  const formatLocalTime = (value: Date): string => {
13
37
  const milliseconds = value.getUTCMilliseconds()
@@ -34,12 +58,17 @@ const expectString = (value: unknown, label: string): string => {
34
58
  throw new Error(`Expected ${label} as string`)
35
59
  }
36
60
 
61
+ const finiteNumberStringPattern = /^[+-]?(?:(?:\d+\.?\d*)|(?:\.\d+))(?:[eE][+-]?\d+)?$/
62
+
37
63
  const normalizeNumber = (value: unknown): number => {
38
64
  if (typeof value === "number" && Number.isFinite(value)) {
39
65
  return value
40
66
  }
41
- if (typeof value === "string" && value.trim() !== "") {
42
- const parsed = Number(value)
67
+ if (typeof value === "string") {
68
+ const trimmed = value.trim()
69
+ const parsed = finiteNumberStringPattern.test(trimmed)
70
+ ? Number(trimmed)
71
+ : Number.NaN
43
72
  if (Number.isFinite(parsed)) {
44
73
  return parsed
45
74
  }
@@ -81,27 +110,12 @@ const normalizeBigIntString = (value: unknown): string => {
81
110
  if (typeof value === "number" && Number.isSafeInteger(value)) {
82
111
  return BigInt(value).toString()
83
112
  }
84
- if (typeof value === "string" && /^-?\d+$/.test(value.trim())) {
85
- return BigInt(value.trim()).toString()
113
+ if (typeof value === "string") {
114
+ return canonicalizeBigIntString(value)
86
115
  }
87
116
  throw new Error("Expected an integer-like bigint value")
88
117
  }
89
118
 
90
- const canonicalizeDecimalString = (input: string): string => {
91
- const trimmed = input.trim()
92
- const match = /^([+-]?)(\d+)(?:\.(\d+))?$/.exec(trimmed)
93
- if (match === null) {
94
- throw new Error("Expected a decimal string")
95
- }
96
- const sign = match[1] === "-" ? "-" : ""
97
- const integer = match[2]!.replace(/^0+(?=\d)/, "") || "0"
98
- const fraction = (match[3] ?? "").replace(/0+$/, "")
99
- if (fraction.length === 0) {
100
- return `${sign}${integer}`
101
- }
102
- return `${sign}${integer}.${fraction}`
103
- }
104
-
105
119
  const normalizeDecimalString = (value: unknown): string => {
106
120
  if (typeof value === "string") {
107
121
  return canonicalizeDecimalString(value)
@@ -118,25 +132,28 @@ const normalizeDecimalString = (value: unknown): string => {
118
132
 
119
133
  const normalizeLocalDate = (value: unknown): string => {
120
134
  if (value instanceof Date) {
121
- return formatLocalDate(value)
135
+ return formatLocalDate(validDate(value))
122
136
  }
123
137
  const raw = expectString(value, "local date").trim()
124
- if (/^\d{4}-\d{2}-\d{2}$/.test(raw)) {
138
+ if (isValidLocalDateString(raw)) {
125
139
  return raw
126
140
  }
127
- const parsed = new Date(raw)
128
- if (!Number.isNaN(parsed.getTime())) {
129
- return formatLocalDate(parsed)
141
+ const canonicalInstant = raw.replace(" ", "T").replace(/z$/, "Z")
142
+ if (isValidInstantString(canonicalInstant)) {
143
+ const parsed = new Date(canonicalInstant)
144
+ if (!Number.isNaN(parsed.getTime())) {
145
+ return formatLocalDate(parsed)
146
+ }
130
147
  }
131
148
  throw new Error("Expected a local-date value")
132
149
  }
133
150
 
134
151
  const normalizeLocalTime = (value: unknown): string => {
135
152
  if (value instanceof Date) {
136
- return formatLocalTime(value)
153
+ return formatLocalTime(validDate(value))
137
154
  }
138
155
  const raw = expectString(value, "local time").trim()
139
- if (/^\d{2}:\d{2}:\d{2}(?:\.\d+)?$/.test(raw)) {
156
+ if (isValidLocalTimeString(raw)) {
140
157
  return raw
141
158
  }
142
159
  throw new Error("Expected a local-time value")
@@ -144,10 +161,10 @@ const normalizeLocalTime = (value: unknown): string => {
144
161
 
145
162
  const normalizeOffsetTime = (value: unknown): string => {
146
163
  if (value instanceof Date) {
147
- return `${formatLocalTime(value)}Z`
164
+ return `${formatLocalTime(validDate(value))}Z`
148
165
  }
149
166
  const raw = expectString(value, "offset time").trim()
150
- if (/^\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(raw)) {
167
+ if (isValidOffsetTimeString(raw)) {
151
168
  return raw
152
169
  }
153
170
  throw new Error("Expected an offset-time value")
@@ -155,14 +172,16 @@ const normalizeOffsetTime = (value: unknown): string => {
155
172
 
156
173
  const normalizeLocalDateTime = (value: unknown): string => {
157
174
  if (value instanceof Date) {
158
- return formatLocalDateTime(value)
175
+ return formatLocalDateTime(validDate(value))
159
176
  }
160
177
  const raw = expectString(value, "local datetime").trim()
161
- if (/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?$/.test(raw)) {
162
- return raw.replace(" ", "T")
178
+ const canonicalLocalDateTime = raw.replace(" ", "T")
179
+ if (isValidLocalDateTimeString(canonicalLocalDateTime)) {
180
+ return canonicalLocalDateTime
163
181
  }
164
- if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(raw)) {
165
- const parsed = new Date(raw)
182
+ const canonicalInstant = raw.replace(" ", "T").replace(/z$/, "Z")
183
+ if (isValidInstantString(canonicalInstant)) {
184
+ const parsed = new Date(canonicalInstant)
166
185
  if (!Number.isNaN(parsed.getTime())) {
167
186
  return formatLocalDateTime(parsed)
168
187
  }
@@ -172,13 +191,17 @@ const normalizeLocalDateTime = (value: unknown): string => {
172
191
 
173
192
  const normalizeInstant = (value: unknown): string => {
174
193
  if (value instanceof Date) {
175
- return value.toISOString()
194
+ return validDate(value).toISOString()
176
195
  }
177
196
  const raw = expectString(value, "instant").trim()
178
197
  if (!/[zZ]|[+-]\d{2}:\d{2}$/.test(raw)) {
179
198
  throw new Error("Instant values require a timezone offset")
180
199
  }
181
- const parsed = new Date(raw)
200
+ const canonicalInstant = raw.replace(" ", "T").replace(/z$/, "Z")
201
+ if (!isValidInstantString(canonicalInstant)) {
202
+ throw new Error("Expected an ISO instant value")
203
+ }
204
+ const parsed = new Date(canonicalInstant)
182
205
  if (Number.isNaN(parsed.getTime())) {
183
206
  throw new Error("Expected an ISO instant value")
184
207
  }
@@ -209,20 +232,21 @@ const normalizeBytes = (value: unknown): Uint8Array => {
209
232
  throw new Error("Expected a byte array value")
210
233
  }
211
234
 
212
- const isJsonValue = (value: unknown): boolean => {
235
+ export const isJsonValue = (value: unknown): boolean => {
213
236
  if (value === null) {
214
237
  return true
215
238
  }
216
239
  switch (typeof value) {
217
240
  case "string":
218
- case "number":
219
241
  case "boolean":
220
242
  return true
243
+ case "number":
244
+ return Number.isFinite(value)
221
245
  case "object":
222
246
  if (Array.isArray(value)) {
223
247
  return value.every(isJsonValue)
224
248
  }
225
- return isRecord(value) && Object.values(value).every(isJsonValue)
249
+ return isPlainRecord(value) && Object.values(value).every(isJsonValue)
226
250
  default:
227
251
  return false
228
252
  }
@@ -230,11 +254,18 @@ const isJsonValue = (value: unknown): boolean => {
230
254
 
231
255
  const normalizeJson = (value: unknown): unknown => {
232
256
  if (typeof value === "string") {
233
- const parsed = JSON.parse(value)
234
- if (isJsonValue(parsed)) {
235
- return parsed
257
+ try {
258
+ const parsed = JSON.parse(value)
259
+ if (isJsonValue(parsed)) {
260
+ return parsed
261
+ }
262
+ throw new Error("Parsed JSON value is not a valid JSON runtime")
263
+ } catch (error) {
264
+ if (error instanceof SyntaxError) {
265
+ return value
266
+ }
267
+ throw error
236
268
  }
237
- throw new Error("Parsed JSON value is not a valid JSON runtime")
238
269
  }
239
270
  if (isJsonValue(value)) {
240
271
  return value
@@ -38,12 +38,14 @@ const schemaCache = new WeakMap<Expression.Any, RuntimeSchema | undefined>()
38
38
  const isRecord = (value: unknown): value is Record<string, unknown> =>
39
39
  typeof value === "object" && value !== null && !Array.isArray(value)
40
40
 
41
+ const FiniteNumberSchema = Schema.Number.pipe(Schema.finite())
42
+
41
43
  const runtimeSchemaForTag = (tag: RuntimeTag): RuntimeSchema | undefined => {
42
44
  switch (tag) {
43
45
  case "string":
44
46
  return Schema.String
45
47
  case "number":
46
- return Schema.Number
48
+ return FiniteNumberSchema
47
49
  case "bigintString":
48
50
  return BigIntStringSchema
49
51
  case "boolean":
@@ -388,7 +390,7 @@ const deriveRuntimeSchema = (
388
390
  return Schema.String
389
391
  case "count":
390
392
  case "jsonLength":
391
- return Schema.Number
393
+ return FiniteNumberSchema
392
394
  case "max":
393
395
  case "min":
394
396
  return expressionRuntimeSchema(ast.value, context)
@@ -404,7 +406,7 @@ const deriveRuntimeSchema = (
404
406
  case "window":
405
407
  return ast.function === "over" && ast.value !== undefined
406
408
  ? expressionRuntimeSchema(ast.value, context)
407
- : Schema.Number
409
+ : FiniteNumberSchema
408
410
  case "jsonGet":
409
411
  case "jsonPath":
410
412
  case "jsonAccess":
@@ -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
  )
@@ -143,7 +143,12 @@ export interface DriverValueMapping {
143
143
  readonly jsonSelectSql?: (sql: string, dbType: DbType.Any) => string
144
144
  }
145
145
 
146
- export type DriverValueMappings = Readonly<Record<string, DriverValueMapping | undefined>>
146
+ export type DriverValueMappings = Readonly<Partial<Record<string, DriverValueMapping>>>
147
+
148
+ export type DriverValueMappingsFor<
149
+ Kind extends string,
150
+ Family extends string
151
+ > = Readonly<Partial<Record<Kind | Family | RuntimeTag, DriverValueMapping>>>
147
152
 
148
153
  /** Canonical static metadata stored on an expression. */
149
154
  export interface State<