Skip to content

Commit c736aa4

Browse files
committed
refactor: codec as generic type
1 parent 671b6c5 commit c736aa4

9 files changed

Lines changed: 75 additions & 46 deletions

File tree

src/types/Codec.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Bidirectional transformation codec.
3+
*
4+
* Generic pattern for encoding and decoding between two type representations.
5+
* Useful for serialization, API transformations, or any A ↔ B conversion.
6+
*
7+
* @example
8+
* ```typescript
9+
* const dateCodec = createCodec({
10+
* encode: (date: Date) => date.toISOString(),
11+
* decode: (str: string) => new Date(str)
12+
* })
13+
*
14+
* const encoded = dateCodec.encode(new Date()) // "2024-01-01T00:00:00.000Z"
15+
* const decoded = dateCodec.decode(encoded) // Date object
16+
* ```
17+
*/
18+
export interface Codec<$Decoded = any, $Encoded = any> {
19+
/**
20+
* Transform from decoded (application) type to encoded (wire) type.
21+
*/
22+
encode: (value: $Decoded) => $Encoded
23+
24+
/**
25+
* Transform from encoded (wire) type to decoded (application) type.
26+
*/
27+
decode: (value: $Encoded) => $Decoded
28+
29+
/**
30+
* Type witness for encoded type (compile-time only).
31+
*/
32+
_typeEncoded: $Encoded
33+
34+
/**
35+
* Type witness for decoded type (compile-time only).
36+
*/
37+
_typeDecoded: $Decoded
38+
}
39+
40+
/**
41+
* Create a codec from encode and decode functions.
42+
*
43+
* @param codec - Object with encode and decode functions
44+
* @returns Typed Codec instance
45+
*/
46+
export const createCodec = <$Decoded, $Encoded>(codec: {
47+
encode: (value: $Decoded) => $Encoded
48+
decode: (value: $Encoded) => $Decoded
49+
}): Codec<$Decoded, $Encoded> => codec as any
50+
51+
/**
52+
* Generic mapper function type for codec operations.
53+
*/
54+
export type Mapper = (value: any) => any

src/types/Schema/_.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Ts } from '@wollybeard/kit'
22
import { describe, expect, test } from 'vitest'
3+
import { createCodec } from '../Codec.js'
34
import { Schema } from './_.js'
45

