diff --git a/packages/type/src/reflection/type.ts b/packages/type/src/reflection/type.ts index e9e5bafa0..e94c142e5 100644 --- a/packages/type/src/reflection/type.ts +++ b/packages/type/src/reflection/type.ts @@ -1548,7 +1548,9 @@ export interface EntityOptions { * const param1 = typeToObject(data[0]); //yes * ``` */ -export type TypeAnnotation = { __meta?: never & [T, Options] }; +export type TypeAnnotation = { __meta?: [T, never | Options] }; + +export type ExtractTypeAnnotationOptions> = Exclude[1], never>; /** * Type to decorate an interface/object literal with entity information. @@ -1994,34 +1996,25 @@ export function registerTypeDecorator(decorator: TypeDecorator) { * type MyAnnotation1 = TypeAnnotation<'myAnnotation', T> * * //under the hood it is: - * type lowLevel1 = { __meta?: never & ['myAnnotation'] } - * type lowLevel2 = { __meta?: never & ['myAnnotation', T] } + * type lowLevel1 = { __meta?: ['myAnnotation', never] } + * type lowLevel2 = { __meta?: ['myAnnotation', never | T] } * ``` */ export function getAnnotationMeta(type: TypeObjectLiteral): { id: string, params: Type[] } | undefined { const meta = getProperty(type, '__meta'); - if (!meta || !meta.optional) return; - let tuple: TypeTuple | undefined = undefined; - if (meta.type.kind === ReflectionKind.intersection) { - if (meta.type.types.length === 1 && meta.type.types[0].kind === ReflectionKind.tuple) { - tuple = meta.type.types[0] as TypeTuple; - } - if (!tuple && meta.type.types.length === 2) { - tuple = meta.type.types.find(v => v.kind === ReflectionKind.tuple) as TypeTuple | undefined; - if (tuple && !meta.type.types.find(v => v.kind === ReflectionKind.never)) { - tuple = undefined; - } - } - } else if (meta.type.kind === ReflectionKind.tuple) { - tuple = meta.type; - } + if (!meta || !meta.optional) return; + if (meta.type.kind !== ReflectionKind.tuple) return; - if (!tuple) return; + const id = meta.type.types[0]; - const id = tuple.types[0]; if (!id || id.type.kind !== ReflectionKind.literal || 'string' !== typeof id.type.literal) return; - const params = tuple.types.slice(1).map(v => v.type); + + const options = meta.type.types[1]; + if (options.type.kind !== ReflectionKind.union) return; + if (options.type.types[0].kind !== ReflectionKind.never) return; + + const params = options.type.types.slice(1); return { id: id.type.literal, params }; } diff --git a/packages/type/tests/annotations.spec.ts b/packages/type/tests/annotations.spec.ts new file mode 100644 index 000000000..3c013b003 --- /dev/null +++ b/packages/type/tests/annotations.spec.ts @@ -0,0 +1,34 @@ +import { test, expect } from '@jest/globals'; +import { ExtractTypeAnnotationOptions, getAnnotationMeta, TypeAnnotation, TypeObjectLiteral } from '../src/reflection/type.js'; +import { typeOf } from '../src/reflection/reflection.js'; +import { expectEqualType } from './utils.js'; + +test('extract type annotation options', () => { + type Skip = TypeAnnotation<'skip', { if: boolean }>; + + type SkipOptions = ExtractTypeAnnotationOptions; + + const options: SkipOptions = { + if: true, + }; +}); + + +test('getAnnotationMeta', () => { + type LevelOptions = { do: boolean }; + + const levelOptionsType = typeOf(); + + type Level = TypeAnnotation<'level', LevelOptions>; + + const levelType = typeOf() as TypeObjectLiteral; + + const meta = getAnnotationMeta(levelType); + + expect(meta).toBeDefined(); + + expect(meta!.id).toBe('level'); + + expectEqualType(meta!.params[0], levelOptionsType); + +}); diff --git a/packages/type/tests/integration4.spec.ts b/packages/type/tests/integration4.spec.ts index f8eb70413..73e051f37 100644 --- a/packages/type/tests/integration4.spec.ts +++ b/packages/type/tests/integration4.spec.ts @@ -9,7 +9,7 @@ */ import { expect, test } from '@jest/globals'; -import { assertType, AutoIncrement, Group, groupAnnotation, PrimaryKey, ReflectionKind } from '../src/reflection/type.js'; +import { assertType, AutoIncrement, ExtractTypeAnnotationOptions, Group, groupAnnotation, PrimaryKey, ReflectionKind, TypeAnnotation } from '../src/reflection/type.js'; import { typeOf } from '../src/reflection/reflection.js'; import { cast } from '../src/serializer-facade.js';