effect-qb 0.19.0 → 4.0.0-beta.92
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/dist/index.js +1987 -679
- package/dist/mysql.js +1486 -616
- package/dist/postgres/metadata.js +1334 -263
- package/dist/postgres.js +3374 -2308
- package/dist/sqlite.js +1569 -627
- package/dist/standard.js +1982 -674
- package/package.json +2 -4
- package/src/internal/coercion/rules.ts +13 -1
- package/src/internal/column-state.d.ts +3 -3
- package/src/internal/column-state.ts +13 -12
- package/src/internal/column.ts +8 -8
- package/src/internal/datatypes/define.ts +5 -0
- package/src/internal/datatypes/lookup.ts +67 -18
- package/src/internal/datatypes/matrix.ts +903 -0
- package/src/internal/datatypes/shape.ts +2 -0
- package/src/internal/dialect-renderers/mysql.ts +6 -4
- package/src/internal/dialect-renderers/postgres.ts +6 -4
- package/src/internal/dialect-renderers/sqlite.ts +6 -4
- package/src/internal/dialect.ts +1 -1
- package/src/internal/executor.ts +56 -43
- package/src/internal/json/path-access.ts +351 -0
- package/src/internal/query.d.ts +1 -1
- package/src/internal/query.ts +1 -1
- package/src/internal/runtime/driver-value-mapping.ts +3 -3
- package/src/internal/runtime/schema.ts +28 -38
- package/src/internal/runtime/value.ts +20 -23
- package/src/internal/scalar.d.ts +1 -1
- package/src/internal/scalar.ts +2 -1
- package/src/internal/schema-derivation.d.ts +7 -7
- package/src/internal/schema-derivation.ts +11 -11
- package/src/internal/standard-dsl.ts +118 -28
- package/src/internal/table.ts +451 -120
- package/src/mysql/column.ts +6 -6
- package/src/mysql/datatypes/index.ts +1 -0
- package/src/mysql/datatypes/spec.ts +6 -176
- package/src/mysql/executor.ts +4 -6
- package/src/mysql/function/temporal.ts +1 -1
- package/src/mysql/internal/dsl.ts +113 -16
- package/src/mysql/json.ts +1 -33
- package/src/mysql/renderer.ts +13 -6
- package/src/mysql/type.ts +60 -0
- package/src/mysql.ts +3 -1
- package/src/postgres/check.ts +1 -0
- package/src/postgres/column.ts +11 -11
- package/src/postgres/datatypes/index.ts +1 -0
- package/src/postgres/datatypes/spec.ts +6 -260
- package/src/postgres/executor.ts +4 -6
- package/src/postgres/foreign-key.ts +24 -0
- package/src/postgres/function/temporal.ts +1 -1
- package/src/postgres/index.ts +1 -0
- package/src/postgres/internal/dsl.ts +119 -21
- package/src/postgres/json-extension.ts +7 -0
- package/src/postgres/json.ts +726 -173
- package/src/postgres/jsonb.ts +0 -1
- package/src/postgres/primary-key.ts +24 -0
- package/src/postgres/renderer.ts +13 -6
- package/src/postgres/schema-management.ts +1 -6
- package/src/postgres/schema.ts +16 -8
- package/src/postgres/table.ts +111 -113
- package/src/postgres/type.ts +86 -4
- package/src/postgres/unique.ts +32 -0
- package/src/postgres.ts +12 -6
- package/src/sqlite/column.ts +6 -6
- package/src/sqlite/datatypes/index.ts +1 -0
- package/src/sqlite/datatypes/spec.ts +6 -94
- package/src/sqlite/executor.ts +4 -6
- package/src/sqlite/function/temporal.ts +1 -1
- package/src/sqlite/internal/dsl.ts +97 -5
- package/src/sqlite/json.ts +1 -32
- package/src/sqlite/renderer.ts +13 -6
- package/src/sqlite/type.ts +40 -0
- package/src/sqlite.ts +3 -1
- package/src/standard/cast.ts +113 -0
- package/src/standard/check.ts +17 -0
- package/src/standard/column.ts +10 -10
- package/src/standard/datatypes/index.ts +2 -2
- package/src/standard/datatypes/spec.ts +10 -96
- package/src/standard/foreign-key.ts +37 -0
- package/src/standard/function/temporal.ts +1 -1
- package/src/standard/index.ts +17 -0
- package/src/standard/json.ts +883 -0
- package/src/standard/primary-key.ts +17 -0
- package/src/standard/renderer.ts +31 -3
- package/src/standard/table.ts +25 -21
- package/src/standard/unique.ts +17 -0
- package/src/standard.ts +14 -0
- package/src/internal/table.d.ts +0 -174
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/internal/dialect.ts
CHANGED
|
@@ -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.
|
|
35
|
+
readonly runtimeSchema?: Schema.Top
|
|
36
36
|
readonly driverValueMapping?: Expression.DriverValueMapping
|
|
37
37
|
}
|
|
38
38
|
|
package/src/internal/executor.ts
CHANGED
|
@@ -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 "
|
|
6
|
-
import * as SqlError from "
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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.
|
|
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.
|
|
342
|
+
if ((Schema.is(schema as Schema.Top) as (value: unknown) => boolean)(normalized)) {
|
|
329
343
|
return normalized
|
|
330
344
|
}
|
|
331
345
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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.
|
|
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.
|
|
546
|
+
return Stream.mapArray(
|
|
528
547
|
sqlDriver.stream(rendered),
|
|
529
|
-
(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.
|
|
558
|
+
Stream.unwrap(
|
|
540
559
|
Effect.flatMap(SqlClient.SqlClient, (sql) =>
|
|
541
560
|
Effect.flatMap(
|
|
542
|
-
Effect.serviceOption(
|
|
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
|
|
583
|
+
* Runs an effect within the ambient `effect/unstable/sql` transaction service.
|
|
571
584
|
*
|
|
572
|
-
*
|
|
573
|
-
*
|
|
585
|
+
* Nested calls rely on the underlying client transaction implementation for
|
|
586
|
+
* savepoint behavior.
|
|
574
587
|
*/
|
|
575
|
-
export const
|
|
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
|
+
}
|
package/src/internal/query.d.ts
CHANGED
|
@@ -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.
|
|
773
|
+
readonly runtimeSchema?: Schema.Top | undefined;
|
|
774
774
|
readonly nullability: Nullable;
|
|
775
775
|
readonly dialect: Dialect;
|
|
776
776
|
readonly kind?: Kind | undefined;
|
package/src/internal/query.ts
CHANGED
|
@@ -2459,7 +2459,7 @@ export const makeExpression = <
|
|
|
2459
2459
|
state: {
|
|
2460
2460
|
readonly runtime: Runtime
|
|
2461
2461
|
readonly dbType: Db
|
|
2462
|
-
readonly runtimeSchema?: Schema.
|
|
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.
|
|
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.
|
|
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.
|
|
108
|
+
schema: Schema.Top | undefined,
|
|
109
109
|
value: unknown
|
|
110
110
|
): { readonly value: unknown; readonly encoded: boolean } => {
|
|
111
111
|
if (schema === undefined) {
|