Skip to content

Commit b77f58a

Browse files
committed
Support custom scalars
1 parent 50282d8 commit b77f58a

File tree

9 files changed

+101
-3
lines changed

9 files changed

+101
-3
lines changed

eslint.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export default [
4949
}
5050
},
5151
{
52-
files: ['source/zod-graphql-query-builder/builder.ts'],
52+
files: ['source/zod-graphql-query-builder/builder.ts', 'source/zod-graphql-query-builder/query-schema.ts'],
5353
// those rules crash for some reason, we should re-enable them as soon as they are not crashing anymore
5454
rules: {
5555
'@typescript-eslint/no-unnecessary-type-assertion': 'off',

source/zod-graphql-client/entry-point.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ export function createGraphqlClient(clientOptions: GraphqlClientOptions): Graphq
1414
}
1515

1616
export type { QuerySchema } from '../zod-graphql-query-builder/entry-point.js';
17-
export { enumValue, graphqlFieldOptions, variablePlaceholder } from '../zod-graphql-query-builder/entry-point.js';
17+
export {
18+
customScalar,
19+
enumValue,
20+
graphqlFieldOptions,
21+
variablePlaceholder
22+
} from '../zod-graphql-query-builder/entry-point.js';
1823
export type { GraphqlClient, GraphqlOverHttpOperationRequestPayload } from './client.js';
1924
export type { OperationErrorDetails } from './operation-error.js';
2025
export { GraphqlOperationError } from './operation-error.js';

source/zod-graphql-client/readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ Some functions from `@schema-hub/zod-graphql-query-builder` are re-exported for
191191
- `graphqlFieldOptions()`
192192
- `enumValue()`
193193
- `variablePlaceholder()`
194+
- `customScalar()`
194195

195196
For more details, see the [`@schema-hub/zod-graphql-query-builder` documentation](../zod-graphql-query-builder/readme.md).
196197

source/zod-graphql-query-builder/builder.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { oneLine } from 'common-tags';
33
import assert from 'node:assert';
44
import { z } from 'zod';
55
import { createQueryBuilder, type OperationOptions, type QueryBuilder } from './builder.js';
6+
import { createCustomScalarSchema } from './custom-scalar.js';
67
import type { QuerySchema } from './query-schema.js';
78
import { variablePlaceholder } from './values/variable-placeholder.js';
89

@@ -634,4 +635,31 @@ function checkQuery(testCase: QueryTestCase): TestFn {
634635
expectedQuery: `${operationType} { foo, bar }`
635636
})
636637
);
638+
639+
test(
640+
`builds a ${operationType} with a custom scalar`,
641+
checkQuery({
642+
type: operationType,
643+
buildSchema() {
644+
return z
645+
.object({
646+
foo: createCustomScalarSchema(z.object({ bar: z.record(z.string()) }).strip())
647+
})
648+
.strict();
649+
},
650+
expectedQuery: `${operationType} { foo }`
651+
})
652+
);
653+
});
654+
655+
test('a schema with custom scalar validates correctly', () => {
656+
const schema = z
657+
.object({
658+
foo: createCustomScalarSchema(z.object({ bar: z.record(z.string()) }).strip())
659+
})
660+
.strict();
661+
662+
const result = schema.safeParse({ foo: { bar: 'bar' } });
663+
664+
assert.strictEqual(result.error?.issues[0]?.message, 'Expected object, received string');
637665
});

source/zod-graphql-query-builder/builder.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { z, ZodArray, ZodDiscriminatedUnion, type ZodLazy, type ZodTuple, ZodUndefined } from 'zod';
2+
import { isCustomScalarSchema } from './custom-scalar.js';
23
import {
34
type FieldSchema,
45
type FieldShape,
@@ -178,12 +179,17 @@ export function createQueryBuilder(): QueryBuilder {
178179
};
179180
}
180181

