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.
- package/README.md +7 -0
- package/dist/mysql.js +1858 -715
- package/dist/postgres/metadata.js +2036 -172
- package/dist/postgres.js +8011 -6849
- package/dist/sqlite.js +8433 -0
- package/package.json +7 -4
- package/src/internal/column-state.d.ts +3 -3
- package/src/internal/column-state.ts +3 -3
- package/src/internal/column.ts +8 -8
- package/src/internal/derived-table.ts +29 -3
- package/src/internal/dialect.ts +3 -1
- package/src/internal/dsl-mutation-runtime.ts +173 -4
- package/src/internal/dsl-plan-runtime.ts +165 -20
- package/src/internal/dsl-query-runtime.ts +60 -6
- package/src/internal/dsl-transaction-ddl-runtime.ts +72 -2
- package/src/internal/executor.ts +100 -43
- package/src/internal/expression-ast.ts +3 -2
- package/src/internal/grouping-key.ts +141 -1
- package/src/internal/implication-runtime.ts +2 -1
- 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 +27 -3
- package/src/internal/query.d.ts +1 -1
- package/src/internal/query.ts +253 -31
- package/src/internal/renderer.ts +35 -2
- package/src/internal/runtime/driver-value-mapping.ts +60 -2
- package/src/internal/runtime/normalize.ts +62 -38
- package/src/internal/runtime/schema.ts +32 -40
- package/src/internal/runtime/value.ts +159 -39
- package/src/internal/scalar.d.ts +1 -1
- package/src/internal/scalar.ts +1 -1
- package/src/internal/schema-derivation.d.ts +12 -61
- package/src/internal/schema-derivation.ts +95 -43
- package/src/internal/table-options.ts +108 -1
- package/src/internal/table.d.ts +29 -22
- package/src/internal/table.ts +260 -53
- package/src/mysql/column.ts +24 -8
- package/src/mysql/datatypes/index.ts +21 -0
- package/src/mysql/errors/catalog.ts +5 -5
- package/src/mysql/errors/normalize.ts +2 -2
- package/src/mysql/executor.ts +4 -4
- package/src/mysql/function/temporal.ts +1 -1
- package/src/mysql/internal/dsl.ts +759 -235
- package/src/mysql/internal/renderer.ts +2 -1
- package/src/mysql/internal/sql-expression-renderer.ts +486 -130
- package/src/mysql/query.ts +9 -2
- package/src/mysql/table.ts +64 -35
- package/src/postgres/column.ts +14 -12
- package/src/postgres/errors/normalize.ts +2 -2
- package/src/postgres/executor.ts +52 -9
- package/src/postgres/function/core.ts +19 -1
- package/src/postgres/function/temporal.ts +1 -1
- package/src/postgres/internal/dsl.ts +705 -256
- package/src/postgres/internal/renderer.ts +2 -1
- package/src/postgres/internal/schema-ddl.ts +2 -1
- package/src/postgres/internal/schema-model.ts +6 -3
- package/src/postgres/internal/sql-expression-renderer.ts +420 -91
- package/src/postgres/json.ts +57 -17
- package/src/postgres/query.ts +9 -2
- package/src/postgres/schema-management.ts +92 -6
- package/src/postgres/schema.ts +1 -1
- package/src/postgres/table.ts +203 -75
- package/src/sqlite/column.ts +128 -0
- package/src/sqlite/datatypes/index.ts +79 -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 +227 -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 +37 -0
- package/src/sqlite/internal/dsl.ts +6927 -0
- package/src/sqlite/internal/renderer.ts +47 -0
- package/src/sqlite/internal/sql-expression-renderer.ts +1821 -0
- package/src/sqlite/json.ts +2 -0
- package/src/sqlite/query.ts +196 -0
- package/src/sqlite/renderer.ts +24 -0
- package/src/sqlite/table.ts +175 -0
- 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"
|
|
42
|
-
const
|
|
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"
|
|
85
|
-
return
|
|
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 (
|
|
131
|
+
if (isValidLocalDateString(raw)) {
|
|
125
132
|
return raw
|
|
126
133
|
}
|
|
127
|
-
const
|
|
128
|
-
if (
|
|
129
|
-
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
162
|
-
|
|
171
|
+
const canonicalLocalDateTime = raw.replace(" ", "T")
|
|
172
|
+
if (isValidLocalDateTimeString(canonicalLocalDateTime)) {
|
|
173
|
+
return canonicalLocalDateTime
|
|
163
174
|
}
|
|
164
|
-
|
|
165
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
138
|
-
case "
|
|
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 === "
|
|
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.
|
|
169
|
-
case "
|
|
159
|
+
return numberAstOf(ast.thunk(), index)
|
|
160
|
+
case "Arrays": {
|
|
170
161
|
const element = ast.elements[index]
|
|
171
162
|
if (element !== undefined) {
|
|
172
|
-
return element
|
|
163
|
+
return element
|
|
173
164
|
}
|
|
174
165
|
if (ast.rest.length === 0) {
|
|
175
166
|
return undefined
|
|
176
167
|
}
|
|
177
|
-
return unionAst(ast.rest
|
|
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
|
-
|
|
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(
|
|
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 "
|
|
244
|
-
case "
|
|
245
|
-
case "
|
|
246
|
-
case "
|
|
247
|
-
case "
|
|
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 ===
|
|
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.
|
|
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.
|
|
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(
|
|
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
|
|
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
|
-
:
|
|
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.
|
|
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
|
|
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.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
|
|
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.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
|
-
|
|
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
|
+
])
|
package/src/internal/scalar.d.ts
CHANGED
|
@@ -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.
|
|
76
|
+
readonly runtimeSchema?: Schema.Top;
|
|
77
77
|
readonly nullability: Nullable;
|
|
78
78
|
readonly dialect: Dialect;
|
|
79
79
|
readonly kind: Kind;
|
package/src/internal/scalar.ts
CHANGED
|
@@ -156,7 +156,7 @@ export interface State<
|
|
|
156
156
|
> {
|
|
157
157
|
readonly runtime: Runtime
|
|
158
158
|
readonly dbType: Db
|
|
159
|
-
readonly runtimeSchema?: Schema.
|
|
159
|
+
readonly runtimeSchema?: Schema.Top
|
|
160
160
|
readonly driverValueMapping?: DriverValueMapping
|
|
161
161
|
readonly nullability: Nullable
|
|
162
162
|
readonly dialect: Dialect
|