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.
Files changed (81) hide show
  1. package/README.md +1294 -0
  2. package/dist/mysql.js +57575 -0
  3. package/dist/postgres.js +6303 -0
  4. package/package.json +42 -0
  5. package/src/internal/aggregation-validation.ts +57 -0
  6. package/src/internal/case-analysis.ts +50 -0
  7. package/src/internal/coercion-analysis.ts +30 -0
  8. package/src/internal/coercion-errors.ts +29 -0
  9. package/src/internal/coercion-kind.ts +32 -0
  10. package/src/internal/coercion-normalize.ts +7 -0
  11. package/src/internal/coercion-rules.ts +25 -0
  12. package/src/internal/column-state.ts +453 -0
  13. package/src/internal/column.ts +417 -0
  14. package/src/internal/datatypes/define.ts +44 -0
  15. package/src/internal/datatypes/lookup.ts +280 -0
  16. package/src/internal/datatypes/shape.ts +72 -0
  17. package/src/internal/derived-table.ts +149 -0
  18. package/src/internal/dialect.ts +30 -0
  19. package/src/internal/executor.ts +390 -0
  20. package/src/internal/expression-ast.ts +349 -0
  21. package/src/internal/expression.ts +325 -0
  22. package/src/internal/grouping-key.ts +82 -0
  23. package/src/internal/json/ast.ts +63 -0
  24. package/src/internal/json/errors.ts +13 -0
  25. package/src/internal/json/path.ts +227 -0
  26. package/src/internal/json/shape.ts +1 -0
  27. package/src/internal/json/types.ts +386 -0
  28. package/src/internal/mysql-dialect.ts +39 -0
  29. package/src/internal/mysql-renderer.ts +37 -0
  30. package/src/internal/plan.ts +64 -0
  31. package/src/internal/postgres-dialect.ts +34 -0
  32. package/src/internal/postgres-renderer.ts +40 -0
  33. package/src/internal/predicate-analysis.ts +71 -0
  34. package/src/internal/predicate-atom.ts +43 -0
  35. package/src/internal/predicate-branches.ts +40 -0
  36. package/src/internal/predicate-context.ts +279 -0
  37. package/src/internal/predicate-formula.ts +100 -0
  38. package/src/internal/predicate-key.ts +28 -0
  39. package/src/internal/predicate-nnf.ts +12 -0
  40. package/src/internal/predicate-normalize.ts +202 -0
  41. package/src/internal/projection-alias.ts +15 -0
  42. package/src/internal/projections.ts +101 -0
  43. package/src/internal/query-ast.ts +297 -0
  44. package/src/internal/query-factory.ts +6757 -0
  45. package/src/internal/query-requirements.ts +40 -0
  46. package/src/internal/query.ts +1590 -0
  47. package/src/internal/renderer.ts +102 -0
  48. package/src/internal/runtime-normalize.ts +344 -0
  49. package/src/internal/runtime-schema.ts +428 -0
  50. package/src/internal/runtime-value.ts +85 -0
  51. package/src/internal/schema-derivation.ts +131 -0
  52. package/src/internal/sql-expression-renderer.ts +1353 -0
  53. package/src/internal/table-options.ts +225 -0
  54. package/src/internal/table.ts +674 -0
  55. package/src/mysql/column.ts +30 -0
  56. package/src/mysql/datatypes/index.ts +6 -0
  57. package/src/mysql/datatypes/spec.ts +180 -0
  58. package/src/mysql/errors/catalog.ts +51662 -0
  59. package/src/mysql/errors/fields.ts +21 -0
  60. package/src/mysql/errors/index.ts +18 -0
  61. package/src/mysql/errors/normalize.ts +232 -0
  62. package/src/mysql/errors/requirements.ts +73 -0
  63. package/src/mysql/executor.ts +134 -0
  64. package/src/mysql/query.ts +189 -0
  65. package/src/mysql/renderer.ts +19 -0
  66. package/src/mysql/table.ts +157 -0
  67. package/src/mysql.ts +18 -0
  68. package/src/postgres/column.ts +20 -0
  69. package/src/postgres/datatypes/index.ts +8 -0
  70. package/src/postgres/datatypes/spec.ts +264 -0
  71. package/src/postgres/errors/catalog.ts +452 -0
  72. package/src/postgres/errors/fields.ts +48 -0
  73. package/src/postgres/errors/index.ts +4 -0
  74. package/src/postgres/errors/normalize.ts +209 -0
  75. package/src/postgres/errors/requirements.ts +65 -0
  76. package/src/postgres/errors/types.ts +38 -0
  77. package/src/postgres/executor.ts +131 -0
  78. package/src/postgres/query.ts +189 -0
  79. package/src/postgres/renderer.ts +29 -0
  80. package/src/postgres/table.ts +157 -0
  81. package/src/postgres.ts +18 -0
