Skip to content

Commit a105432

Browse files
committed
...
... ... ...
1 parent 439b7f7 commit a105432

8 files changed

Lines changed: 247 additions & 1 deletion

File tree

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@
125125
"tsd": "0.33.0",
126126
"tsx": "4.23.1",
127127
"typescript": "7.0.2",
128-
"typescript-6": "npm:typescript@6.0.3"
128+
"typescript-6": "npm:typescript@6.0.3",
129+
"zod": "4.4.3"
129130
},
130131
"packageManager": "pnpm@11.13.1+sha512.b2fc7683b8a6525414e7d13e1ba28caaddde96bf66ec540bfaeb7e702b81f3e0be4d1f295edf7f9fe0396740a8dce4509c582ddf79891f4543fea32d37645f25"
131132
}

pnpm-lock.yaml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import type { QueryResult } from '../../driver/database-connection.js'
2+
import type { StandardSchemaV1 } from '../../util/standard-schema.js'
3+
import type { UnknownRow } from '../../util/type-utils.js'
4+
import type {
5+
KyselyPlugin,
6+
PluginTransformQueryArgs,
7+
PluginTransformResultArgs,
8+
} from '../kysely-plugin.js'
9+
10+
export class StandardSchemaV1Plugin implements KyselyPlugin {
11+
readonly #schema: StandardSchemaV1
12+
13+
constructor(schema: StandardSchemaV1) {
14+
this.#schema = schema
15+
}
16+
17+
transformQuery(args: PluginTransformQueryArgs) {
18+
return args.node
19+
}
20+
21+
async transformResult(
22+
args: PluginTransformResultArgs,
23+
): Promise<QueryResult<UnknownRow>> {
24+
const { result } = args
25+
const { rows } = result
26+
27+
if (!Array.isArray(rows)) {
28+
throw new Error(
29+
'Result is not parseable! Ensure there is no missing returning/output clause or remove `$parseResult` from the chain.',
30+
)
31+
}
32+
33+
return {
34+
...result,
35+
rows: await Promise.all(
36+
rows.map(async (row, index) => {
37+
const validated = await this.#schema['~standard'].validate(row)
38+
39+
const { issues } = validated
40+
41+
if (issues) {
42+
throw new AggregateError(
43+
this.#processIssues(issues),
44+
`Result row at index ${index} has failed parsing! Checks \`.errors\` for more details.`,
45+
)
46+
}
47+
48+
return validated.value as UnknownRow
49+
}),
50+
),
51+
}
52+
}
53+
54+
#processIssues(issues: readonly StandardSchemaV1.Issue[]): string[] {
55+
return issues.map(
56+
({ message, path }) =>
57+
`${message}${
58+
path
59+
? ` @ ${path
60+
.map((segment) =>
61+
typeof segment === 'object' ? segment.key : segment,
62+
)
63+
.join('->')}`
64+
: ''
65+
}`,
66+
)
67+
}
68+
}

src/query-builder/delete-query-builder.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ import type {
8282
ExecuteTakeFirstOrThrowOptions,
8383
} from '../util/executable.js'
8484
import type { AbortableQueryOptions } from '../util/abort.js'
85+
import type { StandardSchemaV1 } from '../util/standard-schema.js'
86+
import { StandardSchemaV1Plugin } from '../plugin/standard-schema/standard-schema-plugin.js'
8587

8688
export class DeleteQueryBuilder<DB, TB extends keyof DB, O>
8789
implements
@@ -1029,6 +1031,15 @@ export class DeleteQueryBuilder<DB, TB extends keyof DB, O>
10291031
return new DeleteQueryBuilder(this.#props) as unknown as any
10301032
}
10311033

1034+
/**
1035+
* TODO: ...
1036+
*/
1037+
$parseResult<O2 extends Record<keyof O, unknown>>(
1038+
schema: StandardSchemaV1<O, O2>,
1039+
): Executable<O2> & Streamable<O2> {
1040+
return this.withPlugin(new StandardSchemaV1Plugin(schema)) as never
1041+
}
1042+
10321043
/**
10331044
* Returns a copy of this DeleteQueryBuilder instance with the given plugin installed.
10341045
*/

