effect-qb 0.16.0 → 4.0.0-beta.66

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/README.md +7 -0
  2. package/dist/mysql.js +1858 -715
  3. package/dist/postgres/metadata.js +2036 -172
  4. package/dist/postgres.js +8011 -6849
  5. package/dist/sqlite.js +8433 -0
  6. package/package.json +7 -4
  7. package/src/internal/column-state.d.ts +3 -3
  8. package/src/internal/column-state.ts +3 -3
  9. package/src/internal/column.ts +8 -8
  10. package/src/internal/derived-table.ts +29 -3
  11. package/src/internal/dialect.ts +3 -1
  12. package/src/internal/dsl-mutation-runtime.ts +173 -4
  13. package/src/internal/dsl-plan-runtime.ts +165 -20
  14. package/src/internal/dsl-query-runtime.ts +60 -6
  15. package/src/internal/dsl-transaction-ddl-runtime.ts +72 -2
  16. package/src/internal/executor.ts +100 -43
  17. package/src/internal/expression-ast.ts +3 -2
  18. package/src/internal/grouping-key.ts +141 -1
  19. package/src/internal/implication-runtime.ts +2 -1
  20. package/src/internal/json/types.ts +155 -40
  21. package/src/internal/predicate/context.ts +14 -1
  22. package/src/internal/predicate/key.ts +19 -2
  23. package/src/internal/predicate/runtime.ts +27 -3
  24. package/src/internal/query.d.ts +1 -1
  25. package/src/internal/query.ts +253 -31
  26. package/src/internal/renderer.ts +35 -2
  27. package/src/internal/runtime/driver-value-mapping.ts +60 -2
  28. package/src/internal/runtime/normalize.ts +62 -38
  29. package/src/internal/runtime/schema.ts +32 -40
  30. package/src/internal/runtime/value.ts +159 -39
  31. package/src/internal/scalar.d.ts +1 -1
  32. package/src/internal/scalar.ts +1 -1
  33. package/src/internal/schema-derivation.d.ts +12 -61
  34. package/src/internal/schema-derivation.ts +95 -43
  35. package/src/internal/table-options.ts +108 -1
  36. package/src/internal/table.d.ts +29 -22
  37. package/src/internal/table.ts +260 -53
  38. package/src/mysql/column.ts +24 -8
  39. package/src/mysql/datatypes/index.ts +21 -0
  40. package/src/mysql/errors/catalog.ts +5 -5
  41. package/src/mysql/errors/normalize.ts +2 -2
  42. package/src/mysql/executor.ts +4 -4
  43. package/src/mysql/function/temporal.ts +1 -1
  44. package/src/mysql/internal/dsl.ts +759 -235
  45. package/src/mysql/internal/renderer.ts +2 -1
  46. package/src/mysql/internal/sql-expression-renderer.ts +486 -130
  47. package/src/mysql/query.ts +9 -2
  48. package/src/mysql/table.ts +64 -35
  49. package/src/postgres/column.ts +14 -12
  50. package/src/postgres/errors/normalize.ts +2 -2
  51. package/src/postgres/executor.ts +52 -9
  52. package/src/postgres/function/core.ts +19 -1
  53. package/src/postgres/function/temporal.ts +1 -1
  54. package/src/postgres/internal/dsl.ts +705 -256
  55. package/src/postgres/internal/renderer.ts +2 -1
  56. package/src/postgres/internal/schema-ddl.ts +2 -1
  57. package/src/postgres/internal/schema-model.ts +6 -3
  58. package/src/postgres/internal/sql-expression-renderer.ts +420 -91
  59. package/src/postgres/json.ts +57 -17
  60. package/src/postgres/query.ts +9 -2
  61. package/src/postgres/schema-management.ts +92 -6
  62. package/src/postgres/schema.ts +1 -1
  63. package/src/postgres/table.ts +203 -75
  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 +6927 -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 +175 -0
  88. package/src/sqlite.ts +22 -0
@@ -1,13 +1,30 @@
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
 
9
26
  const formatLocalDate = (value: Date): string =>
