effect-qb 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +7 -1
  2. package/dist/index.js +1990 -679
  3. package/dist/mysql.js +1490 -617
  4. package/dist/postgres/metadata.js +1334 -263
  5. package/dist/postgres.js +3376 -2307
  6. package/dist/sqlite.js +1573 -628
  7. package/dist/standard.js +1984 -673
  8. package/package.json +2 -4
  9. package/src/internal/coercion/rules.ts +13 -1
  10. package/src/internal/column-state.d.ts +3 -3
  11. package/src/internal/column-state.ts +13 -12
  12. package/src/internal/column.ts +8 -8
  13. package/src/internal/datatypes/define.ts +5 -0
  14. package/src/internal/datatypes/lookup.ts +67 -18
  15. package/src/internal/datatypes/matrix.ts +903 -0
  16. package/src/internal/datatypes/shape.ts +2 -0
  17. package/src/internal/dialect-renderers/mysql.ts +6 -4
  18. package/src/internal/dialect-renderers/postgres.ts +6 -4
  19. package/src/internal/dialect-renderers/sqlite.ts +6 -4
  20. package/src/internal/dialect.ts +1 -1
  21. package/src/internal/executor.ts +56 -43
  22. package/src/internal/json/path-access.ts +351 -0
  23. package/src/internal/query.d.ts +1 -1
  24. package/src/internal/query.ts +1 -1
  25. package/src/internal/runtime/driver-value-mapping.ts +3 -3
  26. package/src/internal/runtime/schema.ts +28 -38
  27. package/src/internal/runtime/value.ts +20 -23
  28. package/src/internal/scalar.d.ts +1 -1
  29. package/src/internal/scalar.ts +2 -1
  30. package/src/internal/schema-derivation.d.ts +7 -7
  31. package/src/internal/schema-derivation.ts +11 -11
  32. package/src/internal/standard-dsl.ts +121 -28
  33. package/src/internal/table.ts +451 -120
  34. package/src/mysql/column.ts +6 -6
  35. package/src/mysql/datatypes/index.ts +1 -0
  36. package/src/mysql/datatypes/spec.ts +6 -176
  37. package/src/mysql/errors/normalize.ts +0 -1
  38. package/src/mysql/executor.ts +4 -6
  39. package/src/mysql/function/temporal.ts +1 -1
  40. package/src/mysql/internal/dsl.ts +116 -16
  41. package/src/mysql/json.ts +1 -33
  42. package/src/mysql/renderer.ts +13 -6
  43. package/src/mysql/type.ts +60 -0
  44. package/src/mysql.ts +3 -1
  45. package/src/postgres/check.ts +1 -0
  46. package/src/postgres/column.ts +11 -11
  47. package/src/postgres/datatypes/index.ts +1 -0
  48. package/src/postgres/datatypes/spec.ts +6 -260
  49. package/src/postgres/errors/normalize.ts +0 -1
  50. package/src/postgres/executor.ts +4 -6
  51. package/src/postgres/foreign-key.ts +24 -0
  52. package/src/postgres/function/temporal.ts +1 -1
  53. package/src/postgres/index.ts +1 -0
  54. package/src/postgres/internal/dsl.ts +122 -21
  55. package/src/postgres/json-extension.ts +7 -0
  56. package/src/postgres/json.ts +726 -173
  57. package/src/postgres/jsonb.ts +0 -1
  58. package/src/postgres/primary-key.ts +24 -0
  59. package/src/postgres/renderer.ts +13 -6
  60. package/src/postgres/schema-management.ts +1 -6
  61. package/src/postgres/schema.ts +16 -8
  62. package/src/postgres/table.ts +111 -113
  63. package/src/postgres/type.ts +86 -4
  64. package/src/postgres/unique.ts +32 -0
  65. package/src/postgres.ts +12 -6
  66. package/src/sqlite/column.ts +6 -6
  67. package/src/sqlite/datatypes/index.ts +1 -0
  68. package/src/sqlite/datatypes/spec.ts +6 -94
  69. package/src/sqlite/errors/normalize.ts +0 -1
  70. package/src/sqlite/executor.ts +4 -6
  71. package/src/sqlite/function/temporal.ts +1 -1
  72. package/src/sqlite/internal/dsl.ts +100 -5
  73. package/src/sqlite/json.ts +1 -32
  74. package/src/sqlite/renderer.ts +13 -6
  75. package/src/sqlite/type.ts +40 -0
  76. package/src/sqlite.ts +3 -1
  77. package/src/standard/cast.ts +113 -0
  78. package/src/standard/check.ts +17 -0
  79. package/src/standard/column.ts +10 -10
  80. package/src/standard/datatypes/index.ts +2 -2
  81. package/src/standard/datatypes/spec.ts +10 -96
  82. package/src/standard/foreign-key.ts +37 -0
  83. package/src/standard/function/temporal.ts +1 -1
  84. package/src/standard/index.ts +17 -0
  85. package/src/standard/json.ts +883 -0
  86. package/src/standard/primary-key.ts +17 -0
  87. package/src/standard/renderer.ts +31 -3
  88. package/src/standard/table.ts +25 -21
  89. package/src/standard/unique.ts +17 -0
  90. package/src/standard.ts +14 -0
  91. package/src/internal/table.d.ts +0 -174
  92. package/src/postgres/cast.ts +0 -45
