Skip to content

Commit 5870005

Browse files
committed
fix(type): make serializer API consistent
move options to callback away from the factory
1 parent a6de70e commit 5870005

File tree

5 files changed

+84
-14
lines changed

5 files changed

+84
-14
lines changed

packages/sql/src/sql-adapter.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,7 @@ export class RawQuery<T> implements FindQuery<T> {
565565
const connection = await this.connectionPool.getConnection(this.session.logger, this.session.assignedTransaction, this.session.stopwatch);
566566

567567
try {
568-
const caster = castFunction(undefined, undefined, undefined, this.type);
568+
const caster = castFunction(undefined, undefined, this.type);
569569
const res = await connection.execAndReturnAll(sql.sql, sql.params);
570570
return (isArray(res) ? [...res] : []).map(v => caster(v)) as T[];
571571
} finally {

packages/type/src/reflection/reflection.ts

+16
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,14 @@ export class ReflectionProperty {
496496

497497
data: { [name: string]: any } = {};
498498

499+
/**
500+
* The type of the property, not the property itself.
501+
*
502+
* Note: If the property is optional via `property?: T`, this information
503+
* is not available here. It's on `property`.
504+
* Use `isOptional()` instead, which handles this case plus the case
505+
* where optionality is given via union of T and undefined.
506+
*/
499507
type: Type;
500508

501509
symbol: symbol;
@@ -587,6 +595,10 @@ export class ReflectionProperty {
587595
return groupAnnotation.getAnnotations(this.getType());
588596
}
589597

598+
isInGroup(...group: string[]): boolean {
599+
return this.getGroups().some(v => group.includes(v));
600+
}
601+
590602
getExcluded(): string[] {
591603
return excludedAnnotation.getAnnotations(this.getType());
592604
}
@@ -1265,6 +1277,10 @@ export class ReflectionClass<T> {
12651277
return this.properties;
12661278
}
12671279

1280+
getPropertiesInGroup(...group: string[]): ReflectionProperty[] {
1281+
return this.properties.filter(v => v.isInGroup(...group));
1282+
}
1283+
12681284
getMethodNames(): (string | number | symbol)[] {
12691285
return this.methodNames;
12701286
}

packages/type/src/serializer-facade.ts

+23-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
import { getClassTypeFromInstance } from '@deepkit/core';
22
import { ReceiveType, resolveReceiveType } from './reflection/reflection.js';
3-
import { getPartialSerializeFunction, getSerializeFunction, NamingStrategy, SerializationOptions, serializer, Serializer, TemplateRegistry } from './serializer.js';
3+
import {
4+
getPartialSerializeFunction,
5+
getSerializeFunction,
6+
NamingStrategy,
7+
SerializationOptions,
8+
SerializeFunction,
9+
serializer,
10+
Serializer,
11+
TemplateRegistry
12+
} from './serializer.js';
413
import { JSONPartial, JSONSingle } from './utils.js';
514
import { typeInfer } from './reflection/processor.js';
615
import { assert } from './typeguard.js';
@@ -22,17 +31,19 @@ export function cast<T>(data: JSONPartial<T> | unknown, options?: SerializationO
2231
}
2332

2433
/**
25-
* Casts/coerces a given data structure to the target data type.
26-
*
27-
* Same as deserialize().
34+
* Same as cast but returns a ready to use function. Used to improve performance.
2835
*/
29-
export function castFunction<T>(options?: SerializationOptions, serializerToUse: Serializer = serializer, namingStrategy?: NamingStrategy, type?: ReceiveType<T>): (data: JSONPartial<T> | unknown) => T {
36+
export function castFunction<T>(serializerToUse: Serializer = serializer, namingStrategy?: NamingStrategy, type?: ReceiveType<T>): (data: JSONPartial<T> | unknown, options?: SerializationOptions) => T {
3037
const fn = getSerializeFunction(resolveReceiveType(type), serializerToUse.deserializeRegistry, namingStrategy);
31-
return (data: JSONPartial<T> | unknown) => fn(data, options);
38+
return (data: JSONPartial<T> | unknown, options?: SerializationOptions) => {
39+
const item = fn(data, options);
40+
assert(item, undefined, type);
41+
return item;
42+
}
3243
}
3344

3445
/**
35-
* Deserialize given data structure from JSON data objects to JavaScript objects.
46+
* Deserialize given data structure from JSON data objects to JavaScript objects, without running any validators.
3647
*
3748
* Types that are already correct will be used as-is.
3849
*
@@ -44,7 +55,7 @@ export function castFunction<T>(options?: SerializationOptions, serializerToUse:
4455
* const data = deserialize<Data>({created: '2009-02-13T23:31:30.123Z'});
4556
* //data is {created: Date(2009-02-13T23:31:30.123Z)}
4657
*
47-
* @throws ValidationError when serialization or validation fails.
58+
* @throws ValidationError when deserialization fails.
4859
* ```
4960
*/
5061
export function deserialize<T>(data: JSONPartial<T> | unknown, options?: SerializationOptions, serializerToUse: Serializer = serializer, namingStrategy?: NamingStrategy, type?: ReceiveType<T>): T {
@@ -55,7 +66,7 @@ export function deserialize<T>(data: JSONPartial<T> | unknown, options?: Seriali
5566
/**
5667
* Same as deserialize but returns a ready to use function. Used to improve performance.
5768
*/
58-
export function deserializeFunction<T>(options?: SerializationOptions, serializerToUse: Serializer = serializer, namingStrategy?: NamingStrategy, type?: ReceiveType<T>): (data: JSONPartial<T> | unknown) => T {
69+
export function deserializeFunction<T>(serializerToUse: Serializer = serializer, namingStrategy?: NamingStrategy, type?: ReceiveType<T>): SerializeFunction<any, T> {
5970
return getSerializeFunction(resolveReceiveType(type), serializerToUse.deserializeRegistry, namingStrategy);
6071
}
6172

@@ -175,7 +186,7 @@ export function serialize<T>(data: T, options?: SerializationOptions, serializer
175186
/**
176187
* Same as serialize but returns a ready to use function. Used to improve performance.
177188
*/
178-
export function serializeFunction<T>(options?: SerializationOptions, serializerToUse: Serializer = serializer, namingStrategy?: NamingStrategy, type?: ReceiveType<T>): (data: T) => any {
189+
export function serializeFunction<T>(serializerToUse: Serializer = serializer, namingStrategy?: NamingStrategy, type?: ReceiveType<T>): SerializeFunction<T> {
179190
return getSerializeFunction(resolveReceiveType(type), serializerToUse.serializeRegistry, namingStrategy);
180191
}
181192

@@ -193,6 +204,8 @@ export function cloneClass<T>(target: T, options?: SerializationOptions): T {
193204
/**
194205
* Tries to deserialize given data as T, and throws an error if it's not possible or validation after conversion fails.
195206
*
207+
* @deprecated use cast() instead
208+
*
196209
* @throws ValidationError when serialization or validation fails.
197210
*/
198211
export function validatedDeserialize<T>(data: any, options?: SerializationOptions, serializerToUse: Serializer = serializer, namingStrategy?: NamingStrategy, type?: ReceiveType<T>) {

packages/type/src/serializer.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ function isGroupAllowed(options: SerializationOptions, groupNames: string[]): bo
169169
}
170170

171171

172-
export type SerializeFunction = (data: any, state?: SerializationOptions) => any;
172+
export type SerializeFunction<T = any, R = any> = (data: T, state?: SerializationOptions) => R;
173173

174174
export function getPartialType(type: TypeClass | TypeObjectLiteral) {
175175
const jitContainer = getTypeJitContainer(type);

packages/type/tests/reflection.spec.ts

+43-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { test, expect } from '@jest/globals';
2-
import { typeOf } from '../src/reflection/reflection.js';
3-
import { assertType, ReflectionKind } from '../src/reflection/type.js';
2+
import { ReflectionClass, typeOf } from '../src/reflection/reflection.js';
3+
import { assertType, Group, ReflectionKind, stringifyType } from '../src/reflection/type.js';
44

55
test('union empty interfaces', () => {
66
interface Dog {
@@ -18,3 +18,44 @@ test('union empty interfaces', () => {
1818
assertType(type.types[1], ReflectionKind.objectLiteral);
1919
expect(type.types).toHaveLength(2);
2020
});
21+
22+
test('groups', () => {
23+
class Model {
24+
created!: Date & Group<'a'>;
25+
modified!: Date & Group<'a'> & Group<'b'>;
26+
27+
field!: (string | number) & Group<'a'>;
28+
field2!: (string | number) & Group<'a'> & Group<'b'>;
29+
}
30+
31+
const schema = ReflectionClass.from(Model);
32+
33+
expect(schema.getPropertiesInGroup('a').map(v => v.name)).toEqual(['created', 'modified', 'field', 'field2']);
34+
expect(schema.getPropertiesInGroup('b').map(v => v.name)).toEqual(['modified', 'field2']);
35+
36+
const created = schema.getProperty('created');
37+
expect(created.getGroups()).toEqual(['a']);
38+
expect(created.isInGroup('a')).toBe(true);
39+
expect(created.isInGroup('b')).toBe(false);
40+
expect(created.isInGroup('c')).toBe(false);
41+
42+
const modified = schema.getProperty('modified');
43+
expect(modified.getGroups()).toEqual(['a', 'b']);
44+
expect(modified.isInGroup('a')).toBe(true);
45+
expect(modified.isInGroup('b')).toBe(true);
46+
expect(modified.isInGroup('c')).toBe(false);
47+
48+
const field = schema.getProperty('field');
49+
expect(field.getGroups()).toEqual(['a']);
50+
expect(field.isInGroup('a')).toBe(true);
51+
expect(field.isInGroup('b')).toBe(false);
52+
expect(field.isInGroup('c')).toBe(false);
53+
expect(stringifyType(field.type)).toBe(`string | number`);
54+
55+
const field2 = schema.getProperty('field2');
56+
expect(field2.getGroups()).toEqual(['a', 'b']);
57+
expect(field2.isInGroup('a')).toBe(true);
58+
expect(field2.isInGroup('b')).toBe(true);
59+
expect(field2.isInGroup('c')).toBe(false);
60+
expect(stringifyType(field2.type)).toBe(`string | number`);
61+
});

0 commit comments

Comments
 (0)