10
- `${value.getUTCFullYear()}-${pad(value.getUTCMonth() + 1)}-${pad(value.getUTCDate())}`
27
+ `${pad(value.getUTCFullYear(), 4)}-${pad(value.getUTCMonth() + 1)}-${pad(value.getUTCDate())}`
11
28
 
12
29
  const formatLocalTime = (value: Date): string => {
13
30
  const milliseconds = value.getUTCMilliseconds()
@@ -34,12 +51,17 @@ const expectString = (value: unknown, label: string): string => {
34
51
  throw new Error(`Expected ${label} as string`)
35
52
  }
36
53
 
54
+ const finiteNumberStringPattern = /^[+-]?(?:(?:\d+\.?\d*)|(?:\.\d+))(?:[eE][+-]?\d+)?$/
55
+
37
56
  const normalizeNumber = (value: unknown): number => {
38
57
  if (typeof value === "number" && Number.isFinite(value)) {
39
58
  return value
40
59
  }
41
- if (typeof value === "string" && value.trim() !== "") {
42
- const parsed = Number(value)
60
+ if (typeof value === "string") {
61
+ const trimmed = value.trim()
62
+ const parsed = finiteNumberStringPattern.test(trimmed)
63
+ ? Number(trimmed)
64
+ : Number.NaN
43
65
  if (Number.isFinite(parsed)) {
44
66
  return parsed
45
67
  }
@@ -81,27 +103,12 @@ const normalizeBigIntString = (value: unknown): string => {
81
103
  if (typeof value === "number" && Number.isSafeInteger(value)) {
82
104
  return BigInt(value).toString()
83
105
  }
84
- if (typeof value === "string" && /^-?\d+$/.test(value.trim())) {
85
- return BigInt(value.trim()).toString()
106
+ if (typeof value === "string") {
107
+ return canonicalizeBigIntString(value)
86
108
  }
87
109
  throw new Error("Expected an integer-like bigint value")
88
110
  }
89
111
 
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
112
  const normalizeDecimalString = (value: unknown): string => {
106
113
  if (typeof value === "string") {
107
114
  return canonicalizeDecimalString(value)
@@ -121,12 +128,15 @@ const normalizeLocalDate = (value: unknown): string => {
121
128
  return formatLocalDate(value)
122
129
  }
123
130
  const raw = expectString(value, "local date").trim()
124
- if (/^\d{4}-\d{2}-\d{2}$/.test(raw)) {
131
+ if (isValidLocalDateString(raw)) {
125
132
  return raw
126
133
  }
127
- const parsed = new Date(raw)
128
- if (!Number.isNaN(parsed.getTime())) {
129
- return formatLocalDate(parsed)
134
+ const canonicalInstant = raw.replace(" ", "T").replace(/z$/, "Z")
135
+ if (isValidInstantString(canonicalInstant)) {
136
+ const parsed = new Date(canonicalInstant)
137
+ if (!Number.isNaN(parsed.getTime())) {
138
+ return formatLocalDate(parsed)
139
+ }
130
140
  }
131
141
  throw new Error("Expected a local-date value")
132
142
  }
@@ -136,7 +146,7 @@ const normalizeLocalTime = (value: unknown): string => {
136
146
  return formatLocalTime(value)
137
147
  }
138
148
  const raw = expectString(value, "local time").trim()
139
- if (/^\d{2}:\d{2}:\d{2}(?:\.\d+)?$/.test(raw)) {
149
+ if (isValidLocalTimeString(raw)) {
140
150
  return raw
141
151
  }
142
152
  throw new Error("Expected a local-time value")
@@ -147,7 +157,7 @@ const normalizeOffsetTime = (value: unknown): string => {
147
157
  return `${formatLocalTime(value)}Z`
148
158
  }
149
159
  const raw = expectString(value, "offset time").trim()
150
- if (/^\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(raw)) {
160
+ if (isValidOffsetTimeString(raw)) {
151
161
  return raw
152
162
  }
153
163
  throw new Error("Expected an offset-time value")
@@ -158,11 +168,13 @@ const normalizeLocalDateTime = (value: unknown): string => {
158
168
  return formatLocalDateTime(value)
159
169
  }
160
170
  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")
171
+ const canonicalLocalDateTime = raw.replace(" ", "T")
172
+ if (isValidLocalDateTimeString(canonicalLocalDateTime)) {
173
+ return canonicalLocalDateTime
163
174
  }
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)
175
+ const canonicalInstant = raw.replace(" ", "T").replace(/z$/, "Z")
176
+ if (isValidInstantString(canonicalInstant)) {
177
+ const parsed = new Date(canonicalInstant)
166
178
  if (!Number.isNaN(parsed.getTime())) {
167
179
  return formatLocalDateTime(parsed)
168
180
  }
@@ -178,7 +190,11 @@ const normalizeInstant = (value: unknown): string => {
178
190
  if (!/[zZ]|[+-]\d{2}:\d{2}$/.test(raw)) {
179
191
  throw new Error("Instant values require a timezone offset")
180
192
  }
181
- const parsed = new Date(raw)
193
+ const canonicalInstant = raw.replace(" ", "T").replace(/z$/, "Z")
194
+ if (!isValidInstantString(canonicalInstant)) {
195
+ throw new Error("Expected an ISO instant value")
196
+ }
197
+ const parsed = new Date(canonicalInstant)
182
198
  if (Number.isNaN(parsed.getTime())) {
183
199
  throw new Error("Expected an ISO instant value")
184
200
  }
@@ -209,20 +225,21 @@ const normalizeBytes = (value: unknown): Uint8Array => {
209
225
  throw new Error("Expected a byte array value")
210
226
  }
211
227
 
212
- const isJsonValue = (value: unknown): boolean => {
228
+ export const isJsonValue = (value: unknown): boolean => {
213
229
  if (value === null) {
214
230
  return true
215
231
  }
216
232
  switch (typeof value) {
217
233
  case "string":
218
- case "number":
219
234
  case "boolean":
220
235
  return true
236
+ case "number":
237
+ return Number.isFinite(value)
221
238
  case "object":
222
239
  if (Array.isArray(value)) {
223
240
  return value.every(isJsonValue)
224
241
  }
225
- return isRecord(value) && Object.values(value).every(isJsonValue)
242
+ return isPlainRecord(value) && Object.values(value).every(isJsonValue)
226
243
  default:
227
244
  return false
228
245
  }
@@ -230,11 +247,18 @@ const isJsonValue = (value: unknown): boolean => {
230
247
 
231
248
  const normalizeJson = (value: unknown): unknown => {
232
249
  if (typeof value === "string") {
233
- const parsed = JSON.parse(value)
234
- if (isJsonValue(parsed)) {
235
- return parsed
250
+ try {
251
+ const parsed = JSON.parse(value)
252
+ if (isJsonValue(parsed)) {
253
+ return parsed
254
+ }
255
+ throw new Error("Parsed JSON value is not a valid JSON runtime")
256
+ } catch (error) {
257
+ if (error instanceof SyntaxError) {
258
+ return value
259
+ }
260
+ throw error
236
261
  }
237
- throw new Error("Parsed JSON value is not a valid JSON runtime")
238
262
  }
239
263
  if (isJsonValue(value)) {
240
264
  return value
@@ -27,7 +27,7 @@ import {
27
27
  } from "./value.js"
28
28
  import type { RuntimeTag } from "../datatypes/shape.js"
29
29
 
30
- export type RuntimeSchema = Schema.Schema<any, any, any>
30
+ export type RuntimeSchema = Schema.Top
31
31
 
32
32
  type SchemaContext = {
33
33
  readonly assumptions: PredicateFormula
@@ -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.check(Schema.isFinite())
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":
@@ -65,14 +67,11 @@ const runtimeSchemaForTag = (tag: RuntimeTag): RuntimeSchema | undefined => {
65
67
  case "decimalString":
66
68
  return DecimalStringSchema
67
69
  case "bytes":
68
- return Schema.Uint8ArrayFromSelf
70
+ return Schema.Uint8Array
69
71
  case "array":
70
72
  return Schema.Array(Schema.Unknown)
71
73
  case "record":
72
- return Schema.Record({
73
- key: Schema.String,
74
- value: Schema.Unknown
75
- })
74
+ return Schema.Record(Schema.String, Schema.Unknown)
76
75
  case "null":
77
76
  return Schema.Null
78
77
  case "unknown":
@@ -112,7 +111,7 @@ export const runtimeSchemaForDbType = (
112
111
  }
113
112
 
114
113
  const makeSchemaFromAst = (ast: SchemaAST.AST): RuntimeSchema =>
115
- Schema.make(ast)
114
+ Schema.make<RuntimeSchema>(ast)
116
115
 
117
116
  const unionAst = (asts: ReadonlyArray<SchemaAST.AST>): SchemaAST.AST | undefined => {
118
117
  if (asts.length === 0) {
@@ -121,7 +120,7 @@ const unionAst = (asts: ReadonlyArray<SchemaAST.AST>): SchemaAST.AST | undefined
121
120
  if (asts.length === 1) {
122
121
  return asts[0]
123
122
  }
124
- return SchemaAST.Union.make(asts)
123
+ return new SchemaAST.Union(asts, "anyOf")
125
124
  }
126
125
 
127
126
  const propertyAstOf = (
@@ -129,18 +128,14 @@ const propertyAstOf = (
129
128
  key: string
130
129
  ): SchemaAST.AST | undefined => {
131
130
  switch (ast._tag) {
132
- case "Transformation":
133
- return propertyAstOf(SchemaAST.typeAST(ast), key)
134
- case "Refinement":
135
- return propertyAstOf(ast.from, key)
136
131
  case "Suspend":
137
- return propertyAstOf(ast.f(), key)
138
- case "TypeLiteral": {
132
+ return propertyAstOf(ast.thunk(), key)
133
+ case "Objects": {
139
134
  const property = ast.propertySignatures.find((entry) => entry.name === key)
140
135
  if (property !== undefined) {
141
136
  return property.type
142
137
  }
143
- const index = ast.indexSignatures.find((entry) => entry.parameter._tag === "StringKeyword")
138
+ const index = ast.indexSignatures.find((entry) => entry.parameter._tag === "String")
144
139
  return index?.type
145
140
  }
146
141
  case "Union": {
@@ -160,21 +155,17 @@ const numberAstOf = (
160
155
  index: number
161
156
  ): SchemaAST.AST | undefined => {
162
157
  switch (ast._tag) {
163
- case "Transformation":
164
- return numberAstOf(SchemaAST.typeAST(ast), index)
165
- case "Refinement":
166
- return numberAstOf(ast.from, index)
167
158
  case "Suspend":
168
- return numberAstOf(ast.f(), index)
169
- case "TupleType": {
159
+ return numberAstOf(ast.thunk(), index)
160
+ case "Arrays": {
170
161
  const element = ast.elements[index]
171
162
  if (element !== undefined) {
172
- return element.type
163
+ return element
173
164
  }
174
165
  if (ast.rest.length === 0) {
175
166
  return undefined
176
167
  }
177
- return unionAst(ast.rest.map((entry) => entry.type))
168
+ return unionAst(ast.rest)
178
169
  }
179
170
  case "Union": {
180
171
  const values = ast.types.flatMap((member) => {
@@ -197,7 +188,9 @@ const schemaAstAtExactJsonPath = (
197
188
  schema: RuntimeSchema,
198
189
  segments: readonly JsonPath.CanonicalSegment[]
199
190
  ): SchemaAST.AST | undefined => {
200
- let current: SchemaAST.AST = SchemaAST.typeAST(schema.ast)
191
+ // JSON path operators return encoded JSON subvalues, so preserve field-level
192
+ // encoding links instead of walking the type-side AST.
193
+ let current: SchemaAST.AST = schema.ast
201
194
  for (const segment of segments) {
202
195
  if (segment.kind === "key") {
203
196
  const property = propertyAstOf(current, segment.key)
@@ -228,7 +221,7 @@ const unionSchemas = (schemas: ReadonlyArray<RuntimeSchema | undefined>): Runtim
228
221
  if (resolved.length === 1) {
229
222
  return resolved[0]
230
223
  }
231
- return Schema.Union(...resolved)
224
+ return Schema.Union(resolved)
232
225
  }
233
226
 
234
227
  const firstSelectedExpression = (
@@ -239,24 +232,23 @@ const firstSelectedExpression = (
239
232
  }
240
233
 
241
234
  const isJsonCompatibleAst = (ast: SchemaAST.AST): boolean => {
235
+ ast = SchemaAST.toType(ast)
242
236
  switch (ast._tag) {
243
- case "StringKeyword":
244
- case "NumberKeyword":
245
- case "BooleanKeyword":
246
- case "TupleType":
247
- case "TypeLiteral":
237
+ case "String":
238
+ case "Number":
239
+ case "Boolean":
240
+ case "Null":
241
+ case "Arrays":
242
+ case "Objects":
248
243
  return true
249
244
  case "Literal":
250
- return ast.literal === null ||
251
- typeof ast.literal === "string" ||
245
+ return typeof ast.literal === "string" ||
252
246
  typeof ast.literal === "number" ||
253
247
  typeof ast.literal === "boolean"
254
248
  case "Union":
255
249
  return ast.types.every(isJsonCompatibleAst)
256
- case "Transformation":
257
- return isJsonCompatibleAst(SchemaAST.typeAST(ast))
258
250
  case "Suspend":
259
- return isJsonCompatibleAst(ast.f())
251
+ return isJsonCompatibleAst(ast.thunk())
260
252
  default:
261
253
  return false
262
254
  }
@@ -266,7 +258,7 @@ const jsonCompatibleSchema = (schema: RuntimeSchema | undefined): RuntimeSchema
266
258
  if (schema === undefined) {
267
259
  return undefined
268
260
  }
269
- const ast = SchemaAST.typeAST(schema.ast)
261
+ const ast = SchemaAST.toType(schema.ast)
270
262
  return isJsonCompatibleAst(ast) ? schema : JsonValueSchema
271
263
  }
272
264
 
@@ -281,7 +273,7 @@ const buildStructSchema = (
281
273
  }
282
274
 
283
275
  const buildTupleSchema = (values: readonly Expression.Any[], context?: SchemaContext): RuntimeSchema =>
284
- Schema.Tuple(...values.map((value) => expressionRuntimeSchema(value, context) ?? JsonValueSchema))
276
+ Schema.Tuple(values.map((value) => expressionRuntimeSchema(value, context) ?? JsonValueSchema))
285
277
 
286
278
  const deriveCaseSchema = (
287
279
  ast: ExpressionAst.CaseNode,
@@ -388,7 +380,7 @@ const deriveRuntimeSchema = (
388
380
  return Schema.String
389
381
  case "count":
390
382
  case "jsonLength":
391
- return Schema.Number
383
+ return FiniteNumberSchema
392
384
  case "max":
393
385
  case "min":
394
386
  return expressionRuntimeSchema(ast.value, context)
@@ -404,7 +396,7 @@ const deriveRuntimeSchema = (
404
396
  case "window":
405
397
  return ast.function === "over" && ast.value !== undefined
406
398
  ? expressionRuntimeSchema(ast.value, context)
407
- : Schema.Number
399
+ : FiniteNumberSchema
408
400
  case "jsonGet":
409
401
  case "jsonPath":
410
402
  case "jsonAccess":
@@ -19,67 +19,187 @@ const brandString = <BrandName extends string>(
19
19
  brand: BrandName
20
20
  ): Schema.Schema<string & Brand.Brand<BrandName>> =>
21
21
  Schema.String.pipe(
22
- Schema.pattern(pattern),
22
+ Schema.check(Schema.isPattern(pattern)),
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.check(Schema.isPattern(localDatePattern)),
104
+ Schema.check(Schema.makeFilter((value) => isValidLocalDateString(value))),
105
+ Schema.brand("LocalDateString")
106
+ ) as unknown as Schema.Schema<LocalDateString>
107
+
108
+ export const LocalTimeStringSchema = Schema.String.pipe(
109
+ Schema.check(Schema.isPattern(localTimePattern)),
110
+ Schema.check(Schema.makeFilter((value) => isValidLocalTimeString(value))),
111
+ Schema.brand("LocalTimeString")
112
+ ) as unknown as Schema.Schema<LocalTimeString>
113
+
114
+ export const OffsetTimeStringSchema = Schema.String.pipe(
115
+ Schema.check(Schema.isPattern(offsetTimePattern)),
116
+ Schema.check(Schema.makeFilter((value) => isValidOffsetTimeString(value))),
117
+ Schema.brand("OffsetTimeString")
118
+ ) as unknown as Schema.Schema<OffsetTimeString>
119
+
120
+ export const LocalDateTimeStringSchema = Schema.String.pipe(
121
+ Schema.check(Schema.isPattern(localDateTimePattern)),
122
+ Schema.check(Schema.makeFilter((value) => isValidLocalDateTimeString(value))),
123
+ Schema.brand("LocalDateTimeString")
124
+ ) as unknown as Schema.Schema<LocalDateTimeString>
125
+
126
+ export const InstantStringSchema = Schema.String.pipe(
127
+ Schema.check(Schema.isPattern(instantPattern)),
128
+ Schema.check(Schema.makeFilter((value) => isValidInstantString(value))),
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.check(Schema.makeFilter((value) => isCanonicalBigIntString(value))),
181
+ Schema.brand("BigIntString")
182
+ ) as unknown as Schema.Schema<BigIntString>
183
+
184
+ export const DecimalStringSchema = Schema.String.pipe(
185
+ Schema.check(Schema.makeFilter((value) => isCanonicalDecimalString(value))),
186
+ Schema.brand("DecimalString")
187
+ ) as unknown as Schema.Schema<DecimalString>
65
188
 
66
189
  export const JsonValueSchema: Schema.Schema<JsonValue> = Schema.suspend(() =>
67
- Schema.Union(
190
+ Schema.Union([
68
191
  Schema.String,
69
- Schema.Number,
192
+ Schema.Number.check(Schema.isFinite()),
70
193
  Schema.Boolean,
71
194
  Schema.Null,
72
195
  Schema.Array(JsonValueSchema),
73
- Schema.Record({
74
- key: Schema.String,
75
- value: JsonValueSchema
76
- })
77
- )
196
+ Schema.Record(Schema.String, JsonValueSchema)
197
+ ])
78
198
  )
79
199
 
80
- export const JsonPrimitiveSchema: Schema.Schema<JsonPrimitive> = Schema.Union(
200
+ export const JsonPrimitiveSchema: Schema.Schema<JsonPrimitive> = Schema.Union([
81
201
  Schema.String,
82
- Schema.Number,
202
+ Schema.Number.check(Schema.isFinite()),
83
203
  Schema.Boolean,
84
204
  Schema.Null
85
- )
205
+ ])
@@ -73,7 +73,7 @@ export declare namespace DbType {
73
73
  export interface State<Runtime, Db extends DbType.Any, Nullable extends Nullability, Dialect extends string, Kind extends ScalarKind, Deps extends BindingId = never> {
74
74
  readonly runtime: Runtime;
75
75
  readonly dbType: Db;
76
- readonly runtimeSchema?: Schema.Schema.Any;
76
+ readonly runtimeSchema?: Schema.Top;
77
77
  readonly nullability: Nullable;
78
78
  readonly dialect: Dialect;
79
79
  readonly kind: Kind;
@@ -156,7 +156,7 @@ export interface State<
156
156
  > {
157
157
  readonly runtime: Runtime
158
158
  readonly dbType: Db
159
- readonly runtimeSchema?: Schema.Schema.Any
159
+ readonly runtimeSchema?: Schema.Top
160
160
  readonly driverValueMapping?: DriverValueMapping
161
161
  readonly nullability: Nullable
162
162
  readonly dialect: Dialect