src/query-builder/insert-query-builder.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,13 @@ import type {
6363
SelectExpressionFromOutputExpression,
6464
} from './output-interface.js'
6565
import { OrActionNode } from '../operation-node/or-action-node.js'
66+
import { StandardSchemaV1Plugin } from '../plugin/standard-schema/standard-schema-plugin.js'
6667
import type {
6768
Executable,
6869
ExecuteTakeFirstOrThrowOptions,
6970
} from '../util/executable.js'
7071
import type { AbortableQueryOptions } from '../util/abort.js'
72+
import type { StandardSchemaV1 } from '../util/standard-schema.js'
7173

7274
export class InsertQueryBuilder<DB, TB extends keyof DB, out O>
7375
implements
@@ -1262,6 +1264,15 @@ export class InsertQueryBuilder<DB, TB extends keyof DB, out O>
12621264
return new InsertQueryBuilder(this.#props) as unknown as any
12631265
}
12641266

1267+
/**
1268+
* TODO: ...
1269+
*/
1270+
$parseResult<O2 extends Record<keyof O, unknown>>(
1271+
schema: StandardSchemaV1<O, O2>,
1272+
): Executable<O2> & Streamable<O2> {
1273+
return this.withPlugin(new StandardSchemaV1Plugin(schema)) as never
1274+
}
1275+
12651276
/**
12661277
* Returns a copy of this InsertQueryBuilder instance with the given plugin installed.
12671278
*/

src/query-builder/select-query-builder.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ import type {
8585
ExecuteTakeFirstOrThrowOptions,
8686
} from '../util/executable.js'
8787
import type { AbortableQueryOptions } from '../util/abort.js'
88+
import type { StandardSchemaV1 } from '../util/standard-schema.js'
89+
import { StandardSchemaV1Plugin } from '../plugin/standard-schema/standard-schema-plugin.js'
8890

8991
export interface SelectQueryBuilder<DB, TB extends keyof DB, O>
9092
extends
@@ -2111,6 +2113,13 @@ export interface SelectQueryBuilder<DB, TB extends keyof DB, O>
21112113
? SelectQueryBuilder<DB, TB, T>
21122114
: KyselyTypeError<`$assertType() call failed: The type passed in is not equal to the output type of the query.`>
21132115

2116+
/**
2117+
* TODO: ...
2118+
*/
2119+
$parseResult<O2 extends Record<keyof O, unknown>>(
2120+
schema: StandardSchemaV1<O, O2>,
2121+
): Executable<O2> & Streamable<O2>
2122+
21142123
/**
21152124
* Returns a copy of this SelectQueryBuilder instance with the given plugin installed.
21162125
*/
@@ -2622,6 +2631,12 @@ class SelectQueryBuilderImpl<
26222631
return new ExpressionWrapper(this.toOperationNode())
26232632
}
26242633

