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,390 @@
1
+ import * as Effect from "effect/Effect"
2
+ import * as Schema from "effect/Schema"
3
+ import * as SqlClient from "@effect/sql/SqlClient"
4
+ import * as SqlError from "@effect/sql/SqlError"
5
+
6
+ import * as Expression from "./expression.js"
7
+ import { normalizeDbValue } from "./runtime-normalize.js"
8
+ import { expressionRuntimeSchema } from "./runtime-schema.js"
9
+ import { flattenSelection } from "./projections.js"
10
+ import * as Query from "./query.js"
11
+ import * as QueryAst from "./query-ast.js"
12
+ import * as Renderer from "./renderer.js"
13
+ import * as Plan from "./plan.js"
14
+
15
+ /** Flat database row keyed by rendered projection aliases. */
16
+ export type FlatRow = Readonly<Record<string, unknown>>
17
+ export type DriverMode = "raw" | "normalized"
18
+
19
+ export interface RowDecodeError {
20
+ readonly _tag: "RowDecodeError"
21
+ readonly message: string
22
+ readonly dialect: string
23
+ readonly query?: {
24
+ readonly sql: string
25
+ readonly params: ReadonlyArray<unknown>
26
+ }
27
+ readonly projection: {
28
+ readonly alias: string
29
+ readonly path: readonly string[]
30
+ }
31
+ readonly dbType: Expression.DbType.Any
32
+ readonly raw: unknown
33
+ readonly normalized?: unknown
34
+ readonly stage: "normalize" | "schema"
35
+ readonly cause: unknown
36
+ }
37
+
38
+ /**
39
+ * Driver that executes already-rendered SQL.
40
+ *
41
+ * Drivers operate on rendered SQL plus projection metadata and return flat
42
+ * alias-keyed rows. Executors then normalize raw driver values into the
43
+ * canonical runtime contract, validate them against runtime schemas, and remap
44
+ * aliases back into the nested result shape.
45
+ */
46
+ export interface Driver<
47
+ Dialect extends string = string,
48
+ Error = never,
49
+ Context = never
50
+ > {
51
+ readonly dialect: Dialect
52
+ execute<Row>(
53
+ query: Renderer.RenderedQuery<Row, Dialect>
54
+ ): Effect.Effect<ReadonlyArray<FlatRow>, Error, Context>
55
+ }
56
+
57
+ /**
58
+ * Public execution contract.
59
+ *
60
+ * Executors only accept complete, dialect-compatible plans. Successful
61
+ * execution yields the compile-time query result contract after canonical
62
+ * scalar normalization plus runtime schema validation.
63
+ */
64
+ export interface Executor<
65
+ Dialect extends string = string,
66
+ Error = never,
67
+ Context = never
68
+ > {
69
+ readonly dialect: Dialect
70
+ execute<PlanValue extends Query.QueryPlan<any, any, any, any, any, any, any, any, any>>(
71
+ plan: Query.DialectCompatiblePlan<PlanValue, Dialect>
72
+ ): Effect.Effect<Query.ResultRows<PlanValue>, Error, Context>
73
+ }
74
+
75
+ const setPath = (
76
+ target: Record<string, unknown>,
77
+ path: readonly string[],
78
+ value: unknown
79
+ ): void => {
80
+ let current = target
81
+ for (let index = 0; index < path.length - 1; index++) {
82
+ const key = path[index]!
83
+ const existing = current[key]
84
+ if (typeof existing === "object" && existing !== null && !Array.isArray(existing)) {
85
+ current = existing as Record<string, unknown>
86
+ continue
87
+ }
88
+ const next: Record<string, unknown> = {}
89
+ current[key] = next
90
+ current = next
91
+ }
92
+ current[path[path.length - 1]!] = value
93
+ }
94
+
95
+ const hasWriteStatement = (statement: QueryAst.QueryStatement): boolean =>
96
+ statement === "insert" ||
97
+ statement === "update" ||
98
+ statement === "delete" ||
99
+ statement === "truncate" ||
100
+ statement === "merge" ||
101
+ statement === "transaction" ||
102
+ statement === "commit" ||
103
+ statement === "rollback" ||
104
+ statement === "savepoint" ||
105
+ statement === "rollbackTo" ||
106
+ statement === "releaseSavepoint" ||
107
+ statement === "createTable" ||
108
+ statement === "createIndex" ||
109
+ statement === "dropIndex" ||
110
+ statement === "dropTable"
111
+
112
+ const hasWriteCapabilityInSource = (source: unknown): boolean =>
113
+ typeof source === "object" && source !== null && "plan" in source
114
+ ? hasWriteCapability((source as { readonly plan: Query.QueryPlan<any, any, any, any, any, any, any, any, any, any> }).plan)
115
+ : false
116
+
117
+ export const hasWriteCapability = (
118
+ plan: Query.QueryPlan<any, any, any, any, any, any, any, any, any, any>
119
+ ): boolean => {
120
+ const ast = Query.getAst(plan)
121
+ if (hasWriteStatement(ast.kind)) {
122
+ return true
123
+ }
124
+ if (ast.kind === "set") {
125
+ if (ast.setBase && hasWriteCapability((ast.setBase as Query.QueryPlan<any, any, any, any, any, any, any, any, any, any>))) {
126
+ return true
127
+ }
128
+ if ((ast.setOperations ?? []).some((entry) => hasWriteCapability(entry.query as Query.QueryPlan<any, any, any, any, any, any, any, any, any, any>))) {
129
+ return true
130
+ }
131
+ }
132
+ if (ast.from && hasWriteCapabilityInSource(ast.from.source)) {
133
+ return true
134
+ }
135
+ if (ast.into && hasWriteCapabilityInSource(ast.into.source)) {
136
+ return true
137
+ }
138
+ if (ast.target && hasWriteCapabilityInSource(ast.target.source)) {
139
+ return true
140
+ }
141
+ if ((ast.joins ?? []).some((join) => hasWriteCapabilityInSource(join.source))) {
142
+ return true
143
+ }
144
+ return false
145
+ }
146
+
147
+ export const remapRows = <Row>(
148
+ query: Renderer.RenderedQuery<Row, any>,
149
+ rows: ReadonlyArray<FlatRow>
150
+ ): ReadonlyArray<Row> =>
151
+ rows.map((row) => {
152
+ const decoded: Record<string, unknown> = {}
153
+ for (const projection of query.projections) {
154
+ if (projection.alias in row) {
155
+ setPath(decoded, projection.path, row[projection.alias])
156
+ }
157
+ }
158
+ return decoded as Row
159
+ })
160
+
161
+ const makeRowDecodeError = (
162
+ rendered: Renderer.RenderedQuery<any, any>,
163
+ projection: Renderer.RenderedQuery<any, any>["projections"][number],
164
+ expression: Expression.Any,
165
+ raw: unknown,
166
+ stage: RowDecodeError["stage"],
167
+ cause: unknown,
168
+ normalized?: unknown
169
+ ): RowDecodeError => ({
170
+ _tag: "RowDecodeError",
171
+ message: stage === "normalize"
172
+ ? `Failed to normalize projection '${projection.alias}'`
173
+ : `Failed to decode projection '${projection.alias}' against its runtime schema`,
174
+ dialect: rendered.dialect,
175
+ query: {
176
+ sql: rendered.sql,
177
+ params: rendered.params
178
+ },
179
+ projection: {
180
+ alias: projection.alias,
181
+ path: projection.path
182
+ },
183
+ dbType: expression[Expression.TypeId].dbType,
184
+ raw,
185
+ normalized,
186
+ stage,
187
+ cause
188
+ })
189
+
190
+ const hasOptionalSourceDependency = (
191
+ expression: Expression.Any,
192
+ available: Readonly<Record<string, Plan.Source>>
193
+ ): boolean => {
194
+ const state = expression[Expression.TypeId]
195
+ if (state.sourceNullability === "resolved") {
196
+ return false
197
+ }
198
+ return Object.keys(state.dependencies).some((sourceName) => available[sourceName]?.mode === "optional")
199
+ }
200
+
201
+ const effectiveRuntimeNullability = (
202
+ expression: Expression.Any,
203
+ available: Readonly<Record<string, Plan.Source>>
204
+ ): Expression.Nullability => {
205
+ const nullability = expression[Expression.TypeId].nullability
206
+ if (nullability === "always") {
207
+ return "always"
208
+ }
209
+ return hasOptionalSourceDependency(expression, available)
210
+ ? "maybe"
211
+ : nullability
212
+ }
213
+
214
+ const decodeProjectionValue = (
215
+ rendered: Renderer.RenderedQuery<any, any>,
216
+ projection: Renderer.RenderedQuery<any, any>["projections"][number],
217
+ expression: Expression.Any,
218
+ raw: unknown,
219
+ available: Readonly<Record<string, Plan.Source>>,
220
+ driverMode: DriverMode
221
+ ): unknown => {
222
+ let normalized = raw
223
+ if (driverMode === "raw") {
224
+ try {
225
+ normalized = normalizeDbValue(expression[Expression.TypeId].dbType, raw)
226
+ } catch (cause) {
227
+ throw makeRowDecodeError(rendered, projection, expression, raw, "normalize", cause)
228
+ }
229
+ }
230
+
231
+ if (normalized === null) {
232
+ if (effectiveRuntimeNullability(expression, available) === "never") {
233
+ throw makeRowDecodeError(
234
+ rendered,
235
+ projection,
236
+ expression,
237
+ raw,
238
+ "schema",
239
+ new Error("Received null for a non-null projection"),
240
+ normalized
241
+ )
242
+ }
243
+ return null
244
+ }
245
+
246
+ const schema = expressionRuntimeSchema(expression)
247
+ if (schema === undefined) {
248
+ return normalized
249
+ }
250
+
251
+ if ((Schema.is(schema as Schema.Schema.Any) as (value: unknown) => boolean)(normalized)) {
252
+ return normalized
253
+ }
254
+
255
+ try {
256
+ return (Schema.decodeUnknownSync as any)(schema)(normalized)
257
+ } catch (cause) {
258
+ throw makeRowDecodeError(rendered, projection, expression, raw, "schema", cause, normalized)
259
+ }
260
+ }
261
+
262
+ export const decodeRows = (
263
+ rendered: Renderer.RenderedQuery<any, any>,
264
+ plan: Query.QueryPlan<any, any, any, any, any, any, any, any, any, any>,
265
+ rows: ReadonlyArray<FlatRow>,
266
+ options: {
267
+ readonly driverMode?: DriverMode
268
+ } = {}
269
+ ): ReadonlyArray<any> => {
270
+ const projections = flattenSelection(
271
+ Query.getAst(plan).select as Record<string, unknown>
272
+ )
273
+ const byAlias = new Map(
274
+ projections.map((projection) => [projection.alias, projection.expression] as const)
275
+ )
276
+ const driverMode = options.driverMode ?? "raw"
277
+ const available = plan[Plan.TypeId].available
278
+ return rows.map((row) => {
279
+ const decoded: Record<string, unknown> = {}
280
+ for (const projection of rendered.projections) {
281
+ if (!(projection.alias in row)) {
282
+ continue
283
+ }
284
+ const expression = byAlias.get(projection.alias)
285
+ if (expression === undefined) {
286
+ continue
287
+ }
288
+ setPath(
289
+ decoded,
290
+ projection.path,
291
+ decodeProjectionValue(rendered, projection, expression, row[projection.alias], available, driverMode)
292
+ )
293
+ }
294
+ return decoded
295
+ })
296
+ }
297
+
298
+ /**
299
+ * Constructs an executor from a dialect and implementation callback.
300
+ */
301
+ export const make = <
302
+ Dialect extends string,
303
+ Error = never,
304
+ Context = never
305
+ >(
306
+ dialect: Dialect,
307
+ execute: <PlanValue extends Query.QueryPlan<any, any, any, any, any, any, any, any, any>>(
308
+ plan: Query.DialectCompatiblePlan<PlanValue, Dialect>
309
+ ) => Effect.Effect<Query.ResultRows<PlanValue>, Error, Context>
310
+ ): Executor<Dialect, Error, Context> => ({
311
+ dialect,
312
+ execute(plan) {
313
+ return (execute as any)(plan)
314
+ }
315
+ }) as Executor<Dialect, Error, Context>
316
+
317
+ /**
318
+ * Constructs a driver from a dialect and execution callback.
319
+ */
320
+ export const driver = <
321
+ Dialect extends string,
322
+ Error = never,
323
+ Context = never
324
+ >(
325
+ dialect: Dialect,
326
+ execute: <Row>(
327
+ query: Renderer.RenderedQuery<Row, Dialect>
328
+ ) => Effect.Effect<ReadonlyArray<FlatRow>, Error, Context>
329
+ ): Driver<Dialect, Error, Context> => ({
330
+ dialect,
331
+ execute(query) {
332
+ return execute(query)
333
+ }
334
+ })
335
+
336
+ /**
337
+ * Creates an executor by composing a renderer with a rendered-query driver.
338
+ *
339
+ * This is the concrete render -> run -> remap pipeline:
340
+ * 1. render a complete query plan into SQL + params
341
+ * 2. execute that rendered query through the driver
342
+ * 3. remap flat alias-keyed rows back into nested objects
343
+ */
344
+ export const fromDriver = <
345
+ Dialect extends string,
346
+ Error = never,
347
+ Context = never
348
+ >(
349
+ renderer: Renderer.Renderer<Dialect>,
350
+ sqlDriver: Driver<Dialect, Error, Context>
351
+ ): Executor<Dialect, Error, Context> => {
352
+ const executor = {
353
+ dialect: renderer.dialect,
354
+ execute(plan: any) {
355
+ const rendered = renderer.render(plan) as Renderer.RenderedQuery<any, Dialect>
356
+ return Effect.map(
357
+ sqlDriver.execute(rendered),
358
+ (rows) => remapRows<any>(rendered, rows)
359
+ )
360
+ }
361
+ }
362
+ return executor as unknown as Executor<Dialect, Error, Context>
363
+ }
364
+
365
+ /**
366
+ * Creates an executor backed by `@effect/sql`'s `SqlClient`.
367
+ */
368
+ export const fromSqlClient = <Dialect extends string>(
369
+ renderer: Renderer.Renderer<Dialect>
370
+ ): Executor<Dialect, unknown, SqlClient.SqlClient> =>
371
+ fromDriver(renderer, driver(renderer.dialect, (query) =>
372
+ Effect.flatMap(SqlClient.SqlClient, (sql) =>
373
+ sql.unsafe<FlatRow>(query.sql, [...query.params]))))
374
+
375
+ /** Runs an effect within the ambient `@effect/sql` transaction service. */
376
+ export const withTransaction = <A, E, R>(
377
+ effect: Effect.Effect<A, E, R>
378
+ ): Effect.Effect<A, E | SqlError.SqlError, R | SqlClient.SqlClient> =>
379
+ Effect.flatMap(SqlClient.SqlClient, (sql) => sql.withTransaction(effect))
380
+
381
+ /**
382
+ * Runs an effect in a nested transaction scope.
383
+ *
384
+ * When the ambient `@effect/sql` client is already inside a transaction, the
385
+ * underlying client implementation uses a savepoint.
386
+ */
387
+ export const withSavepoint = <A, E, R>(
388
+ effect: Effect.Effect<A, E, R>
389
+ ): Effect.Effect<A, E | SqlError.SqlError, R | SqlClient.SqlClient> =>
390
+ Effect.flatMap(SqlClient.SqlClient, (sql) => sql.withTransaction(effect))