181-
// eslint-disable-next-line max-statements -- no good idea how to refactor at the moment
182+
// eslint-disable-next-line max-statements, complexity -- no good idea how to refactor at the moment
182183
function serializeFieldSchema(
183184
fieldName: string,
184185
fieldSchema: FieldSchema
185186
): NormalizedGraphqlValue {
186187
const fieldSelector = serializedFieldSelector(fieldName, fieldSchema);
188+
189+
if (isCustomScalarSchema(fieldSchema)) {
190+
return fieldSelector;
191+
}
192+
187193
const unwrappedSchema = unwrapFieldSchema(fieldSchema);
188194

189195
if (unwrappedSchema instanceof ZodUndefined) {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { z, type ZodLazy, type ZodTypeAny } from 'zod';
2+
3+
const customScalarSymbol: unique symbol = Symbol('custom-scalar');
4+
5+
export type CustomScalarSchema<Schema extends ZodTypeAny> = ZodLazy<Schema> & {
6+
[customScalarSymbol]: true;
7+
};
8+
9+
export function isCustomScalarSchema(schema: ZodTypeAny): schema is CustomScalarSchema<ZodTypeAny> {
10+
return Object.hasOwn(schema, customScalarSymbol);
11+
}
12+
13+
export function createCustomScalarSchema<Schema extends ZodTypeAny>(
14+
schema: Schema
15+
): CustomScalarSchema<Schema> {
16+
const wrappedSchema = z.lazy(() => {
17+
return schema;
18+
}) as CustomScalarSchema<Schema>;
19+
20+
wrappedSchema[customScalarSymbol] = true;
21+
22+
return wrappedSchema;
23+
}

source/zod-graphql-query-builder/entry-point.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const graphqlFieldOptions = builder.registerFieldOptions;
66
export const buildGraphqlQuery = builder.buildQuery;
77
export const buildGraphqlMutation = builder.buildMutation;
88

9+
export { createCustomScalarSchema as customScalar } from './custom-scalar.js';
910
export { enumValue } from './values/enum.js';
1011
export { variablePlaceholder } from './values/variable-placeholder.js';
1112

source/zod-graphql-query-builder/query-schema.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
type ZodUndefined,
1818
type ZodUnion
1919
} from 'zod';
20+
import { type CustomScalarSchema, isCustomScalarSchema } from './custom-scalar.js';
2021

2122
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions,@typescript-eslint/no-empty-object-type -- generic type alias can’t be circular
2223
export interface StrictObjectSchema<Shape extends ZodRawShape>
@@ -44,6 +45,7 @@ export declare type FragmentUnionOptionSchema = ZodObject<
4445
// eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style -- see https://github.com/microsoft/TypeScript/pull/57293
4546
export type FieldShape = { readonly [FieldName: string]: FieldSchema; };
4647
export type NonWrappedFieldSchema =
48+
| CustomScalarSchema<ZodTypeAny>
4749
| PrimitiveSchema
4850
| StrictObjectSchema<FieldShape>
4951
| ZodArray<FieldSchema>
@@ -58,6 +60,10 @@ export type WrappedFieldSchema =
5860
export type FieldSchema = NonWrappedFieldSchema | WrappedFieldSchema;
5961

6062
function isWrappedFieldSchema(schema: FieldSchema): schema is WrappedFieldSchema {
63+
if (isCustomScalarSchema(schema)) {
64+
return false;
65+
}
66+
6167
return schema instanceof ZodLazy || schema instanceof ZodEffects || schema instanceof ZodNullable;
6268
}
6369

source/zod-graphql-query-builder/readme.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,34 @@ const query = buildGraphqlQuery(mySchema);
129129
query { foo { ... on A { __typename, valueA }, ... on B { __typename, valueB } } }
130130
```
131131

132+
### Working with custom scalars
133+
134+
**Input:**
135+
136+
```typescript
137+
import { buildGraphqlQuery, customScalar } from '@schema-hub/zod-graphql-query-builder';
138+
import { z } from 'zod';
139+
140+
const mySchema = z
141+
.object({
142+
foo: customScalar(
143+
z
144+
.object({
145+
bar: z.record(z.string())
146+
})
147+
.strip()
148+
)
149+
})
150+
.strict();
151+
const query = buildGraphqlQuery(mySchema);
152+
```
153+
154+
**Built query:**
155+
156+
```graphql
157+
query { foo }
158+
```
159+
132160
## Mutations
133161

134162
### Basic Mutation

0 commit comments

Comments
 (0)