2634+
$parseResult<O2>(
2635+
schema: StandardSchemaV1<Partial<O>, O2>,
2636+
): Executable<O2> & Streamable<O2> {
2637+
return this.withPlugin(new StandardSchemaV1Plugin(schema)) as never
2638+
}
2639+
26252640
withPlugin(plugin: KyselyPlugin): SelectQueryBuilder<DB, TB, O> {
26262641
return new SelectQueryBuilderImpl({
26272642
...this.#props,

src/query-builder/update-query-builder.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ import type {
8888
ExecuteTakeFirstOrThrowOptions,
8989
} from '../util/executable.js'
9090
import type { AbortableQueryOptions } from '../util/abort.js'
91+
import type { StandardSchemaV1 } from '../util/standard-schema.js'
92+
import { StandardSchemaV1Plugin } from '../plugin/standard-schema/standard-schema-plugin.js'
9193

9294
export class UpdateQueryBuilder<DB, UT extends keyof DB, TB extends keyof DB, O>
9395
implements
@@ -1118,6 +1120,15 @@ export class UpdateQueryBuilder<DB, UT extends keyof DB, TB extends keyof DB, O>
11181120
return new UpdateQueryBuilder(this.#props) as unknown as any
11191121
}
11201122

1123+
/**
1124+
* TODO: ...
1125+
*/
1126+
$parseResult<O2 extends Record<keyof O, unknown>>(
1127+
schema: StandardSchemaV1<O, O2>,
1128+
): Executable<O2> & Streamable<O2> {
1129+
return this.withPlugin(new StandardSchemaV1Plugin(schema)) as never
1130+
}
1131+
11211132
/**
11221133
* Returns a copy of this UpdateQueryBuilder instance with the given plugin installed.
11231134
*/

src/util/standard-schema.ts

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/**
2+
* Copied from https://github.com/standard-schema/standard-schema/blob/70ca676f26e98cb1ebd24ae21330dfbcaca69f57/packages/spec/README.md
3+
*
4+
* MIT License
5+
*
6+
* Copyright (c) 2024 Colin McDonnell
7+
*/
8+
9+
// #########################
10+
// ### Standard Typed ###
11+
// #########################
12+
13+
/** The Standard Typed interface. This is a base type extended by other specs. */
14+
export interface StandardTypedV1<Input = unknown, Output = Input> {
15+
/** The Standard properties. */
16+
readonly '~standard': StandardTypedV1.Props<Input, Output>
17+
}
18+
19+
export declare namespace StandardTypedV1 {
20+
/** The Standard Typed properties interface. */
21+
export interface Props<Input = unknown, Output = Input> {
22+
/** The version number of the standard. */
23+
readonly version: 1
24+
/** The vendor name of the schema library. */
25+
readonly vendor: string
26+
/** Inferred types associated with the schema. */
27+
readonly types?: Types<Input, Output> | undefined
28+
}
29+
30+
/** The Standard Typed types interface. */
31+
export interface Types<Input = unknown, Output = Input> {
32+
/** The input type of the schema. */
33+
readonly input: Input
34+
/** The output type of the schema. */
35+
readonly output: Output
36+
}
37+
38+
/** Infers the input type of a Standard Typed. */
39+
export type InferInput<Schema extends StandardTypedV1> = NonNullable<
40+
Schema['~standard']['types']
41+
>['input']
42+
43+
/** Infers the output type of a Standard Typed. */
44+
export type InferOutput<Schema extends StandardTypedV1> = NonNullable<
45+
Schema['~standard']['types']
46+
>['output']
47+
}
48+
49+
// ##########################
50+
// ### Standard Schema ###
51+
// ##########################
52+
53+
/** The Standard Schema interface. */
54+
export interface StandardSchemaV1<Input = unknown, Output = Input> {
55+
/** The Standard Schema properties. */
56+
readonly '~standard': StandardSchemaV1.Props<Input, Output>
57+
}
58+
59+
export declare namespace StandardSchemaV1 {
60+
/** The Standard Schema properties interface. */
61+
export interface Props<
62+
Input = unknown,
63+
Output = Input,
64+
> extends StandardTypedV1.Props<Input, Output> {
65+
/** Validates unknown input values. */
66+
readonly validate: (
67+
value: unknown,
68+
options?: StandardSchemaV1.Options | undefined,
69+
) => Result<Output> | Promise<Result<Output>>
70+
}
71+
72+
/** The result interface of the validate function. */
73+
export type Result<Output> = SuccessResult<Output> | FailureResult
74+
75+
/** The result interface if validation succeeds. */
76+
export interface SuccessResult<Output> {
77+
/** The typed output value. */
78+
readonly value: Output
79+
/** A falsy value for `issues` indicates success. */
80+
readonly issues?: undefined
81+
}
82+
83+
export interface Options {
84+
/** Explicit support for additional vendor-specific parameters, if needed. */
85+
readonly libraryOptions?: Record<string, unknown> | undefined
86+
}
87+
88+
/** The result interface if validation fails. */
89+
export interface FailureResult {
90+
/** The issues of failed validation. */
91+
readonly issues: ReadonlyArray<Issue>
92+
}
93+
94+
/** The issue interface of the failure output. */
95+
export interface Issue {
96+
/** The error message of the issue. */
97+
readonly message: string
98+
/** The path of the issue, if any. */
99+
readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined
100+
}
101+
102+
/** The path segment interface of the issue. */
103+
export interface PathSegment {
104+
/** The key representing a path segment. */
105+
readonly key: PropertyKey
106+
}
107+
108+
/** The Standard types interface. */
109+
export interface Types<
110+
Input = unknown,
111+
Output = Input,
112+
> extends StandardTypedV1.Types<Input, Output> {}
113+
114+
/** Infers the input type of a Standard. */
115+
export type InferInput<Schema extends StandardTypedV1> =
116+
StandardTypedV1.InferInput<Schema>
117+
118+
/** Infers the output type of a Standard. */
119+
export type InferOutput<Schema extends StandardTypedV1> =
120+
StandardTypedV1.InferOutput<Schema>
121+
}

0 commit comments

Comments
 (0)