@@ -0,0 +1,102 @@
1
+ import * as Query from "./query.js"
2
+ import { type Projection, validateProjections } from "./projections.js"
3
+ import { renderPostgresPlan } from "./postgres-renderer.js"
4
+
5
+ /** Symbol used to attach rendered-query phantom row metadata. */
6
+ export const TypeId: unique symbol = Symbol.for("effect-qb/Renderer")
7
+
8
+ export type TypeId = typeof TypeId
9
+
10
+ /** Column projection metadata emitted by the renderer. */
11
+ export type { Projection }
12
+
13
+ /**
14
+ * Rendered SQL plus phantom row typing.
15
+ *
16
+ * The rendered query exposes the SQL text, parameter values, target dialect,
17
+ * and projection metadata alongside the canonical row type implied by the
18
+ * source query plan.
19
+ */
20
+ export interface RenderedQuery<Row, Dialect extends string = string> {
21
+ readonly sql: string
22
+ readonly params: readonly unknown[]
23
+ readonly dialect: Dialect
24
+ readonly projections: readonly Projection[]
25
+ readonly [TypeId]: {
26
+ readonly row: Row
27
+ readonly dialect: Dialect
28
+ }
29
+ }
30
+
31
+ /** Extracts the row type carried by a rendered query. */
32
+ export type RowOf<Value extends RenderedQuery<any, any>> = Value[typeof TypeId]["row"]
33
+
34
+ /**
35
+ * Public rendering contract.
36
+ *
37
+ * Renderers only accept complete, dialect-compatible plans. The returned
38
+ * `RenderedQuery` keeps the canonical `Query.ResultRow<...>` type attached for
39
+ * downstream executor layers, and the built-in renderer also performs a
40
+ * matching runtime aggregate-shape validation.
41
+ */
42
+ export interface Renderer<Dialect extends string = string> {
43
+ readonly dialect: Dialect
44
+ render<PlanValue extends Query.QueryPlan<any, any, any, any, any, any, any, any, any>>(
45
+ plan: Query.DialectCompatiblePlan<PlanValue, Dialect>
46
+ ): RenderedQuery<any, Dialect>
47
+ }
48
+
49
+ type CustomRender<Dialect extends string> = <PlanValue extends Query.QueryPlan<any, any, any, any, any, any, any, any, any>>(
50
+ plan: Query.DialectCompatiblePlan<PlanValue, Dialect>
51
+ ) => {
52
+ readonly sql: string
53
+ readonly params?: readonly unknown[]
54
+ readonly projections?: readonly Projection[]
55
+ }
56
+
57
+ /**
58
+ * Constructs a renderer from a dialect and optional implementation callback.
59
+ *
60
+ * When no callback is provided, the library supplies a built-in renderer for
61
+ * `"postgres"` that consumes the query AST directly and produces SQL text plus
62
+ * parameter values.
63
+ */
64
+ export function make(dialect: "postgres"): Renderer<"postgres">
65
+ export function make<Dialect extends string>(
66
+ dialect: Dialect,
67
+ render: CustomRender<Dialect>
68
+ ): Renderer<Dialect>
69
+ export function make<Dialect extends string>(
70
+ dialect: Dialect,
71
+ render?: CustomRender<Dialect>
72
+ ): Renderer<Dialect> {
73
+ const implementation = render ?? ((dialect === "postgres"
74
+ ? renderPostgresPlan
75
+ : undefined) as CustomRender<Dialect> | undefined)
76
+
77
+ if (!implementation) {
78
+ throw new Error(`No built-in renderer for dialect: ${dialect}`)
79
+ }
80
+
81
+ return {
82
+ dialect,
83
+ render(plan) {
84
+ const rendered = implementation(plan)
85
+ const projections = rendered.projections ?? []
86
+ validateProjections(projections)
87
+ return {
88
+ sql: rendered.sql,
89
+ params: rendered.params ?? [],
90
+ projections,
91
+ dialect,
92
+ [TypeId]: {
93
+ row: undefined as any,
94
+ dialect
95
+ }
96
+ }
97
+ }
98
+ } as Renderer<Dialect>
99
+ }
100
+
101
+ /** Built-in Postgres renderer backed by the current query AST. */
102
+ export const postgres = make("postgres")
@@ -0,0 +1,344 @@
1
+ import type * as Expression from "./expression.js"
2
+ import { mysqlDatatypeKinds } from "../mysql/datatypes/spec.js"
3
+ import { postgresDatatypeKinds } from "../postgres/datatypes/spec.js"
4
+ import type { RuntimeTag } from "./datatypes/shape.js"
5
+
6
+ const stripParameterizedKind = (kind: string): string => {
7
+ const openParen = kind.indexOf("(")
8
+ return openParen === -1 ? kind : kind.slice(0, openParen)
9
+ }
10
+
11
+ const stripArrayKind = (kind: string): string => {
12
+ let current = kind
13
+ while (current.endsWith("[]")) {
14
+ current = current.slice(0, -2)
15
+ }
16
+ return current
17
+ }
18
+
19
+ const baseKind = (kind: string): string => stripArrayKind(stripParameterizedKind(kind))
20
+
21
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
22
+ typeof value === "object" && value !== null && !Array.isArray(value)
23
+
24
+ const pad = (value: number, width = 2): string => value.toString().padStart(width, "0")
25
+
26
+ const formatLocalDate = (value: Date): string =>
27
+ `${value.getUTCFullYear()}-${pad(value.getUTCMonth() + 1)}-${pad(value.getUTCDate())}`
28
+
29
+ const formatLocalTime = (value: Date): string => {
30
+ const milliseconds = value.getUTCMilliseconds()
31
+ const base = `${pad(value.getUTCHours())}:${pad(value.getUTCMinutes())}:${pad(value.getUTCSeconds())}`
32
+ return milliseconds === 0 ? base : `${base}.${pad(milliseconds, 3)}`
33
+ }
34
+
35
+ const formatLocalDateTime = (value: Date): string => {
36
+ const milliseconds = value.getUTCMilliseconds()
37
+ const base = `${formatLocalDate(value)}T${pad(value.getUTCHours())}:${pad(value.getUTCMinutes())}:${pad(value.getUTCSeconds())}`
38
+ return milliseconds === 0 ? base : `${base}.${pad(milliseconds, 3)}`
39
+ }
40
+
41
+ const runtimeTagOfBaseDbType = (
42
+ dialect: string,
43
+ kind: string
44
+ ): RuntimeTag | undefined => {
45
+ const normalizedKind = baseKind(kind)
46
+ if (dialect === "postgres") {
47
+ return postgresDatatypeKinds[normalizedKind as keyof typeof postgresDatatypeKinds]?.runtime
48
+ }
49
+ if (dialect === "mysql") {
50
+ return mysqlDatatypeKinds[normalizedKind as keyof typeof mysqlDatatypeKinds]?.runtime
51
+ }
52
+ return undefined
53
+ }
54
+
55
+ const expectString = (value: unknown, label: string): string => {
56
+ if (typeof value === "string") {
57
+ return value
58
+ }
59
+ throw new Error(`Expected ${label} as string`)
60
+ }
61
+
62
+ const normalizeNumber = (value: unknown): number => {
63
+ if (typeof value === "number" && Number.isFinite(value)) {
64
+ return value
65
+ }
66
+ if (typeof value === "string" && value.trim() !== "") {
67
+ const parsed = Number(value)
68
+ if (Number.isFinite(parsed)) {
69
+ return parsed
70
+ }
71
+ }
72
+ if (typeof value === "bigint" && Number.isSafeInteger(Number(value))) {
73
+ return Number(value)
74
+ }
75
+ throw new Error("Expected a finite numeric value")
76
+ }
77
+
78
+ const normalizeBoolean = (value: unknown): boolean => {
79
+ if (typeof value === "boolean") {
80
+ return value
81
+ }
82
+ if (typeof value === "number") {
83
+ if (value === 1) {
84
+ return true
85
+ }
86
+ if (value === 0) {
87
+ return false
88
+ }
89
+ }
90
+ if (typeof value === "string") {
91
+ const normalized = value.trim().toLowerCase()
92
+ if (normalized === "true" || normalized === "t" || normalized === "1") {
93
+ return true
94
+ }
95
+ if (normalized === "false" || normalized === "f" || normalized === "0") {
96
+ return false
97
+ }
98
+ }
99
+ throw new Error("Expected a boolean-like value")
100
+ }
101
+
102
+ const normalizeBigIntString = (value: unknown): string => {
103
+ if (typeof value === "bigint") {
104
+ return value.toString()
105
+ }
106
+ if (typeof value === "number" && Number.isSafeInteger(value)) {
107
+ return BigInt(value).toString()
108
+ }
109
+ if (typeof value === "string" && /^-?\d+$/.test(value.trim())) {
110
+ return BigInt(value.trim()).toString()
111
+ }
112
+ throw new Error("Expected an integer-like bigint value")
113
+ }
114
+
115
+ const canonicalizeDecimalString = (input: string): string => {
116
+ const trimmed = input.trim()
117
+ const match = /^([+-]?)(\d+)(?:\.(\d+))?$/.exec(trimmed)
118
+ if (match === null) {
119
+ throw new Error("Expected a decimal string")
120
+ }
121
+ const sign = match[1] === "-" ? "-" : ""
122
+ const integer = match[2]!.replace(/^0+(?=\d)/, "") || "0"
123
+ const fraction = (match[3] ?? "").replace(/0+$/, "")
124
+ if (fraction.length === 0) {
125
+ return `${sign}${integer}`
126
+ }
127
+ return `${sign}${integer}.${fraction}`
128
+ }
129
+
130
+ const normalizeDecimalString = (value: unknown): string => {
131
+ if (typeof value === "string") {
132
+ return canonicalizeDecimalString(value)
133
+ }
134
+ if (typeof value === "number" && Number.isFinite(value)) {
135
+ const rendered = String(value)
136
+ if (/[eE]/.test(rendered)) {
137
+ throw new Error("Scientific notation is not a supported decimal runtime")
138
+ }
139
+ return canonicalizeDecimalString(rendered)
140
+ }
141
+ throw new Error("Expected a decimal-like value")
142
+ }
143
+
144
+ const normalizeLocalDate = (value: unknown): string => {
145
+ if (value instanceof Date) {
146
+ return formatLocalDate(value)
147
+ }
148
+ const raw = expectString(value, "local date").trim()
149
+ if (/^\d{4}-\d{2}-\d{2}$/.test(raw)) {
150
+ return raw
151
+ }
152
+ const parsed = new Date(raw)
153
+ if (!Number.isNaN(parsed.getTime())) {
154
+ return formatLocalDate(parsed)
155
+ }
156
+ throw new Error("Expected a local-date value")
157
+ }
158
+
159
+ const normalizeLocalTime = (value: unknown): string => {
160
+ if (value instanceof Date) {
161
+ return formatLocalTime(value)
162
+ }
163
+ const raw = expectString(value, "local time").trim()
164
+ if (/^\d{2}:\d{2}:\d{2}(?:\.\d+)?$/.test(raw)) {
165
+ return raw
166
+ }
167
+ throw new Error("Expected a local-time value")
168
+ }
169
+
170
+ const normalizeOffsetTime = (value: unknown): string => {
171
+ if (value instanceof Date) {
172
+ return `${formatLocalTime(value)}Z`
173
+ }
174
+ const raw = expectString(value, "offset time").trim()
175
+ if (/^\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(raw)) {
176
+ return raw
177
+ }
178
+ throw new Error("Expected an offset-time value")
179
+ }
180
+
181
+ const normalizeLocalDateTime = (value: unknown): string => {
182
+ if (value instanceof Date) {
183
+ return formatLocalDateTime(value)
184
+ }
185
+ const raw = expectString(value, "local datetime").trim()
186
+ if (/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?$/.test(raw)) {
187
+ return raw.replace(" ", "T")
188
+ }
189
+ if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(raw)) {
190
+ const parsed = new Date(raw)
191
+ if (!Number.isNaN(parsed.getTime())) {
192
+ return formatLocalDateTime(parsed)
193
+ }
194
+ }
195
+ throw new Error("Expected a local-datetime value")
196
+ }
197
+
198
+ const normalizeInstant = (value: unknown): string => {
199
+ if (value instanceof Date) {
200
+ return value.toISOString()
201
+ }
202
+ const raw = expectString(value, "instant").trim()
203
+ if (!/[zZ]|[+-]\d{2}:\d{2}$/.test(raw)) {
204
+ throw new Error("Instant values require a timezone offset")
205
+ }
206
+ const parsed = new Date(raw)
207
+ if (Number.isNaN(parsed.getTime())) {
208
+ throw new Error("Expected an ISO instant value")
209
+ }
210
+ return parsed.toISOString()
211
+ }
212
+
213
+ const normalizeYear = (value: unknown): string => {
214
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 9999) {
215
+ return pad(value, 4)
216
+ }
217
+ const raw = expectString(value, "year").trim()
218
+ if (/^\d{4}$/.test(raw)) {
219
+ return raw
220
+ }
221
+ throw new Error("Expected a four-digit year")
222
+ }
223
+
224
+ const normalizeBytes = (value: unknown): Uint8Array => {
225
+ if (value instanceof Uint8Array) {
226
+ return new Uint8Array(value)
227
+ }
228
+ if (typeof Buffer !== "undefined" && value instanceof Buffer) {
229
+ return new Uint8Array(value)
230
+ }
231
+ throw new Error("Expected a byte array value")
232
+ }
233
+
234
+ const isJsonValue = (value: unknown): boolean => {
235
+ if (value === null) {
236
+ return true
237
+ }
238
+ switch (typeof value) {
239
+ case "string":
240
+ case "number":
241
+ case "boolean":
242
+ return true
243
+ case "object":
244
+ if (Array.isArray(value)) {
245
+ return value.every(isJsonValue)
246
+ }
247
+ return isRecord(value) && Object.values(value).every(isJsonValue)
248
+ default:
249
+ return false
250
+ }
251
+ }
252
+
253
+ const normalizeJson = (value: unknown): unknown => {
254
+ if (typeof value === "string") {
255
+ const parsed = JSON.parse(value)
256
+ if (isJsonValue(parsed)) {
257
+ return parsed
258
+ }
259
+ throw new Error("Parsed JSON value is not a valid JSON runtime")
260
+ }
261
+ if (isJsonValue(value)) {
262
+ return value
263
+ }
264
+ throw new Error("Expected a JSON value")
265
+ }
266
+
267
+ export const normalizeDbValue = (
268
+ dbType: Expression.DbType.Any,
269
+ value: unknown
270
+ ): unknown => {
271
+ if (value === null) {
272
+ return null
273
+ }
274
+ if ("base" in dbType) {
275
+ return normalizeDbValue(dbType.base, value)
276
+ }
277
+ if ("element" in dbType) {
278
+ if (!Array.isArray(value)) {
279
+ throw new Error("Expected an array value")
280
+ }
281
+ return value.map((entry) => normalizeDbValue(dbType.element, entry))
282
+ }
283
+ if ("fields" in dbType) {
284
+ if (!isRecord(value)) {
285
+ throw new Error("Expected a record value")
286
+ }
287
+ const normalized: Record<string, unknown> = {}
288
+ for (const [key, fieldDbType] of Object.entries(dbType.fields)) {
289
+ if (key in value) {
290
+ normalized[key] = normalizeDbValue(fieldDbType, value[key])
291
+ }
292
+ }
293
+ return normalized
294
+ }
295
+ if ("variant" in dbType && dbType.variant === "json") {
296
+ return normalizeJson(value)
297
+ }
298
+ if ("variant" in dbType && (dbType.variant === "enum" || dbType.variant === "set")) {
299
+ return expectString(value, "text")
300
+ }
301
+ switch (runtimeTagOfBaseDbType(dbType.dialect, dbType.kind)) {
302
+ case "string":
303
+ return expectString(value, "text")
304
+ case "number":
305
+ return normalizeNumber(value)
306
+ case "bigintString":
307
+ return normalizeBigIntString(value)
308
+ case "boolean":
309
+ return normalizeBoolean(value)
310
+ case "json":
311
+ return normalizeJson(value)
312
+ case "localDate":
313
+ return normalizeLocalDate(value)
314
+ case "localTime":
315
+ return normalizeLocalTime(value)
316
+ case "offsetTime":
317
+ return normalizeOffsetTime(value)
318
+ case "localDateTime":
319
+ return normalizeLocalDateTime(value)
320
+ case "instant":
321
+ return normalizeInstant(value)
322
+ case "year":
323
+ return normalizeYear(value)
324
+ case "decimalString":
325
+ return normalizeDecimalString(value)
326
+ case "bytes":
327
+ return normalizeBytes(value)
328
+ case "array":
329
+ if (!Array.isArray(value)) {
330
+ throw new Error("Expected an array value")
331
+ }
332
+ return value
333
+ case "record":
334
+ if (!isRecord(value)) {
335
+ throw new Error("Expected a record value")
336
+ }
337
+ return value
338
+ case "null":
339
+ return null
340
+ case "unknown":
341
+ case undefined:
342
+ return value
343
+ }
344
+ }