@@ -56,10 +56,12 @@ export interface DatatypeTraits {
56
56
  export interface DatatypeFamilySpec<
57
57
  CompareGroup extends string = string,
58
58
  CastTargets extends readonly string[] = readonly string[],
59
+ ImplicitTargets extends readonly string[] = readonly string[],
59
60
  Traits extends DatatypeTraits = DatatypeTraits
60
61
  > {
61
62
  readonly compareGroup: CompareGroup
62
63
  readonly castTargets: CastTargets
64
+ readonly implicitTargets?: ImplicitTargets
63
65
  readonly traits: Traits
64
66
  }
65
67
 
@@ -5,6 +5,7 @@ import * as Expression from "../scalar.js"
5
5
  import * as Table from "../table.js"
6
6
  import * as QueryAst from "../query-ast.js"
7
7
  import { renderDbTypeName, type RenderState, type RenderValueContext, type SqlDialect } from "../dialect.js"
8
+ import { renderPortableDatatypeCastType, renderPortableDatatypeDdlType } from "../datatypes/matrix.js"
8
9
  import * as ExpressionAst from "../expression-ast.js"
9
10
  import * as JsonPath from "../json/path.js"
10
11
  import { renderMysqlMutationLockMode, renderSelectLockMode } from "../dsl-plan-runtime.js"
@@ -25,10 +26,7 @@ const renderDbType = (
25
26
  dialect: SqlDialect,
26
27
  dbType: Expression.DbType.Any
27
28
  ): string => {
28
- if (dialect.name === "mysql" && dbType.kind === "uuid") {
29
- return "char(36)"
30
- }
31
- return renderDbTypeName(dbType.kind)
29
+ return renderDbTypeName(renderPortableDatatypeDdlType(dialect.name, dbType.kind) ?? dbType.kind)
32
30
  }
33
31
 
34
32
  const isArrayDbType = (dbType: Expression.DbType.Any): boolean =>
@@ -39,6 +37,10 @@ const renderCastType = (
39
37
  dbType: unknown
40
38
  ): string => {
41
39
  const kind = (dbType as { readonly kind?: string } | undefined)?.kind as string
40
+ const portableType = renderPortableDatatypeCastType(dialect.name, kind)
41
+ if (portableType !== undefined) {
42
+ return renderDbTypeName(portableType)
43
+ }
42
44
  if (dialect.name !== "mysql") {
43
45
  return renderDbTypeName(kind)
44
46
  }
@@ -5,6 +5,7 @@ import * as Expression from "../scalar.js"
5
5
  import * as Table from "../table.js"
6
6
  import * as QueryAst from "../query-ast.js"
7
7
  import { renderDbTypeName, type RenderState, type RenderValueContext, type SqlDialect } from "../dialect.js"
8
+ import { renderPortableDatatypeCastType, renderPortableDatatypeDdlType } from "../datatypes/matrix.js"
8
9
  import * as ExpressionAst from "../expression-ast.js"
9
10
  import * as JsonPath from "../json/path.js"
10
11
  import { renderSelectLockMode } from "../dsl-plan-runtime.js"
@@ -26,10 +27,7 @@ const renderDbType = (
26
27
  dialect: SqlDialect,
27
28
  dbType: Expression.DbType.Any
28
29
  ): string => {
29
- if (dialect.name === "postgres" && dbType.kind === "blob") {
30
- return "bytea"
31
- }
32
- return renderDbTypeName(dbType.kind)
30
+ return renderDbTypeName(renderPortableDatatypeDdlType(dialect.name, dbType.kind) ?? dbType.kind)
33
31
  }
34
32
 
35
33
  const isArrayDbType = (dbType: Expression.DbType.Any): boolean =>
@@ -40,6 +38,10 @@ const renderCastType = (
40
38
  dbType: unknown
41
39
  ): string => {
42
40
  const kind = (dbType as { readonly kind?: string } | undefined)?.kind as string
41
+ const portableType = renderPortableDatatypeCastType(dialect.name, kind)
42
+ if (portableType !== undefined) {
43
+ return renderDbTypeName(portableType)
44
+ }
43
45
  if (dialect.name !== "mysql") {
44
46
  return renderDbTypeName(kind)
45
47
  }
@@ -5,6 +5,7 @@ import * as Expression from "../scalar.js"
5
5
  import * as Table from "../table.js"
6
6
  import * as QueryAst from "../query-ast.js"
7
7
  import { renderDbTypeName, type RenderState, type RenderValueContext, type SqlDialect } from "../dialect.js"
8
+ import { renderPortableDatatypeCastType, renderPortableDatatypeDdlType } from "../datatypes/matrix.js"
8
9
  import * as ExpressionAst from "../expression-ast.js"
9
10
  import * as JsonPath from "../json/path.js"
10
11
  import { expectConflictClause } from "../dsl-mutation-runtime.js"
@@ -24,10 +25,7 @@ const renderDbType = (
24
25
  dialect: SqlDialect,
25
26
  dbType: Expression.DbType.Any
26
27
  ): string => {
27
- if (dialect.name === "sqlite" && dbType.kind === "uuid") {
28
- return "text"
29
- }
30
- return renderDbTypeName(dbType.kind)
28
+ return renderDbTypeName(renderPortableDatatypeDdlType(dialect.name, dbType.kind) ?? dbType.kind)
31
29
  }
32
30
 
33
31
  const isArrayDbType = (dbType: Expression.DbType.Any): boolean =>
@@ -38,6 +36,10 @@ const renderCastType = (
38
36
  dbType: unknown
39
37
  ): string => {
40
38
  const kind = (dbType as { readonly kind?: string } | undefined)?.kind as string
39
+ const portableType = renderPortableDatatypeCastType(dialect.name, kind)
40
+ if (portableType !== undefined) {
41
+ return renderDbTypeName(portableType)
42
+ }
41
43
  if (dialect.name !== "sqlite") {
42
44
  return renderDbTypeName(kind)
43
45
  }
@@ -32,7 +32,7 @@ export interface RenderState {
32
32
 
33
33
  export interface RenderValueContext {
34
34
  readonly dbType?: Expression.DbType.Any
35
- readonly runtimeSchema?: Schema.Schema.Any
35
+ readonly runtimeSchema?: Schema.Top
36
36
  readonly driverValueMapping?: Expression.DriverValueMapping
37
37
  }
38
38
 
@@ -1,9 +1,10 @@
1
1
  import * as Chunk from "effect/Chunk"
2
2
  import * as Effect from "effect/Effect"
3
+ import * as Exit from "effect/Exit"
3
4
  import * as Option from "effect/Option"
4
5
  import * as Schema from "effect/Schema"
5
- import * as SqlClient from "@effect/sql/SqlClient"
6
- import * as SqlError from "@effect/sql/SqlError"
6
+ import * as SqlClient from "effect/unstable/sql/SqlClient"
7
+ import * as SqlError from "effect/unstable/sql/SqlError"
7
8
  import * as Stream from "effect/Stream"
8
9
 
9
10
  import * as Expression from "./scalar.js"
@@ -44,6 +45,10 @@ export interface RowDecodeError {
44
45
  readonly normalized?: unknown
45
46
  readonly stage: "normalize" | "schema"
46
47
  readonly cause: unknown
48
+ readonly schemaError?: {
49
+ readonly message: string
50
+ readonly issue: unknown
51
+ }
47
52
  }
48
53
 
49
54
  /**
@@ -183,26 +188,35 @@ const makeRowDecodeError = (
183
188
  stage: RowDecodeError["stage"],
184
189
  cause: unknown,
185
190
  normalized?: unknown
186
- ): RowDecodeError => ({
187
- _tag: "RowDecodeError",
188
- message: stage === "normalize"
189
- ? `Failed to normalize projection '${projection.alias}'`
190
- : `Failed to decode projection '${projection.alias}' against its runtime schema`,
191
- dialect: rendered.dialect,
192
- query: {
193
- sql: rendered.sql,
194
- params: rendered.params
195
- },
196
- projection: {
197
- alias: projection.alias,
198
- path: projection.path
199
- },
200
- dbType: expression[Expression.TypeId].dbType,
201
- raw,
202
- normalized,
203
- stage,
204
- cause
205
- })
191
+ ): RowDecodeError => {
192
+ const schemaError = Schema.isSchemaError(cause)
193
+ ? {
194
+ message: cause.message,
195
+ issue: cause.issue
196
+ }
197
+ : undefined
198
+ return {
199
+ _tag: "RowDecodeError",
200
+ message: stage === "normalize"
201
+ ? `Failed to normalize projection '${projection.alias}'`
202
+ : `Failed to decode projection '${projection.alias}' against its runtime schema`,
203
+ dialect: rendered.dialect,
204
+ query: {
205
+ sql: rendered.sql,
206
+ params: rendered.params
207
+ },
208
+ projection: {
209
+ alias: projection.alias,
210
+ path: projection.path
211
+ },
212
+ dbType: expression[Expression.TypeId].dbType,
213
+ raw,
214
+ normalized,
215
+ stage,
216
+ cause,
217
+ ...(schemaError === undefined ? {} : { schemaError })
218
+ }
219
+ }
206
220
 
207
221
  const hasOptionalSourceDependency = (
208
222
  expression: Expression.Any,
@@ -249,7 +263,7 @@ const dbTypeAllowsTopLevelJsonNull = (
249
263
  }
250
264
 
251
265
  const schemaAcceptsNull = (
252
- schema: Schema.Schema.Any | undefined
266
+ schema: Schema.Top | undefined
253
267
  ): boolean =>
254
268
  schema !== undefined && (Schema.is(schema) as (value: unknown) => boolean)(null)
255
269
 
@@ -325,15 +339,20 @@ const decodeProjectionValue = (
325
339
  return normalized
326
340
  }
327
341
 
328
- if ((Schema.is(schema as Schema.Schema.Any) as (value: unknown) => boolean)(normalized)) {
342
+ if ((Schema.is(schema as Schema.Top) as (value: unknown) => boolean)(normalized)) {
329
343
  return normalized
330
344
  }
331
345
 
332
- try {
333
- return (Schema.decodeUnknownSync as any)(schema)(normalized)
334
- } catch (cause) {
335
- throw makeRowDecodeError(rendered, projection, expression, raw, "schema", cause, normalized)
346
+ const decoded = (Schema.decodeUnknownExit as any)(schema)(normalized)
347
+ if (Exit.isSuccess(decoded)) {
348
+ return decoded.value
336
349
  }
350
+
351
+ const cause = Option.match(Exit.findErrorOption(decoded), {
352
+ onNone: () => decoded.cause,
353
+ onSome: (schemaError) => schemaError
354
+ })
355
+ throw makeRowDecodeError(rendered, projection, expression, raw, "schema", cause, normalized)
337
356
  }
338
357
 
339
358
  export const makeRowDecoder = (
@@ -390,7 +409,7 @@ export const decodeChunk = (
390
409
  } = {}
391
410
  ): Chunk.Chunk<any> => {
392
411
  const decodeRow = makeRowDecoder(rendered, plan, options)
393
- return Chunk.unsafeFromArray(Chunk.toReadonlyArray(rows).map((row) => decodeRow(row)))
412
+ return Chunk.fromIterable(Chunk.toReadonlyArray(rows).map((row) => decodeRow(row)))
394
413
  }
395
414
 
396
415
  export const decodeRows = (
@@ -524,9 +543,9 @@ export const fromDriver = <
524
543
  plan: Query.DialectCompatiblePlan<PlanValue, Dialect>
525
544
  ) {
526
545
  const rendered = renderer.render(plan) as Renderer.RenderedQuery<any, Dialect>
527
- return Stream.mapChunks(
546
+ return Stream.mapArray(
528
547
  sqlDriver.stream(rendered),
529
- (rows) => Chunk.unsafeFromArray(remapRows<any>(rendered, Chunk.toReadonlyArray(rows)))
548
+ (rows) => remapRows<any>(rendered, rows) as never
530
549
  ) as Stream.Stream<Query.ResultRow<PlanValue>, Error, Context>
531
550
  }
532
551
  }
@@ -536,10 +555,10 @@ export const fromDriver = <
536
555
  export const streamFromSqlClient = <Dialect extends string>(
537
556
  query: Renderer.RenderedQuery<any, Dialect>
538
557
  ): Stream.Stream<FlatRow, SqlError.SqlError, SqlClient.SqlClient> =>
539
- Stream.unwrapScoped(
558
+ Stream.unwrap(
540
559
  Effect.flatMap(SqlClient.SqlClient, (sql) =>
541
560
  Effect.flatMap(
542
- Effect.serviceOption(SqlClient.TransactionConnection),
561
+ Effect.serviceOption(sql.transactionService),
543
562
  Option.match({
544
563
  onNone: () => sql.reserve,
545
564
  onSome: ([connection]) => Effect.succeed(connection)
@@ -560,19 +579,13 @@ export const fromSqlClient = <Dialect extends string>(
560
579
  stream: (query) => streamFromSqlClient(query)
561
580
  }))
562
581
 
563
- /** Runs an effect within the ambient `@effect/sql` transaction service. */
564
- export const withTransaction = <A, E, R>(
565
- effect: Effect.Effect<A, E, R>
566
- ): Effect.Effect<A, E | SqlError.SqlError, R | SqlClient.SqlClient> =>
567
- Effect.flatMap(SqlClient.SqlClient, (sql) => sql.withTransaction(effect))
568
-
569
582
  /**
570
- * Runs an effect in a nested transaction scope.
583
+ * Runs an effect within the ambient `effect/unstable/sql` transaction service.
571
584
  *
572
- * When the ambient `@effect/sql` client is already inside a transaction, the
573
- * underlying client implementation uses a savepoint.
585
+ * Nested calls rely on the underlying client transaction implementation for
586
+ * savepoint behavior.
574
587
  */
575
- export const withSavepoint = <A, E, R>(
588
+ export const withTransaction = <A, E, R>(
576
589
  effect: Effect.Effect<A, E, R>
577
590
  ): Effect.Effect<A, E | SqlError.SqlError, R | SqlClient.SqlClient> =>
578
591
  Effect.flatMap(SqlClient.SqlClient, (sql) => sql.withTransaction(effect))
@@ -0,0 +1,351 @@
1
+ import { pipeArguments, type Pipeable } from "effect/Pipeable"
2
+
3
+ import * as ExpressionAst from "../expression-ast.js"
4
+ import * as Expression from "../scalar.js"
5
+ import * as JsonPath from "./path.js"
6
+ import type { JsonValueAtPath } from "./types.js"
7
+
8
+ const WrappedTypeId: unique symbol = Symbol.for("effect-qb/JsonPathAccess")
9
+
10
+ type JsonDb = Expression.DbType.Json<any, any>
11
+
12
+ type AnyJsonExpression = Expression.Scalar<
13
+ any,
14
+ JsonDb,
15
+ Expression.Nullability,
16
+ string,
17
+ Expression.ScalarKind,
18
+ Expression.BindingId
19
+ >
20
+
21
+ type JsonNullabilityOf<Output> =
22
+ null extends Output
23
+ ? Exclude<Output, null> extends never ? "always" : "maybe"
24
+ : "never"
25
+
26
+ type IsAny<Value> = 0 extends (1 & Value) ? true : false
27
+
28
+ type SegmentTuple<Segments> = Segments extends readonly JsonPath.CanonicalSegment[]
29
+ ? Segments
30
+ : readonly JsonPath.CanonicalSegment[]
31
+
32
+ type JsonAccessAst<Value> = Value extends {
33
+ readonly [ExpressionAst.TypeId]: infer Ast
34
+ } ? Ast : never
35
+
36
+ type JsonAccessParts<
37
+ Value,
38
+ Acc extends readonly JsonPath.CanonicalSegment[] = readonly [],
39
+ Depth extends readonly unknown[] = []
40
+ > =
41
+ Depth["length"] extends 8 ? readonly [Value, Acc] :
42
+ [JsonAccessAst<Value>] extends [never]
43
+ ? readonly [Value, Acc]
44
+ : JsonAccessAst<Value> extends ExpressionAst.JsonAccessNode<any, infer Base extends Expression.Any, infer Segments>
45
+ ? JsonAccessParts<Base, readonly [...SegmentTuple<Segments>, ...Acc], readonly [...Depth, unknown]>
46
+ : readonly [Value, Acc]
47
+
48
+ type JsonAccessRoot<Value> =
49
+ JsonAccessParts<Value> extends readonly [infer Base, readonly JsonPath.CanonicalSegment[]]
50
+ ? Base
51
+ : Value
52
+
53
+ type JsonAccessSegments<Value> =
54
+ JsonAccessParts<Value> extends readonly [any, infer Segments extends readonly JsonPath.CanonicalSegment[]]
55
+ ? Segments
56
+ : readonly []
57
+
58
+ type JsonAccessBase<Value> =
59
+ JsonAccessRoot<Value> extends Expression.Any ? JsonAccessRoot<Value> : Value
60
+
61
+ type JsonPathOutput<
62
+ Root,
63
+ Segments extends readonly JsonPath.CanonicalSegment[],
64
+ Operation extends string
65
+ > =
66
+ JsonValueAtPath<Exclude<Root, null>, JsonPath.Path<Segments>, Operation> |
67
+ (null extends Root ? null : never)
68
+
69
+ type JsonAccessExpression<
70
+ Base extends AnyJsonExpression,
71
+ Segments extends readonly JsonPath.CanonicalSegment[]
72
+ > = WithJsonPathAccess<
73
+ Expression.Scalar<
74
+ JsonPathOutput<Expression.RuntimeOf<JsonAccessBase<Base>>, Segments, "json.get">,
75
+ Expression.DbTypeOf<JsonAccessBase<Base>>,
76
+ JsonNullabilityOf<JsonPathOutput<Expression.RuntimeOf<JsonAccessBase<Base>>, Segments, "json.get">>,
77
+ Expression.DbTypeOf<JsonAccessBase<Base>>["dialect"],
78
+ Expression.KindOf<JsonAccessBase<Base>>,
79
+ Expression.DependenciesOf<JsonAccessBase<Base>>
80
+ > & {
81
+ readonly [ExpressionAst.TypeId]: ExpressionAst.JsonAccessNode<
82
+ JsonPath.IsExactPath<JsonPath.Path<Segments>> extends true ? "jsonPath" : "jsonTraverse",
83
+ JsonAccessBase<Base>,
84
+ Segments
85
+ >
86
+ }
87
+ >
88
+
89
+ type JsonKeyAccess<
90
+ Base extends AnyJsonExpression,
91
+ Key extends string
92
+ > = JsonAccessExpression<Base, readonly [...JsonAccessSegments<Base>, JsonPath.KeySegment<Key>]>
93
+
94
+ type JsonIndexAccess<
95
+ Base extends AnyJsonExpression,
96
+ Index extends number
97
+ > = JsonAccessExpression<Base, readonly [...JsonAccessSegments<Base>, JsonPath.IndexSegment<Index>]>
98
+
99
+ type ReservedPathKey =
100
+ | "pipe"
101
+ | "then"
102
+ | "toString"
103
+ | "toJSON"
104
+ | "valueOf"
105
+ | "constructor"
106
+ | "__proto__"
107
+ | "prototype"
108
+ | "inspect"
109
+ | "schema"
110
+ | "metadata"
111
+ | "columns"
112
+ | "name"
113
+
114
+ type KeysOfUnion<Value> = Value extends unknown ? keyof Value : never
115
+
116
+ type JsonObjectKey<Key> = Key extends string
117
+ ? Key extends ReservedPathKey ? never :
118
+ Key extends `${number}` ? never :
119
+ Key
120
+ : never
121
+
122
+ type TupleNumberKey<Key> = Key extends `${infer Index extends number}` ? Index : never
123
+
124
+ type JsonTupleAccessors<
125
+ Base extends AnyJsonExpression,
126
+ Runtime extends readonly unknown[]
127
+ > = {
128
+ readonly [Key in Extract<keyof Runtime, `${number}`> as TupleNumberKey<Key>]: JsonIndexAccess<Base, TupleNumberKey<Key>>
129
+ } & {
130
+ readonly [index: number]: JsonIndexAccess<Base, number>
131
+ }
132
+
133
+ type JsonArrayAccessors<
134
+ Base extends AnyJsonExpression,
135
+ Runtime extends readonly unknown[]
136
+ > = number extends Runtime["length"]
137
+ ? {
138
+ readonly [index: number]: JsonIndexAccess<Base, number>
139
+ }
140
+ : JsonTupleAccessors<Base, Runtime>
141
+
142
+ type JsonObjectAccessors<
143
+ Base extends AnyJsonExpression,
144
+ Runtime extends object
145
+ > = {
146
+ readonly [Key in KeysOfUnion<Runtime> as JsonObjectKey<Key>]: JsonKeyAccess<Base, JsonObjectKey<Key>>
147
+ }
148
+
149
+ type JsonObjectPredicateKeys<Runtime> =
150
+ Runtime extends readonly unknown[]
151
+ ? never
152
+ : Runtime extends object
153
+ ? Extract<KeysOfUnion<Runtime>, string>
154
+ : never
155
+
156
+ export type JsonObjectKeyOf<Value extends Expression.Any> =
157
+ IsAny<Expression.RuntimeOf<Value>> extends true
158
+ ? string
159
+ : Exclude<Expression.RuntimeOf<Value>, null> extends infer Runtime
160
+ ? string extends JsonObjectPredicateKeys<Runtime>
161
+ ? string
162
+ : JsonObjectPredicateKeys<Runtime>
163
+ : never
164
+
165
+ type JsonPathAccessors<Base extends AnyJsonExpression> =
166
+ IsAny<Expression.RuntimeOf<Base>> extends true
167
+ ? {}
168
+ : Exclude<Expression.RuntimeOf<Base>, null> extends infer Runtime
169
+ ? Runtime extends readonly unknown[]
170
+ ? JsonArrayAccessors<Base, Runtime>
171
+ : Runtime extends object
172
+ ? JsonObjectAccessors<Base, Runtime>
173
+ : {}
174
+ : {}
175
+
176
+ export type WithJsonPathAccess<Value> = Value extends AnyJsonExpression
177
+ ? Value & JsonPathAccessors<Value>
178
+ : Value
179
+
180
+ const accessKinds = new Set<string>([
181
+ "jsonGet",
182
+ "jsonPath",
183
+ "jsonAccess",
184
+ "jsonTraverse",
185
+ "jsonGetText",
186
+ "jsonPathText",
187
+ "jsonAccessText",
188
+ "jsonTraverseText"
189
+ ])
190
+
191
+ const reservedKeys = new Set<string>([
192
+ "pipe",
193
+ "then",
194
+ "toString",
195
+ "toJSON",
196
+ "valueOf",
197
+ "constructor",
198
+ "__proto__",
199
+ "prototype",
200
+ "inspect",
201
+ "schema",
202
+ "metadata",
203
+ "columns",
204
+ "name"
205
+ ])
206
+
207
+ const isObjectLike = (value: unknown): value is object =>
208
+ (typeof value === "object" || typeof value === "function") && value !== null
209
+
210
+ const isExpression = (value: unknown): value is Expression.Any =>
211
+ isObjectLike(value) && Expression.TypeId in value
212
+
213
+ const isJsonExpression = (value: unknown): value is AnyJsonExpression => {
214
+ if (!isExpression(value)) {
215
+ return false
216
+ }
217
+ const dbType = value[Expression.TypeId].dbType as { readonly kind?: string; readonly variant?: string }
218
+ return dbType.kind === "json" || dbType.kind === "jsonb" || dbType.variant === "json" || dbType.variant === "jsonb"
219
+ }
220
+
221
+ const isWrapped = (value: object): boolean =>
222
+ (value as { readonly [WrappedTypeId]?: true })[WrappedTypeId] === true
223
+
224
+ const normalizeSegment = (segment: JsonPath.CanonicalSegment): JsonPath.CanonicalSegment => {
225
+ switch (segment.kind) {
226
+ case "key":
227
+ return JsonPath.key(segment.key)
228
+ case "index":
229
+ return JsonPath.index(segment.index)
230
+ case "wildcard":
231
+ return JsonPath.wildcard()
232
+ case "slice":
233
+ return JsonPath.slice(segment.start, segment.end)
234
+ case "descend":
235
+ return JsonPath.descend()
236
+ }
237
+ }
238
+
239
+ const accessPathOf = (value: Expression.Any): {
240
+ readonly base: Expression.Any
241
+ readonly segments: readonly JsonPath.CanonicalSegment[]
242
+ } => {
243
+ const segments: JsonPath.CanonicalSegment[] = []
244
+ let base: Expression.Any = value
245
+ while (isExpression(base)) {
246
+ const ast = (base as Expression.Any & {
247
+ readonly [ExpressionAst.TypeId]?: ExpressionAst.Any
248
+ })[ExpressionAst.TypeId] as { readonly kind?: unknown; readonly base?: unknown; readonly segments?: unknown } | undefined
249
+ if (ast === undefined || typeof ast.kind !== "string" || !accessKinds.has(ast.kind) || !isExpression(ast.base) || !Array.isArray(ast.segments)) {
250
+ break
251
+ }
252
+ segments.unshift(...ast.segments.map(normalizeSegment))
253
+ base = ast.base
254
+ }
255
+ return { base, segments }
256
+ }
257
+
258
+ const isIntegerProperty = (property: string): boolean =>
259
+ /^(?:-?(?:0|[1-9][0-9]*))$/.test(property)
260
+
261
+ const jsonAccessKind = (
262
+ segments: readonly JsonPath.CanonicalSegment[]
263
+ ): ExpressionAst.JsonAccessKind =>
264
+ segments.every((segment) => segment.kind === "key" || segment.kind === "index")
265
+ ? segments.length === 1 ? "jsonGet" : "jsonPath"
266
+ : "jsonTraverse"
267
+
268
+ const makeExpression = <
269
+ Runtime,
270
+ Db extends Expression.DbType.Any,
271
+ Nullable extends Expression.Nullability,
272
+ Dialect extends string,
273
+ Kind extends Expression.ScalarKind,
274
+ Deps extends Expression.BindingId,
275
+ Ast extends ExpressionAst.Any
276
+ >(
277
+ state: Expression.State<Runtime, Db, Nullable, Dialect, Kind, Deps>,
278
+ ast: Ast
279
+ ): Expression.Scalar<Runtime, Db, Nullable, Dialect, Kind, Deps> & {
280
+ readonly [ExpressionAst.TypeId]: Ast
281
+ } => {
282
+ const expression = Object.create(null) as Expression.Scalar<Runtime, Db, Nullable, Dialect, Kind, Deps> & {
283
+ [ExpressionAst.TypeId]: Ast
284
+ [Expression.TypeId]: Expression.State<Runtime, Db, Nullable, Dialect, Kind, Deps>
285
+ pipe: Pipeable["pipe"]
286
+ }
287
+ Object.defineProperty(expression, "pipe", {
288
+ configurable: true,
289
+ writable: true,
290
+ value: function(this: Pipeable) {
291
+ return pipeArguments(expression, arguments)
292
+ }
293
+ })
294
+ expression[Expression.TypeId] = state
295
+ expression[ExpressionAst.TypeId] = ast
296
+ return expression
297
+ }
298
+
299
+ const makePathExpression = (
300
+ value: AnyJsonExpression,
301
+ segment: JsonPath.CanonicalSegment
302
+ ): AnyJsonExpression => {
303
+ const access = accessPathOf(value)
304
+ const segments = [...access.segments, normalizeSegment(segment)]
305
+ const base = access.base as AnyJsonExpression
306
+ const baseState = base[Expression.TypeId]
307
+ return withJsonPathAccess(makeExpression(
308
+ {
309
+ ...baseState,
310
+ runtime: undefined,
311
+ nullability: undefined as unknown as Expression.Nullability
312
+ },
313
+ {
314
+ kind: jsonAccessKind(segments),
315
+ base,
316
+ segments
317
+ } satisfies ExpressionAst.JsonAccessNode<any, any, any>
318
+ ) as AnyJsonExpression)
319
+ }
320
+
321
+ const pathProxyHandler: ProxyHandler<AnyJsonExpression> = {
322
+ get(target, property, receiver) {
323
+ if (typeof property === "symbol") {
324
+ return Reflect.get(target, property, receiver)
325
+ }
326
+ if (Reflect.has(target, property) || reservedKeys.has(property)) {
327
+ return Reflect.get(target, property, receiver)
328
+ }
329
+ const segment = isIntegerProperty(property)
330
+ ? JsonPath.index(Number(property))
331
+ : JsonPath.key(property)
332
+ return makePathExpression(target, segment)
333
+ },
334
+ has(target, property) {
335
+ return Reflect.has(target, property)
336
+ }
337
+ }
338
+
339
+ export const withJsonPathAccess = <Value>(value: Value): WithJsonPathAccess<Value> => {
340
+ if (!isObjectLike(value) || !isJsonExpression(value) || isWrapped(value)) {
341
+ return value as WithJsonPathAccess<Value>
342
+ }
343
+ const proxy = new Proxy(value, pathProxyHandler) as unknown as WithJsonPathAccess<Value> & {
344
+ [WrappedTypeId]: true
345
+ }
346
+ Object.defineProperty(proxy, WrappedTypeId, {
347
+ configurable: false,
348
+ value: true
349
+ })
350
+ return proxy
351
+ }
@@ -770,7 +770,7 @@ export declare const mergeManyDependencies: <Values extends readonly Expression.
770
770
  export declare const makeExpression: <Runtime, Db extends Expression.DbType.Any, Nullable extends Expression.Nullability, Dialect extends string, Kind extends Expression.ScalarKind, Deps extends string = never, Ast extends ExpressionAst.Any = ExpressionAst.Any, GroupKey extends string = GroupingKeyOfAst<Ast>>(state: {
771
771
  readonly runtime: Runtime;
772
772
  readonly dbType: Db;
773
- readonly runtimeSchema?: Schema.Schema.Any | undefined;
773
+ readonly runtimeSchema?: Schema.Top | undefined;
774
774
  readonly nullability: Nullable;
775
775
  readonly dialect: Dialect;
776
776
  readonly kind?: Kind | undefined;
@@ -2459,7 +2459,7 @@ export const makeExpression = <
2459
2459
  state: {
2460
2460
  readonly runtime: Runtime
2461
2461
  readonly dbType: Db
2462
- readonly runtimeSchema?: Schema.Schema.Any
2462
+ readonly runtimeSchema?: Schema.Top
2463
2463
  readonly driverValueMapping?: Expression.DriverValueMapping
2464
2464
  readonly nullability: Nullable
2465
2465
  readonly dialect: Dialect
@@ -9,7 +9,7 @@ export type DriverValueMappings = Expression.DriverValueMappings
9
9
  export interface DriverValueContext {
10
10
  readonly dialect?: string
11
11
  readonly dbType?: Expression.DbType.Any
12
- readonly runtimeSchema?: Schema.Schema.Any
12
+ readonly runtimeSchema?: Schema.Top
13
13
  readonly driverValueMapping?: DriverValueMapping
14
14
  readonly valueMappings?: DriverValueMappings
15
15
  }
@@ -99,13 +99,13 @@ const isJsonDbType = (dbType: Expression.DbType.Any | undefined): boolean => {
99
99
  }
100
100
 
101
101
  const schemaAccepts = (
102
- schema: Schema.Schema.Any | undefined,
102
+ schema: Schema.Top | undefined,
103
103
  value: unknown
104
104
  ): boolean =>
105
105
  schema !== undefined && (Schema.is(schema) as (candidate: unknown) => boolean)(value)
106
106
 
107
107
  const encodeWithSchema = (
108
- schema: Schema.Schema.Any | undefined,
108
+ schema: Schema.Top | undefined,
109
109
  value: unknown
110
110
  ): { readonly value: unknown; readonly encoded: boolean } => {
111
111
  if (schema === undefined) {