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.
- package/README.md +4 -0
- package/dist/index.js +8065 -0
- package/dist/mysql.js +4036 -2418
- package/dist/postgres/metadata.js +2536 -625
- package/dist/postgres.js +8248 -7857
- package/dist/sqlite.js +8854 -0
- package/dist/standard.js +8019 -0
- package/package.json +15 -3
- package/src/casing.ts +71 -0
- package/src/index.ts +2 -0
- package/src/internal/casing.ts +89 -0
- package/src/internal/column-state.ts +11 -6
- package/src/internal/column.ts +44 -7
- package/src/internal/datatypes/define.ts +2 -1
- package/src/internal/datatypes/enrich.ts +23 -0
- package/src/internal/datatypes/lookup.ts +14 -7
- package/src/internal/derived-table.ts +7 -13
- package/src/internal/dialect-renderers/mysql.ts +2046 -0
- package/src/{postgres/internal/sql-expression-renderer.ts → internal/dialect-renderers/postgres.ts} +867 -283
- package/src/{mysql/internal/sql-expression-renderer.ts → internal/dialect-renderers/sqlite.ts} +834 -358
- package/src/internal/dialect.ts +37 -0
- package/src/internal/dsl-mutation-runtime.ts +29 -10
- package/src/internal/dsl-plan-runtime.ts +41 -24
- package/src/internal/dsl-query-runtime.ts +11 -31
- package/src/internal/dsl-transaction-ddl-runtime.ts +61 -15
- package/src/internal/executor.ts +57 -15
- package/src/internal/expression-ast.ts +3 -2
- package/src/internal/grouping-key.ts +216 -9
- package/src/internal/implication-runtime.ts +3 -2
- package/src/internal/json/types.ts +155 -40
- package/src/internal/predicate/context.ts +14 -1
- package/src/internal/predicate/key.ts +19 -2
- package/src/internal/predicate/runtime.ts +30 -3
- package/src/internal/query.d.ts +38 -11
- package/src/internal/query.ts +315 -54
- package/src/internal/renderer.ts +51 -6
- package/src/internal/runtime/driver-value-mapping.ts +58 -0
- package/src/internal/runtime/normalize.ts +74 -43
- package/src/internal/runtime/schema.ts +5 -3
- package/src/internal/runtime/value.ts +153 -30
- package/src/internal/scalar.ts +6 -1
- package/src/internal/schema-derivation.d.ts +12 -61
- package/src/internal/schema-derivation.ts +90 -38
- package/src/internal/schema-expression.ts +2 -2
- package/src/internal/sql-expression-renderer.ts +19 -0
- package/src/internal/standard-dsl.ts +6885 -0
- package/src/internal/table-options.ts +229 -62
- package/src/internal/table.d.ts +33 -32
- package/src/internal/table.ts +469 -160
- package/src/mysql/column-extension.ts +3 -0
- package/src/mysql/column.ts +27 -12
- package/src/mysql/datatypes/index.ts +24 -2
- package/src/mysql/errors/catalog.ts +5 -5
- package/src/mysql/errors/normalize.ts +2 -2
- package/src/mysql/executor.ts +7 -5
- package/src/mysql/internal/dialect.ts +9 -4
- package/src/mysql/internal/dsl.ts +906 -324
- package/src/mysql/internal/renderer.ts +7 -2
- package/src/mysql/json.ts +37 -0
- package/src/mysql/query-extension.ts +16 -0
- package/src/mysql/query.ts +9 -2
- package/src/mysql/renderer.ts +31 -4
- package/src/mysql.ts +4 -12
- package/src/postgres/column-extension.ts +28 -0
- package/src/postgres/column.ts +9 -13
- package/src/postgres/datatypes/index.d.ts +2 -1
- package/src/postgres/datatypes/index.ts +3 -2
- package/src/postgres/errors/normalize.ts +2 -2
- package/src/postgres/executor.ts +55 -10
- package/src/postgres/function/core.ts +20 -4
- package/src/postgres/function/index.ts +1 -17
- package/src/postgres/internal/dialect.ts +9 -4
- package/src/postgres/internal/dsl.ts +850 -359
- package/src/postgres/internal/renderer.ts +7 -2
- package/src/postgres/internal/schema-ddl.ts +22 -9
- package/src/postgres/internal/schema-model.ts +244 -10
- package/src/postgres/json.ts +100 -24
- package/src/postgres/jsonb.ts +38 -0
- package/src/postgres/query-extension.ts +2 -0
- package/src/postgres/query.ts +9 -2
- package/src/postgres/renderer.ts +31 -4
- package/src/postgres/schema-management.ts +108 -16
- package/src/postgres/schema.ts +98 -15
- package/src/postgres/table.ts +203 -398
- package/src/postgres/type.ts +8 -7
- package/src/postgres.ts +9 -11
- package/src/sqlite/column-extension.ts +3 -0
- package/src/sqlite/column.ts +127 -0
- package/src/sqlite/datatypes/index.ts +80 -0
- package/src/sqlite/datatypes/spec.ts +98 -0
- package/src/sqlite/errors/catalog.ts +103 -0
- package/src/sqlite/errors/fields.ts +19 -0
- package/src/sqlite/errors/index.ts +19 -0
- package/src/sqlite/errors/normalize.ts +229 -0
- package/src/sqlite/errors/requirements.ts +71 -0
- package/src/sqlite/errors/types.ts +29 -0
- package/src/sqlite/executor.ts +229 -0
- package/src/sqlite/function/aggregate.ts +2 -0
- package/src/sqlite/function/core.ts +2 -0
- package/src/sqlite/function/index.ts +19 -0
- package/src/sqlite/function/string.ts +2 -0
- package/src/sqlite/function/temporal.ts +100 -0
- package/src/sqlite/function/window.ts +2 -0
- package/src/sqlite/internal/dialect.ts +42 -0
- package/src/sqlite/internal/dsl.ts +6979 -0
- package/src/sqlite/internal/renderer.ts +51 -0
- package/src/sqlite/json.ts +39 -0
- package/src/sqlite/query-extension.ts +2 -0
- package/src/sqlite/query.ts +196 -0
- package/src/sqlite/renderer.ts +51 -0
- package/src/sqlite.ts +14 -0
- package/src/standard/column.ts +163 -0
- package/src/standard/datatypes/index.ts +83 -0
- package/src/standard/datatypes/spec.ts +98 -0
- package/src/standard/dialect.ts +40 -0
- package/src/standard/function/aggregate.ts +2 -0
- package/src/standard/function/core.ts +2 -0
- package/src/standard/function/index.ts +18 -0
- package/src/standard/function/string.ts +2 -0
- package/src/standard/function/temporal.ts +78 -0
- package/src/standard/function/window.ts +2 -0
- package/src/standard/internal/renderer.ts +45 -0
- package/src/standard/query.ts +152 -0
- package/src/standard/renderer.ts +21 -0
- package/src/standard/table.ts +147 -0
- package/src/standard.ts +18 -0
- package/src/internal/aggregation-validation.ts +0 -57
- 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"
|
|
42
|
-
const
|
|
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"
|
|
85
|
-
return
|
|
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 (
|
|
138
|
+
if (isValidLocalDateString(raw)) {
|
|
125
139
|
return raw
|
|
126
140
|
}
|
|
127
|
-
const
|
|
128
|
-
if (
|
|
129
|
-
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
162
|
-
|
|
178
|
+
const canonicalLocalDateTime = raw.replace(" ", "T")
|
|
179
|
+
if (isValidLocalDateTimeString(canonicalLocalDateTime)) {
|
|
180
|
+
return canonicalLocalDateTime
|
|
163
181
|
}
|
|
164
|
-
|
|
165
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
|
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
|
|
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
|
-
:
|
|
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
|
|
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
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
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
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
62
|
-
|
|
63
|
-
|
|
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
|
)
|
package/src/internal/scalar.ts
CHANGED
|
@@ -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
|
|
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<
|