56
describe('LookupCustomScalarOrFallbackToUnknown', () => {
@@ -24,7 +25,7 @@ describe('LookupCustomScalarOrFallbackToUnknown', () => {
2425
const DateScalar: DateScalar = {
2526
kind: 'Scalar',
2627
name: 'Date',
27-
codec: Schema.Scalar.createCodec({ encode: () => '', decode: () => new Date() }),
28+
codec: createCodec({ encode: () => '', decode: () => new Date() }),
2829
}
2930

3031
// Custom scalar in registry should resolve
@@ -53,7 +54,7 @@ describe('lookupCustomScalarOrFallbackToUnknown (runtime)', () => {
5354
const DateScalar = {
5455
kind: 'Scalar' as const,
5556
name: 'Date',
56-
codec: Schema.Scalar.createCodec({ encode: () => '', decode: () => new Date() }),
57+
codec: createCodec({ encode: () => '', decode: () => new Date() }),
5758
}
5859
const scalars = { Date: DateScalar }
5960

src/types/Schema/helpers.ts

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
import type { Schema } from './_.js'
2-
import * as Nodes from './nodes/__.js'
3-
import type { List } from './nodes/List.js'
4-
import type { Nullable } from './nodes/Nullable.js'
5-
import * as Standard from './standard/scalars/scalars.js'
2+
import { Nodes } from './nodes/_.js'
3+
import { Standard } from './standard/_.js'
64

75
/**
86
* Unwrap a type to get its named type, ditching inline types like List and Nullable.
97
*/
108
// todo extends any because of infinite depth issue in generated schema types
119
// dprint-ignore
1210
export type GetNamedType<$Type> =
13-
$Type extends List<infer $innerType> ? GetNamedType<$innerType> :
14-
$Type extends Nullable<infer $innerType> ? GetNamedType<$innerType> :
11+
$Type extends Nodes.List<infer $innerType> ? GetNamedType<$innerType> :
12+
$Type extends Nodes.Nullable<infer $innerType> ? GetNamedType<$innerType> :
1513
$Type extends Nodes.__typename ? $Type :
1614
$Type
1715

@@ -33,11 +31,11 @@ export type Define<$Type extends Partial<Schema>> = $Type & Schema
3331
// dprint-ignore
3432
export type LookupCustomScalarOrFallbackToUnknown<$Name extends string, $Scalars extends Nodes.Scalar.Registry> =
3533
$Name extends keyof $Scalars['map'] ? $Scalars['map'][$Name]
36-
: $Name extends 'String' ? typeof Standard.String
37-
: $Name extends 'Int' ? typeof Standard.Int
38-
: $Name extends 'Float' ? typeof Standard.Float
39-
: $Name extends 'Boolean' ? typeof Standard.Boolean
40-
: $Name extends 'ID' ? typeof Standard.ID
34+
: $Name extends 'String' ? typeof Standard.Scalars.String
35+
: $Name extends 'Int' ? typeof Standard.Scalars.Int
36+
: $Name extends 'Float' ? typeof Standard.Scalars.Float
37+
: $Name extends 'Boolean' ? typeof Standard.Scalars.Boolean
38+
: $Name extends 'ID' ? typeof Standard.Scalars.ID
4139
: typeof Nodes.Scalar.UnknownScalar
4240

4341
/**
@@ -61,15 +59,15 @@ export const lookupCustomScalarOrFallbackToUnknown = (scalars: Nodes.Scalar.Scal
6159
// Handle standard GraphQL scalars
6260
switch (name) {
6361
case 'String':
64-
return Standard.String
62+
return Standard.Scalars.String
6563
case 'Int':
66-
return Standard.Int
64+
return Standard.Scalars.Int
6765
case 'Float':
68-
return Standard.Float
66+
return Standard.Scalars.Float
6967
case 'Boolean':
70-
return Standard.Boolean
68+
return Standard.Scalars.Boolean
7169
case 'ID':
72-
return Standard.ID
70+
return Standard.Scalars.ID
7371
default:
7472
return Nodes.Scalar.UnknownScalar
7573
}

src/types/Schema/nodes/Scalar/_.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import type { GraphqlKit } from '#src/lib/graphql-kit/_.js'
2-
import type { Codec } from './codec.js'
1+
import type { Codec } from '../../../Codec.js'
32

43
export * as Scalar from './__.js'
54

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
export * from '../ScalarCodecless.js'
2-
export * from './codec.js'
32
export * from './create.js'
43
export * from './helpers.js'

src/types/Schema/nodes/Scalar/codec.ts

Lines changed: 0 additions & 20 deletions
This file was deleted.

src/types/Schema/nodes/Scalar/helpers.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Obj, Ts } from '@wollybeard/kit'
2+
import { createCodec, type Mapper } from '../../../Codec.js'
23
import type { Scalar } from './_.js'
3-
import { createCodec, type Mapper } from './codec.js'
44

55
/**
66
* Scalar representing an unknown custom scalar type without a codec definition.
@@ -11,9 +11,6 @@ import { createCodec, type Mapper } from './codec.js'
1111
* The decoded type is `unknown`, forcing users to either:
1212
* 1. Define a proper codec for the custom scalar, or
1313
* 2. Explicitly handle the unknown type at runtime
14-
*
15-
* This aligns with gql-tada's behavior and provides better type safety than
16-
* the previous fallback to `string`.
1714
*/
1815
export const UnknownScalar: Scalar<string, unknown, any> = {
1916
kind: 'Scalar',

src/types/Schema/nodes/_.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * as Nodes from './__.js'

src/types/Schema/standard/scalars/scalars.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createCodec } from '../../nodes/Scalar/codec.js'
1+
import { createCodec } from '../../../Codec.js'
22
import { create } from '../../nodes/Scalar/create.js'
33

44
export const JavaScriptScalarCodecs = {

0 commit comments

Comments
 (0)