effect-qb 0.12.3
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 +1294 -0
- package/dist/mysql.js +57575 -0
- package/dist/postgres.js +6303 -0
- package/package.json +42 -0
- package/src/internal/aggregation-validation.ts +57 -0
- package/src/internal/case-analysis.ts +50 -0
- package/src/internal/coercion-analysis.ts +30 -0
- package/src/internal/coercion-errors.ts +29 -0
- package/src/internal/coercion-kind.ts +32 -0
- package/src/internal/coercion-normalize.ts +7 -0
- package/src/internal/coercion-rules.ts +25 -0
- package/src/internal/column-state.ts +453 -0
- package/src/internal/column.ts +417 -0
- package/src/internal/datatypes/define.ts +44 -0
- package/src/internal/datatypes/lookup.ts +280 -0
- package/src/internal/datatypes/shape.ts +72 -0
- package/src/internal/derived-table.ts +149 -0
- package/src/internal/dialect.ts +30 -0
- package/src/internal/executor.ts +390 -0
- package/src/internal/expression-ast.ts +349 -0
- package/src/internal/expression.ts +325 -0
- package/src/internal/grouping-key.ts +82 -0
- package/src/internal/json/ast.ts +63 -0
- package/src/internal/json/errors.ts +13 -0
- package/src/internal/json/path.ts +227 -0
- package/src/internal/json/shape.ts +1 -0
- package/src/internal/json/types.ts +386 -0
- package/src/internal/mysql-dialect.ts +39 -0
- package/src/internal/mysql-renderer.ts +37 -0
- package/src/internal/plan.ts +64 -0
- package/src/internal/postgres-dialect.ts +34 -0
- package/src/internal/postgres-renderer.ts +40 -0
- package/src/internal/predicate-analysis.ts +71 -0
- package/src/internal/predicate-atom.ts +43 -0
- package/src/internal/predicate-branches.ts +40 -0
- package/src/internal/predicate-context.ts +279 -0
- package/src/internal/predicate-formula.ts +100 -0
- package/src/internal/predicate-key.ts +28 -0
- package/src/internal/predicate-nnf.ts +12 -0
- package/src/internal/predicate-normalize.ts +202 -0
- package/src/internal/projection-alias.ts +15 -0
- package/src/internal/projections.ts +101 -0
- package/src/internal/query-ast.ts +297 -0
- package/src/internal/query-factory.ts +6757 -0
- package/src/internal/query-requirements.ts +40 -0
- package/src/internal/query.ts +1590 -0
- package/src/internal/renderer.ts +102 -0
- package/src/internal/runtime-normalize.ts +344 -0
- package/src/internal/runtime-schema.ts +428 -0
- package/src/internal/runtime-value.ts +85 -0
- package/src/internal/schema-derivation.ts +131 -0
- package/src/internal/sql-expression-renderer.ts +1353 -0
- package/src/internal/table-options.ts +225 -0
- package/src/internal/table.ts +674 -0
- package/src/mysql/column.ts +30 -0
- package/src/mysql/datatypes/index.ts +6 -0
- package/src/mysql/datatypes/spec.ts +180 -0
- package/src/mysql/errors/catalog.ts +51662 -0
- package/src/mysql/errors/fields.ts +21 -0
- package/src/mysql/errors/index.ts +18 -0
- package/src/mysql/errors/normalize.ts +232 -0
- package/src/mysql/errors/requirements.ts +73 -0
- package/src/mysql/executor.ts +134 -0
- package/src/mysql/query.ts +189 -0
- package/src/mysql/renderer.ts +19 -0
- package/src/mysql/table.ts +157 -0
- package/src/mysql.ts +18 -0
- package/src/postgres/column.ts +20 -0
- package/src/postgres/datatypes/index.ts +8 -0
- package/src/postgres/datatypes/spec.ts +264 -0
- package/src/postgres/errors/catalog.ts +452 -0
- package/src/postgres/errors/fields.ts +48 -0
- package/src/postgres/errors/index.ts +4 -0
- package/src/postgres/errors/normalize.ts +209 -0
- package/src/postgres/errors/requirements.ts +65 -0
- package/src/postgres/errors/types.ts +38 -0
- package/src/postgres/executor.ts +131 -0
- package/src/postgres/query.ts +189 -0
- package/src/postgres/renderer.ts +29 -0
- package/src/postgres/table.ts +157 -0
- package/src/postgres.ts +18 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema"
|
|
2
|
+
import * as SchemaAST from "effect/SchemaAST"
|
|
3
|
+
|
|
4
|
+
import * as Expression from "./expression.js"
|
|
5
|
+
import * as ExpressionAst from "./expression-ast.js"
|
|
6
|
+
import * as Query from "./query.js"
|
|
7
|
+
import * as JsonPath from "./json/path.js"
|
|
8
|
+
import { flattenSelection } from "./projections.js"
|
|
9
|
+
import {
|
|
10
|
+
BigIntStringSchema,
|
|
11
|
+
DecimalStringSchema,
|
|
12
|
+
InstantStringSchema,
|
|
13
|
+
JsonValueSchema,
|
|
14
|
+
LocalDateStringSchema,
|
|
15
|
+
LocalDateTimeStringSchema,
|
|
16
|
+
LocalTimeStringSchema,
|
|
17
|
+
OffsetTimeStringSchema,
|
|
18
|
+
YearStringSchema
|
|
19
|
+
} from "./runtime-value.js"
|
|
20
|
+
import { mysqlDatatypeKinds } from "../mysql/datatypes/spec.js"
|
|
21
|
+
import { postgresDatatypeKinds } from "../postgres/datatypes/spec.js"
|
|
22
|
+
import type { RuntimeTag } from "./datatypes/shape.js"
|
|
23
|
+
|
|
24
|
+
export type RuntimeSchema = Schema.Schema<any, any, any>
|
|
25
|
+
|
|
26
|
+
const schemaCache = new WeakMap<Expression.Any, RuntimeSchema | undefined>()
|
|
27
|
+
|
|
28
|
+
const stripParameterizedKind = (kind: string): string => {
|
|
29
|
+
const openParen = kind.indexOf("(")
|
|
30
|
+
return openParen === -1 ? kind : kind.slice(0, openParen)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const stripArrayKind = (kind: string): string => {
|
|
34
|
+
let current = kind
|
|
35
|
+
while (current.endsWith("[]")) {
|
|
36
|
+
current = current.slice(0, -2)
|
|
37
|
+
}
|
|
38
|
+
return current
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const baseKind = (kind: string): string => stripArrayKind(stripParameterizedKind(kind))
|
|
42
|
+
|
|
43
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
44
|
+
typeof value === "object" && value !== null && !Array.isArray(value)
|
|
45
|
+
|
|
46
|
+
const runtimeSchemaForTag = (tag: RuntimeTag): RuntimeSchema | undefined => {
|
|
47
|
+
switch (tag) {
|
|
48
|
+
case "string":
|
|
49
|
+
return Schema.String
|
|
50
|
+
case "number":
|
|
51
|
+
return Schema.Number
|
|
52
|
+
case "bigintString":
|
|
53
|
+
return BigIntStringSchema
|
|
54
|
+
case "boolean":
|
|
55
|
+
return Schema.Boolean
|
|
56
|
+
case "json":
|
|
57
|
+
return JsonValueSchema
|
|
58
|
+
case "localDate":
|
|
59
|
+
return LocalDateStringSchema
|
|
60
|
+
case "localTime":
|
|
61
|
+
return LocalTimeStringSchema
|
|
62
|
+
case "offsetTime":
|
|
63
|
+
return OffsetTimeStringSchema
|
|
64
|
+
case "localDateTime":
|
|
65
|
+
return LocalDateTimeStringSchema
|
|
66
|
+
case "instant":
|
|
67
|
+
return InstantStringSchema
|
|
68
|
+
case "year":
|
|
69
|
+
return YearStringSchema
|
|
70
|
+
case "decimalString":
|
|
71
|
+
return DecimalStringSchema
|
|
72
|
+
case "bytes":
|
|
73
|
+
return Schema.Uint8ArrayFromSelf
|
|
74
|
+
case "array":
|
|
75
|
+
return Schema.Array(Schema.Unknown)
|
|
76
|
+
case "record":
|
|
77
|
+
return Schema.Record({
|
|
78
|
+
key: Schema.String,
|
|
79
|
+
value: Schema.Unknown
|
|
80
|
+
})
|
|
81
|
+
case "null":
|
|
82
|
+
return Schema.Null
|
|
83
|
+
case "unknown":
|
|
84
|
+
return undefined
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const runtimeTagOfBaseDbType = (
|
|
89
|
+
dialect: string,
|
|
90
|
+
kind: string
|
|
91
|
+
): RuntimeTag | undefined => {
|
|
92
|
+
const normalizedKind = baseKind(kind)
|
|
93
|
+
if (dialect === "postgres") {
|
|
94
|
+
return postgresDatatypeKinds[normalizedKind as keyof typeof postgresDatatypeKinds]?.runtime
|
|
95
|
+
}
|
|
96
|
+
if (dialect === "mysql") {
|
|
97
|
+
return mysqlDatatypeKinds[normalizedKind as keyof typeof mysqlDatatypeKinds]?.runtime
|
|
98
|
+
}
|
|
99
|
+
return undefined
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const runtimeSchemaForDbType = (
|
|
103
|
+
dbType: Expression.DbType.Any
|
|
104
|
+
): RuntimeSchema | undefined => {
|
|
105
|
+
if ("base" in dbType) {
|
|
106
|
+
return runtimeSchemaForDbType(dbType.base)
|
|
107
|
+
}
|
|
108
|
+
if ("element" in dbType) {
|
|
109
|
+
return Schema.Array(runtimeSchemaForDbType(dbType.element) ?? Schema.Unknown)
|
|
110
|
+
}
|
|
111
|
+
if ("fields" in dbType) {
|
|
112
|
+
const fields = Object.fromEntries(
|
|
113
|
+
Object.entries(dbType.fields).map(([key, field]) => [key, runtimeSchemaForDbType(field) ?? Schema.Unknown])
|
|
114
|
+
)
|
|
115
|
+
return Schema.Struct(fields as Record<string, RuntimeSchema>)
|
|
116
|
+
}
|
|
117
|
+
if ("variant" in dbType && dbType.variant === "json") {
|
|
118
|
+
return JsonValueSchema
|
|
119
|
+
}
|
|
120
|
+
if ("variant" in dbType && (dbType.variant === "enum" || dbType.variant === "set")) {
|
|
121
|
+
return Schema.String
|
|
122
|
+
}
|
|
123
|
+
const runtimeTag = runtimeTagOfBaseDbType(dbType.dialect, dbType.kind)
|
|
124
|
+
return runtimeTag === undefined ? undefined : runtimeSchemaForTag(runtimeTag)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const makeSchemaFromAst = (ast: SchemaAST.AST): RuntimeSchema =>
|
|
128
|
+
Schema.make(ast)
|
|
129
|
+
|
|
130
|
+
const unionAst = (asts: ReadonlyArray<SchemaAST.AST>): SchemaAST.AST | undefined => {
|
|
131
|
+
if (asts.length === 0) {
|
|
132
|
+
return undefined
|
|
133
|
+
}
|
|
134
|
+
if (asts.length === 1) {
|
|
135
|
+
return asts[0]
|
|
136
|
+
}
|
|
137
|
+
return SchemaAST.Union.make(asts)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const propertyAstOf = (
|
|
141
|
+
ast: SchemaAST.AST,
|
|
142
|
+
key: string
|
|
143
|
+
): SchemaAST.AST | undefined => {
|
|
144
|
+
switch (ast._tag) {
|
|
145
|
+
case "Transformation":
|
|
146
|
+
return propertyAstOf(SchemaAST.typeAST(ast), key)
|
|
147
|
+
case "Refinement":
|
|
148
|
+
return propertyAstOf(ast.from, key)
|
|
149
|
+
case "Suspend":
|
|
150
|
+
return propertyAstOf(ast.f(), key)
|
|
151
|
+
case "TypeLiteral": {
|
|
152
|
+
const property = ast.propertySignatures.find((entry) => entry.name === key)
|
|
153
|
+
if (property !== undefined) {
|
|
154
|
+
return property.type
|
|
155
|
+
}
|
|
156
|
+
const index = ast.indexSignatures.find((entry) => entry.parameter._tag === "StringKeyword")
|
|
157
|
+
return index?.type
|
|
158
|
+
}
|
|
159
|
+
case "Union": {
|
|
160
|
+
const values = ast.types.flatMap((member) => {
|
|
161
|
+
const next = propertyAstOf(member, key)
|
|
162
|
+
return next === undefined ? [] : [next]
|
|
163
|
+
})
|
|
164
|
+
return unionAst(values)
|
|
165
|
+
}
|
|
166
|
+
default:
|
|
167
|
+
return undefined
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const numberAstOf = (
|
|
172
|
+
ast: SchemaAST.AST,
|
|
173
|
+
index: number
|
|
174
|
+
): SchemaAST.AST | undefined => {
|
|
175
|
+
switch (ast._tag) {
|
|
176
|
+
case "Transformation":
|
|
177
|
+
return numberAstOf(SchemaAST.typeAST(ast), index)
|
|
178
|
+
case "Refinement":
|
|
179
|
+
return numberAstOf(ast.from, index)
|
|
180
|
+
case "Suspend":
|
|
181
|
+
return numberAstOf(ast.f(), index)
|
|
182
|
+
case "TupleType": {
|
|
183
|
+
const element = ast.elements[index]
|
|
184
|
+
if (element !== undefined) {
|
|
185
|
+
return element.type
|
|
186
|
+
}
|
|
187
|
+
if (ast.rest.length === 0) {
|
|
188
|
+
return undefined
|
|
189
|
+
}
|
|
190
|
+
return unionAst(ast.rest.map((entry) => entry.type))
|
|
191
|
+
}
|
|
192
|
+
case "Union": {
|
|
193
|
+
const values = ast.types.flatMap((member) => {
|
|
194
|
+
const next = numberAstOf(member, index)
|
|
195
|
+
return next === undefined ? [] : [next]
|
|
196
|
+
})
|
|
197
|
+
return unionAst(values)
|
|
198
|
+
}
|
|
199
|
+
default:
|
|
200
|
+
return undefined
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const exactJsonSegments = (
|
|
205
|
+
segments: readonly JsonPath.CanonicalSegment[]
|
|
206
|
+
): segments is readonly (JsonPath.KeySegment | JsonPath.IndexSegment)[] =>
|
|
207
|
+
segments.every((segment) => segment.kind === "key" || segment.kind === "index")
|
|
208
|
+
|
|
209
|
+
const schemaAstAtExactJsonPath = (
|
|
210
|
+
schema: RuntimeSchema,
|
|
211
|
+
segments: readonly JsonPath.CanonicalSegment[]
|
|
212
|
+
): SchemaAST.AST | undefined => {
|
|
213
|
+
let current: SchemaAST.AST = SchemaAST.typeAST(schema.ast)
|
|
214
|
+
for (const segment of segments) {
|
|
215
|
+
if (segment.kind === "key") {
|
|
216
|
+
const property = propertyAstOf(current, segment.key)
|
|
217
|
+
if (property === undefined) {
|
|
218
|
+
return undefined
|
|
219
|
+
}
|
|
220
|
+
current = property
|
|
221
|
+
continue
|
|
222
|
+
}
|
|
223
|
+
if (segment.kind === "index") {
|
|
224
|
+
const next = numberAstOf(current, segment.index)
|
|
225
|
+
if (next === undefined) {
|
|
226
|
+
return undefined
|
|
227
|
+
}
|
|
228
|
+
current = next
|
|
229
|
+
continue
|
|
230
|
+
}
|
|
231
|
+
return undefined
|
|
232
|
+
}
|
|
233
|
+
return current
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const unionSchemas = (schemas: ReadonlyArray<RuntimeSchema | undefined>): RuntimeSchema | undefined => {
|
|
237
|
+
const resolved = schemas.filter((schema): schema is RuntimeSchema => schema !== undefined)
|
|
238
|
+
if (resolved.length === 0) {
|
|
239
|
+
return undefined
|
|
240
|
+
}
|
|
241
|
+
if (resolved.length === 1) {
|
|
242
|
+
return resolved[0]
|
|
243
|
+
}
|
|
244
|
+
return Schema.Union(...resolved)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const firstSelectedExpression = (
|
|
248
|
+
plan: Query.QueryPlan<any, any, any, any, any, any, any, any, any, any>
|
|
249
|
+
): Expression.Any | undefined => {
|
|
250
|
+
const selection = Query.getAst(plan).select
|
|
251
|
+
return flattenSelection(selection as Record<string, unknown>)[0]?.expression
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const isJsonCompatibleAst = (ast: SchemaAST.AST): boolean => {
|
|
255
|
+
switch (ast._tag) {
|
|
256
|
+
case "StringKeyword":
|
|
257
|
+
case "NumberKeyword":
|
|
258
|
+
case "BooleanKeyword":
|
|
259
|
+
case "TupleType":
|
|
260
|
+
case "TypeLiteral":
|
|
261
|
+
return true
|
|
262
|
+
case "Literal":
|
|
263
|
+
return ast.literal === null ||
|
|
264
|
+
typeof ast.literal === "string" ||
|
|
265
|
+
typeof ast.literal === "number" ||
|
|
266
|
+
typeof ast.literal === "boolean"
|
|
267
|
+
case "Union":
|
|
268
|
+
return ast.types.every(isJsonCompatibleAst)
|
|
269
|
+
case "Transformation":
|
|
270
|
+
return isJsonCompatibleAst(SchemaAST.typeAST(ast))
|
|
271
|
+
case "Suspend":
|
|
272
|
+
return isJsonCompatibleAst(ast.f())
|
|
273
|
+
default:
|
|
274
|
+
return false
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const jsonCompatibleSchema = (schema: RuntimeSchema | undefined): RuntimeSchema | undefined => {
|
|
279
|
+
if (schema === undefined) {
|
|
280
|
+
return undefined
|
|
281
|
+
}
|
|
282
|
+
const ast = SchemaAST.typeAST(schema.ast)
|
|
283
|
+
return isJsonCompatibleAst(ast) ? schema : JsonValueSchema
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const buildStructSchema = (
|
|
287
|
+
entries: readonly { readonly key: string; readonly value: Expression.Any }[]
|
|
288
|
+
): RuntimeSchema => {
|
|
289
|
+
const fields = Object.fromEntries(
|
|
290
|
+
entries.map((entry) => [entry.key, expressionRuntimeSchema(entry.value) ?? JsonValueSchema])
|
|
291
|
+
)
|
|
292
|
+
return Schema.Struct(fields as Record<string, RuntimeSchema>)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const buildTupleSchema = (values: readonly Expression.Any[]): RuntimeSchema =>
|
|
296
|
+
Schema.Tuple(...values.map((value) => expressionRuntimeSchema(value) ?? JsonValueSchema))
|
|
297
|
+
|
|
298
|
+
const deriveRuntimeSchema = (expression: Expression.Any): RuntimeSchema | undefined => {
|
|
299
|
+
const state = expression[Expression.TypeId]
|
|
300
|
+
if (state.runtimeSchema !== undefined) {
|
|
301
|
+
return state.runtimeSchema
|
|
302
|
+
}
|
|
303
|
+
const ast = (expression as Expression.Any & {
|
|
304
|
+
readonly [ExpressionAst.TypeId]: ExpressionAst.Any
|
|
305
|
+
})[ExpressionAst.TypeId]
|
|
306
|
+
switch (ast.kind) {
|
|
307
|
+
case "column":
|
|
308
|
+
case "excluded":
|
|
309
|
+
return state.runtimeSchema
|
|
310
|
+
case "literal":
|
|
311
|
+
if (ast.value === null) {
|
|
312
|
+
return Schema.Null
|
|
313
|
+
}
|
|
314
|
+
if (typeof ast.value === "string" || typeof ast.value === "number" || typeof ast.value === "boolean") {
|
|
315
|
+
return Schema.Literal(ast.value)
|
|
316
|
+
}
|
|
317
|
+
return runtimeSchemaForDbType(state.dbType)
|
|
318
|
+
case "cast":
|
|
319
|
+
return runtimeSchemaForDbType(ast.target)
|
|
320
|
+
case "isNull":
|
|
321
|
+
case "isNotNull":
|
|
322
|
+
case "not":
|
|
323
|
+
case "eq":
|
|
324
|
+
case "neq":
|
|
325
|
+
case "lt":
|
|
326
|
+
case "lte":
|
|
327
|
+
case "gt":
|
|
328
|
+
case "gte":
|
|
329
|
+
case "like":
|
|
330
|
+
case "ilike":
|
|
331
|
+
case "isDistinctFrom":
|
|
332
|
+
case "isNotDistinctFrom":
|
|
333
|
+
case "contains":
|
|
334
|
+
case "containedBy":
|
|
335
|
+
case "overlaps":
|
|
336
|
+
case "and":
|
|
337
|
+
case "or":
|
|
338
|
+
case "in":
|
|
339
|
+
case "notIn":
|
|
340
|
+
case "between":
|
|
341
|
+
case "exists":
|
|
342
|
+
case "inSubquery":
|
|
343
|
+
case "comparisonAny":
|
|
344
|
+
case "comparisonAll":
|
|
345
|
+
case "jsonHasKey":
|
|
346
|
+
case "jsonKeyExists":
|
|
347
|
+
case "jsonHasAnyKeys":
|
|
348
|
+
case "jsonHasAllKeys":
|
|
349
|
+
case "jsonPathExists":
|
|
350
|
+
case "jsonPathMatch":
|
|
351
|
+
return Schema.Boolean
|
|
352
|
+
case "upper":
|
|
353
|
+
case "lower":
|
|
354
|
+
case "concat":
|
|
355
|
+
case "jsonGetText":
|
|
356
|
+
case "jsonPathText":
|
|
357
|
+
case "jsonAccessText":
|
|
358
|
+
case "jsonTraverseText":
|
|
359
|
+
case "jsonTypeOf":
|
|
360
|
+
return Schema.String
|
|
361
|
+
case "count":
|
|
362
|
+
case "jsonLength":
|
|
363
|
+
return Schema.Number
|
|
364
|
+
case "max":
|
|
365
|
+
case "min":
|
|
366
|
+
return expressionRuntimeSchema(ast.value)
|
|
367
|
+
case "case":
|
|
368
|
+
return unionSchemas([
|
|
369
|
+
...ast.branches.map((branch) => expressionRuntimeSchema(branch.then)),
|
|
370
|
+
expressionRuntimeSchema(ast.else)
|
|
371
|
+
])
|
|
372
|
+
case "coalesce":
|
|
373
|
+
return unionSchemas(ast.values.map(expressionRuntimeSchema))
|
|
374
|
+
case "scalarSubquery":
|
|
375
|
+
{
|
|
376
|
+
const selection = firstSelectedExpression(ast.plan)
|
|
377
|
+
return selection === undefined ? undefined : expressionRuntimeSchema(selection)
|
|
378
|
+
}
|
|
379
|
+
case "window":
|
|
380
|
+
return ast.function === "over" && ast.value !== undefined
|
|
381
|
+
? expressionRuntimeSchema(ast.value)
|
|
382
|
+
: Schema.Number
|
|
383
|
+
case "jsonGet":
|
|
384
|
+
case "jsonPath":
|
|
385
|
+
case "jsonAccess":
|
|
386
|
+
case "jsonTraverse": {
|
|
387
|
+
const baseSchema = expressionRuntimeSchema(ast.base!)
|
|
388
|
+
const segments = ast.segments
|
|
389
|
+
if (baseSchema === undefined || segments === undefined || !exactJsonSegments(segments)) {
|
|
390
|
+
return JsonValueSchema
|
|
391
|
+
}
|
|
392
|
+
const subAst = schemaAstAtExactJsonPath(baseSchema, segments)
|
|
393
|
+
return subAst === undefined ? JsonValueSchema : makeSchemaFromAst(subAst)
|
|
394
|
+
}
|
|
395
|
+
case "jsonDelete":
|
|
396
|
+
case "jsonDeletePath":
|
|
397
|
+
case "jsonRemove":
|
|
398
|
+
case "jsonSet":
|
|
399
|
+
case "jsonInsert":
|
|
400
|
+
return expressionRuntimeSchema(ast.base!)
|
|
401
|
+
case "jsonStripNulls":
|
|
402
|
+
return expressionRuntimeSchema(ast.value!)
|
|
403
|
+
case "jsonConcat":
|
|
404
|
+
case "jsonMerge":
|
|
405
|
+
return JsonValueSchema
|
|
406
|
+
case "jsonBuildObject":
|
|
407
|
+
return buildStructSchema(ast.entries ?? [])
|
|
408
|
+
case "jsonBuildArray":
|
|
409
|
+
return buildTupleSchema(ast.values ?? [])
|
|
410
|
+
case "jsonToJson":
|
|
411
|
+
case "jsonToJsonb":
|
|
412
|
+
return jsonCompatibleSchema(expressionRuntimeSchema(ast.value!))
|
|
413
|
+
case "jsonKeys":
|
|
414
|
+
return Schema.Array(Schema.String)
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export const expressionRuntimeSchema = (
|
|
419
|
+
expression: Expression.Any
|
|
420
|
+
): RuntimeSchema | undefined => {
|
|
421
|
+
const cached = schemaCache.get(expression)
|
|
422
|
+
if (cached !== undefined || schemaCache.has(expression)) {
|
|
423
|
+
return cached
|
|
424
|
+
}
|
|
425
|
+
const resolved = deriveRuntimeSchema(expression) ?? runtimeSchemaForDbType(expression[Expression.TypeId].dbType)
|
|
426
|
+
schemaCache.set(expression, resolved)
|
|
427
|
+
return resolved
|
|
428
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type * as Brand from "effect/Brand"
|
|
2
|
+
import * as Schema from "effect/Schema"
|
|
3
|
+
|
|
4
|
+
import type { JsonPrimitive, JsonValue } from "./json/types.js"
|
|
5
|
+
|
|
6
|
+
export type { JsonPrimitive, JsonValue } from "./json/types.js"
|
|
7
|
+
|
|
8
|
+
export type LocalDateString = string & Brand.Brand<"LocalDateString">
|
|
9
|
+
export type LocalTimeString = string & Brand.Brand<"LocalTimeString">
|
|
10
|
+
export type OffsetTimeString = string & Brand.Brand<"OffsetTimeString">
|
|
11
|
+
export type LocalDateTimeString = string & Brand.Brand<"LocalDateTimeString">
|
|
12
|
+
export type InstantString = string & Brand.Brand<"InstantString">
|
|
13
|
+
export type YearString = string & Brand.Brand<"YearString">
|
|
14
|
+
export type BigIntString = string & Brand.Brand<"BigIntString">
|
|
15
|
+
export type DecimalString = string & Brand.Brand<"DecimalString">
|
|
16
|
+
|
|
17
|
+
const brandString = <BrandName extends string>(
|
|
18
|
+
pattern: RegExp,
|
|
19
|
+
brand: BrandName
|
|
20
|
+
): Schema.Schema<string & Brand.Brand<BrandName>> =>
|
|
21
|
+
Schema.String.pipe(
|
|
22
|
+
Schema.pattern(pattern),
|
|
23
|
+
Schema.brand(brand)
|
|
24
|
+
) as unknown as Schema.Schema<string & Brand.Brand<BrandName>>
|
|
25
|
+
|
|
26
|
+
export const LocalDateStringSchema = brandString(
|
|
27
|
+
/^\d{4}-\d{2}-\d{2}$/,
|
|
28
|
+
"LocalDateString"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
export const LocalTimeStringSchema = brandString(
|
|
32
|
+
/^\d{2}:\d{2}:\d{2}(?:\.\d+)?$/,
|
|
33
|
+
"LocalTimeString"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
export const OffsetTimeStringSchema = brandString(
|
|
37
|
+
/^\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/,
|
|
38
|
+
"OffsetTimeString"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
export const LocalDateTimeStringSchema = brandString(
|
|
42
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?$/,
|
|
43
|
+
"LocalDateTimeString"
|
|
44
|
+
)
|
|
45
|
+
|
|
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
|
+
)
|
|
50
|
+
|
|
51
|
+
export const YearStringSchema = brandString(
|
|
52
|
+
/^\d{4}$/,
|
|
53
|
+
"YearString"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
export const BigIntStringSchema = brandString(
|
|
57
|
+
/^-?\d+$/,
|
|
58
|
+
"BigIntString"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
export const DecimalStringSchema = brandString(
|
|
62
|
+
/^-?(?:0|[1-9]\d*)(?:\.\d+)?$/,
|
|
63
|
+
"DecimalString"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
export const JsonValueSchema: Schema.Schema<JsonValue> = Schema.suspend(() =>
|
|
67
|
+
Schema.Union(
|
|
68
|
+
Schema.String,
|
|
69
|
+
Schema.Number,
|
|
70
|
+
Schema.Boolean,
|
|
71
|
+
Schema.Null,
|
|
72
|
+
Schema.Array(JsonValueSchema),
|
|
73
|
+
Schema.Record({
|
|
74
|
+
key: Schema.String,
|
|
75
|
+
value: JsonValueSchema
|
|
76
|
+
})
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
export const JsonPrimitiveSchema: Schema.Schema<JsonPrimitive> = Schema.Union(
|
|
81
|
+
Schema.String,
|
|
82
|
+
Schema.Number,
|
|
83
|
+
Schema.Boolean,
|
|
84
|
+
Schema.Null
|
|
85
|
+
)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import * as VariantSchema from "@effect/experimental/VariantSchema"
|
|
2
|
+
import * as Schema from "effect/Schema"
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
type AnyColumnDefinition,
|
|
6
|
+
type HasDefault,
|
|
7
|
+
type InsertType,
|
|
8
|
+
type IsGenerated,
|
|
9
|
+
type IsNullable,
|
|
10
|
+
type SelectType,
|
|
11
|
+
type UpdateType
|
|
12
|
+
} from "./column-state.js"
|
|
13
|
+
|
|
14
|
+
/** Variant-schema helper used to derive select / insert / update schemas. */
|
|
15
|
+
export const TableSchema = VariantSchema.make({
|
|
16
|
+
variants: ["select", "insert", "update"] as const,
|
|
17
|
+
defaultVariant: "select"
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
type Variants = "select" | "insert" | "update"
|
|
21
|
+
|
|
22
|
+
/** Normalized field map used by table definitions. */
|
|
23
|
+
export type TableFieldMap = Record<string, AnyColumnDefinition>
|
|
24
|
+
|
|
25
|
+
type GeneratedKeys<Fields extends TableFieldMap> = {
|
|
26
|
+
[K in keyof Fields]: IsGenerated<Fields[K]> extends true ? K : never
|
|
27
|
+
}[keyof Fields]
|
|
28
|
+
|
|
29
|
+
type OptionalInsertKeys<Fields extends TableFieldMap> = {
|
|
30
|
+
[K in keyof Fields]:
|
|
31
|
+
IsGenerated<Fields[K]> extends true ? never :
|
|
32
|
+
IsNullable<Fields[K]> extends true ? K :
|
|
33
|
+
HasDefault<Fields[K]> extends true ? K :
|
|
34
|
+
never
|
|
35
|
+
}[keyof Fields]
|
|
36
|
+
|
|
37
|
+
type RequiredInsertKeys<Fields extends TableFieldMap> = Exclude<keyof Fields, GeneratedKeys<Fields> | OptionalInsertKeys<Fields>>
|
|
38
|
+
|
|
39
|
+
type UpdateKeys<Fields extends TableFieldMap, PrimaryKey extends keyof Fields> = Exclude<
|
|
40
|
+
keyof Fields,
|
|
41
|
+
GeneratedKeys<Fields> | PrimaryKey
|
|
42
|
+
>
|
|
43
|
+
|
|
44
|
+
type Simplify<T> = { [K in keyof T]: T[K] } & {}
|
|
45
|
+
|
|
46
|
+
/** Row shape returned by selecting from a table. */
|
|
47
|
+
export type SelectRow<Fields extends TableFieldMap> = Simplify<{
|
|
48
|
+
[K in keyof Fields]: SelectType<Fields[K]>
|
|
49
|
+
}>
|
|
50
|
+
|
|
51
|
+
/** Insert payload derived from a table field map. */
|
|
52
|
+
export type InsertRow<Fields extends TableFieldMap> = Simplify<
|
|
53
|
+
{ [K in RequiredInsertKeys<Fields>]: InsertType<Fields[K]> } &
|
|
54
|
+
{ [K in OptionalInsertKeys<Fields>]?: InsertType<Fields[K]> }
|
|
55
|
+
>
|
|
56
|
+
|
|
57
|
+
/** Update payload derived from a table field map and primary key. */
|
|
58
|
+
export type UpdateRow<Fields extends TableFieldMap, PrimaryKey extends keyof Fields> = Simplify<
|
|
59
|
+
Partial<{
|
|
60
|
+
[K in UpdateKeys<Fields, PrimaryKey>]: UpdateType<Fields[K]>
|
|
61
|
+
}>
|
|
62
|
+
>
|
|
63
|
+
|
|
64
|
+
const selectSchema = (column: AnyColumnDefinition): Schema.Schema.Any =>
|
|
65
|
+
column.metadata.nullable ? Schema.NullOr(column.schema) : column.schema
|
|
66
|
+
|
|
67
|
+
const insertSchema = (column: AnyColumnDefinition): any | undefined => {
|
|
68
|
+
if (column.metadata.generated) {
|
|
69
|
+
return undefined
|
|
70
|
+
}
|
|
71
|
+
const base = column.metadata.nullable ? Schema.NullOr(column.schema) : column.schema
|
|
72
|
+
return column.metadata.nullable || column.metadata.hasDefault ? Schema.optional(base) : base
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const updateSchema = (
|
|
76
|
+
column: AnyColumnDefinition,
|
|
77
|
+
isPrimaryKey: boolean
|
|
78
|
+
): any | undefined => {
|
|
79
|
+
if (column.metadata.generated || isPrimaryKey) {
|
|
80
|
+
return undefined
|
|
81
|
+
}
|
|
82
|
+
const base = column.metadata.nullable ? Schema.NullOr(column.schema) : column.schema
|
|
83
|
+
return Schema.optional(base)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Derives the `select`, `insert`, and `update` schemas for a table.
|
|
88
|
+
*
|
|
89
|
+
* This is the central place where the column capability flags are turned into
|
|
90
|
+
* real runtime schemas.
|
|
91
|
+
*/
|
|
92
|
+
export const deriveSchemas = <
|
|
93
|
+
Fields extends TableFieldMap,
|
|
94
|
+
PrimaryKeyColumns extends keyof Fields & string
|
|
95
|
+
>(
|
|
96
|
+
fields: Fields,
|
|
97
|
+
primaryKeyColumns: readonly PrimaryKeyColumns[]
|
|
98
|
+
): {
|
|
99
|
+
readonly select: Schema.Schema<SelectRow<Fields>>
|
|
100
|
+
readonly insert: Schema.Schema<InsertRow<Fields>>
|
|
101
|
+
readonly update: Schema.Schema<UpdateRow<Fields, PrimaryKeyColumns>>
|
|
102
|
+
} => {
|
|
103
|
+
const primaryKeySet = new Set<string>(primaryKeyColumns)
|
|
104
|
+
const variants: Record<string, VariantSchema.Field<any>> = {}
|
|
105
|
+
for (const [key, column] of Object.entries(fields)) {
|
|
106
|
+
const config: Record<Variants, any> = {
|
|
107
|
+
select: selectSchema(column),
|
|
108
|
+
insert: undefined,
|
|
109
|
+
update: undefined
|
|
110
|
+
}
|
|
111
|
+
const insert = insertSchema(column)
|
|
112
|
+
const update = updateSchema(column, primaryKeySet.has(key))
|
|
113
|
+
if (insert !== undefined) {
|
|
114
|
+
config.insert = insert
|
|
115
|
+
} else {
|
|
116
|
+
delete config.insert
|
|
117
|
+
}
|
|
118
|
+
if (update !== undefined) {
|
|
119
|
+
config.update = update
|
|
120
|
+
} else {
|
|
121
|
+
delete config.update
|
|
122
|
+
}
|
|
123
|
+
variants[key] = TableSchema.Field(config)
|
|
124
|
+
}
|
|
125
|
+
const struct = TableSchema.Struct(variants as any)
|
|
126
|
+
return {
|
|
127
|
+
select: TableSchema.extract(struct, "select") as unknown as Schema.Schema<SelectRow<Fields>>,
|
|
128
|
+
insert: TableSchema.extract(struct, "insert") as unknown as Schema.Schema<InsertRow<Fields>>,
|
|
129
|
+
update: TableSchema.extract(struct, "update") as unknown as Schema.Schema<UpdateRow<Fields, PrimaryKeyColumns>>
|
|
130
|
+
}
|
|
131
|
+
}
|