diff --git a/packages/app/src/command.ts b/packages/app/src/command.ts index 1a6fe3032..8a4003106 100644 --- a/packages/app/src/command.ts +++ b/packages/app/src/command.ts @@ -9,12 +9,12 @@ */ import { ClassType } from '@deepkit/core'; -import { ClassDecoratorResult, createClassDecoratorContext, createPropertyDecoratorContext, FreeFluidDecorator, PropertyApiTypeInterface } from '@deepkit/type'; +import { ClassDecoratorResult, createClassDecoratorContext, createPropertyDecoratorContext, FreeFluidDecorator, PropertyApiTypeInterface, TypeAnnotation } from '@deepkit/type'; /** * Flag is a command line argument that is prefixed with `--` and can have a value. */ -export type Flag = { __meta?: ['cli:flag', Options] }; +export type Flag = TypeAnnotation<'cli:flag', Options>; class ArgDefinitions { name: string = ''; diff --git a/packages/desktop-ui/src/components/app/state.ts b/packages/desktop-ui/src/components/app/state.ts index b76515151..44fd51919 100644 --- a/packages/desktop-ui/src/components/app/state.ts +++ b/packages/desktop-ui/src/components/app/state.ts @@ -16,7 +16,7 @@ import onChange from 'on-change'; * } * ``` */ -export type PartOfUrl = { __meta?: ['partOfUrl'] }; +export type PartOfUrl = { __meta?: never & ['partOfUrl'] }; export type FilterActions = { [name in keyof T]: T[name] extends (a: infer A extends [...a: any[]], ...args: any[]) => infer R ? (...args: A) => R : never }; diff --git a/packages/http/src/model.ts b/packages/http/src/model.ts index 594932ed9..39204d52a 100644 --- a/packages/http/src/model.ts +++ b/packages/http/src/model.ts @@ -12,7 +12,7 @@ import { IncomingMessage, OutgoingHttpHeader, OutgoingHttpHeaders, ServerRespons import { UploadedFile } from './router.js'; import * as querystring from 'querystring'; import { Writable } from 'stream'; -import { metaAnnotation, ReflectionKind, Type, ValidationErrorItem } from '@deepkit/type'; +import { metaAnnotation, ReflectionKind, Type, ValidationErrorItem, TypeAnnotation } from '@deepkit/type'; import { asyncOperation, isArray } from '@deepkit/core'; export class HttpResponse extends ServerResponse { @@ -116,12 +116,12 @@ export class ValidatedBody { } } -export type HttpBody = T & { __meta?: ['httpBody'] }; -export type HttpBodyValidation = ValidatedBody & { __meta?: ['httpBodyValidation'] }; -export type HttpPath = T & { __meta?: ['httpPath', Options] }; -export type HttpHeader = T & { __meta?: ['httpHeader', Options] }; -export type HttpQuery = T & { __meta?: ['httpQuery', Options] }; -export type HttpQueries = T & { __meta?: ['httpQueries', Options] }; +export type HttpBody = T & TypeAnnotation<'httpBody'>; +export type HttpBodyValidation = ValidatedBody & TypeAnnotation<'httpBodyValidation'>; +export type HttpPath = T & TypeAnnotation<'httpPath', Options>; +export type HttpHeader = T & TypeAnnotation<'httpHeader'>; +export type HttpQuery = T & TypeAnnotation<'httpQuery', Options>; +export type HttpQueries = T & TypeAnnotation<'httpQueries', Options>; /** * For all parameters used in the URL path, a regular expression of /[^/]+/ is used. To change that, use getRegExp. @@ -137,7 +137,7 @@ export type HttpQueries = T & { __met * } * ``` */ -export type HttpRegExp = T & { __meta?: ['httpRegExp', Pattern] }; +export type HttpRegExp = T & { __meta?: never & ['httpRegExp', Pattern] }; export function getRegExp(type: Type): string | RegExp | undefined { const options = metaAnnotation.getForName(type, 'httpRegExp'); diff --git a/packages/http/tests/router.spec.ts b/packages/http/tests/router.spec.ts index 27e7115aa..0842c234e 100644 --- a/packages/http/tests/router.spec.ts +++ b/packages/http/tests/router.spec.ts @@ -1021,7 +1021,7 @@ test('unpopulated entity without type information', async () => { }); test('extend with custom type', async () => { - type StringifyTransport = { __meta?: ['stringifyTransport'] }; + type StringifyTransport = { __meta?: never & ['stringifyTransport'] }; function isStringifyTransportType(type: Type): boolean { return !!metaAnnotation.getForName(type, 'stringifyTransport'); diff --git a/packages/orm/tests/query.spec.ts b/packages/orm/tests/query.spec.ts index f6a4f74fd..81400ddad 100644 --- a/packages/orm/tests/query.spec.ts +++ b/packages/orm/tests/query.spec.ts @@ -1,11 +1,27 @@ -import { BackReference, deserialize, PrimaryKey, Reference } from '@deepkit/type'; +import { BackReference, deserialize, Index, PrimaryKey, Reference, UUID, uuid } from '@deepkit/type'; import { expect, test } from '@jest/globals'; import { assert, IsExact } from 'conditional-type-checks'; import { Database } from '../src/database.js'; import { MemoryDatabaseAdapter, MemoryQuery } from '../src/memory-db.js'; -import { AnyQuery, BaseQuery, Query } from '../src/query.js'; +import { AnyQuery, Query } from '../src/query.js'; import { OrmEntity } from '../src/type.js'; +test('types do not interfere with type check', () => { + class Books { + bookId: UUID & Index = uuid(); + } + + const database = new Database(new MemoryDatabaseAdapter()); + + function get(bookId: UUID) { + return database + .query(Books) + // this should just compile and not error + .filter({ bookId }) + .findOneOrUndefined(); + } +}); + test('query select', async () => { class s { id!: number & PrimaryKey; diff --git a/packages/type/src/reflection/type.ts b/packages/type/src/reflection/type.ts index 3b9118676..99aa1ba28 100644 --- a/packages/type/src/reflection/type.ts +++ b/packages/type/src/reflection/type.ts @@ -1418,6 +1418,28 @@ export interface EntityOptions { indexes?: { names: string[], options: IndexOptions }[]; } +/** + * Type to use for custom type annotations. + * + * + * ```typescript + * type MyType = TypeAnnotation<'myType', T>; + * + * interface User { + * id: number & MyType<'yes'>; + * } + * + * const reflection = ReflectionClass.from(); + * const id = reflection.getProperty('id'); + * + * // data is set when `id` used `MyType` and contains the type of 'yes' as type object + * // which can be converted to JS with `typeToObject` + * const data = metaAnnotation.getForName(id.type, 'myType'); + * const param1 = typeToObject(data[0]); //yes + * ``` + */ +export type TypeAnnotation = { __meta?: never & [T, Options] }; + /** * Type to decorate an interface/object literal with entity information. * @@ -1428,7 +1450,7 @@ export interface EntityOptions { * } * ``` */ -export type Entity = { __meta?: ['entity', T] } +export type Entity = TypeAnnotation<'entity', T> /** * Marks a property as primary key. @@ -1438,7 +1460,7 @@ export type Entity = { __meta?: ['entity', T] } * } * ``` */ -export type PrimaryKey = { __meta?: ['primaryKey'] }; +export type PrimaryKey = TypeAnnotation<'primaryKey'>; type TypeKeyOf = T[keyof T]; export type PrimaryKeyFields = any extends T ? any : { [P in keyof T]: Required extends Required ? T[P] : never }; @@ -1455,7 +1477,7 @@ export type ReferenceFields = { [P in keyof T]: Required extends Requir * } * ``` */ -export type AutoIncrement = { __meta?: ['autoIncrement'] }; +export type AutoIncrement = TypeAnnotation<'autoIncrement'>; /** * UUID v4, as string, serialized as string in JSON, and binary in database. @@ -1467,12 +1489,12 @@ export type AutoIncrement = { __meta?: ['autoIncrement'] }; * } * ``` */ -export type UUID = string & { __meta?: ['UUIDv4'] }; +export type UUID = string & TypeAnnotation<'UUIDv4'>; /** * MongoDB's ObjectID type. serialized as string in JSON, ObjectID in database. */ -export type MongoId = string & { __meta?: ['mongoId'] }; +export type MongoId = string & TypeAnnotation<'mongoId'>; /** * Same as `bigint` but serializes to unsigned binary with unlimited size (instead of 8 bytes in most databases). @@ -1484,7 +1506,7 @@ export type MongoId = string & { __meta?: ['mongoId'] }; * } * ``` */ -export type BinaryBigInt = bigint & { __meta?: ['binaryBigInt'] }; +export type BinaryBigInt = bigint & TypeAnnotation<'binaryBigInt'>; /** * Same as `bigint` but serializes to signed binary with unlimited size (instead of 8 bytes in most databases). @@ -1496,7 +1518,7 @@ export type BinaryBigInt = bigint & { __meta?: ['binaryBigInt'] }; * } * ``` */ -export type SignedBinaryBigInt = bigint & { __meta?: ['signedBinaryBigInt'] }; +export type SignedBinaryBigInt = bigint & TypeAnnotation<'signedBinaryBigInt'>; export interface BackReferenceOptions { /** @@ -1512,12 +1534,12 @@ export interface BackReferenceOptions { mappedBy?: string, } -export type Reference = { __meta?: ['reference', Options] }; -export type BackReference = { __meta?: ['backReference', Options] }; -export type EmbeddedMeta = { __meta?: ['embedded', Options] }; +export type Reference = TypeAnnotation<'reference', Options>; +export type BackReference = TypeAnnotation<'backReference', Options>; +export type EmbeddedMeta = TypeAnnotation<'embedded', Options>; export type Embedded = T & EmbeddedMeta; -export type MapName = { __meta?: ['mapName', Alias, ForSerializer] }; +export type MapName = { __meta?: never & ['mapName', Alias, ForSerializer] }; export const referenceAnnotation = new AnnotationDefinition('reference'); export const entityAnnotation = new class extends AnnotationDefinition { @@ -1682,7 +1704,7 @@ export function hasEmbedded(type: Type): boolean { * } * ``` */ -export type Group = { __meta?: ['group', never & Name] }; +export type Group = TypeAnnotation<'group', Name>; /** * Excludes the type from serialization of all kind. @@ -1695,7 +1717,7 @@ export type Group = { __meta?: ['group', never & Name] }; * } * ``` */ -export type Excluded = { __meta?: ['excluded', never & Name] }; +export type Excluded = TypeAnnotation<'excluded', Name>; /** * Assigns arbitrary data to a type that can be read in runtime. @@ -1708,7 +1730,7 @@ export type Excluded = { __meta?: ['excluded', never * } * ``` */ -export type Data = { __meta?: ['data', never & Name, never & Value] }; +export type Data = { __meta?: never & ['data', Name, Value] }; /** * Resets an already set decorator to undefined. @@ -1723,7 +1745,7 @@ export type Data = { __meta?: ['data', never & Name, * } * ``` */ -export type ResetAnnotation = { __meta?: ['reset', Name] }; +export type ResetAnnotation = TypeAnnotation<'reset', Name>; export type IndexOptions = { name?: string; @@ -1741,8 +1763,8 @@ export type IndexOptions = { expireAfterSeconds?: number, }; -export type Unique = { __meta?: ['index', never & Options & { unique: true }] }; -export type Index = { __meta?: ['index', never & Options] }; +export type Unique = TypeAnnotation<'index', Options & { unique: true }>; +export type Index = TypeAnnotation<'index', Options>; export interface DatabaseFieldOptions { /** @@ -1790,7 +1812,7 @@ export interface PostgresOptions extends DatabaseFieldOptions { export interface SqliteOptions extends DatabaseFieldOptions { } -type Database = { __meta?: ['database', never & Name, never & Options] }; +type Database = { __meta?: never & ['database', Name, Options] }; export type MySQL = Database<'mysql', Options>; export type Postgres = Database<'postgres', Options>; export type SQLite = Database<'sqlite', Options>; @@ -1848,31 +1870,76 @@ export function registerTypeDecorator(decorator: TypeDecorator) { typeDecorators.push(decorator); } +/** + * Type annotations are object literals with a single optional __meta in it + * that has as type a tuple with the name of the annotation as first entry. + * The tuple is intersected with the `never` type to make sure it does not + * interfere with type checking. + * + * The processor has currently implemented to not resolve `never & x` to `never`, + * so we still have the intersection type in runtime to resolve __meta correctly. + * + * ```typescript + * type MyAnnotation1 = TypeAnnotation<'myAnnotation'> + * type MyAnnotation1 = TypeAnnotation<'myAnnotation', T> + * + * //under the hood it is: + * type lowLevel1 = { __meta?: never & ['myAnnotation'] } + * type lowLevel2 = { __meta?: never & ['myAnnotation', 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 (!tuple) return; + + 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); + + return { id: id.type.literal, params }; +} + export const typeDecorators: TypeDecorator[] = [ (annotations: Annotations, decorator: TypeObjectLiteral) => { - const meta = getProperty(decorator, '__meta'); - if (!meta || meta.type.kind !== ReflectionKind.tuple) return false; - const id = meta.type.types[0]; - if (!id || id.type.kind !== ReflectionKind.literal) return false; + const meta = getAnnotationMeta(decorator); + if (!meta) return false; - switch (id.type.literal) { + switch (meta.id) { case 'reference': { - const optionsType = meta.type.types[1]; - if (!optionsType || optionsType.type.kind !== ReflectionKind.objectLiteral) return false; - const options = typeToObject(optionsType.type); + const optionsType = meta.params[0]; + if (!optionsType || optionsType.kind !== ReflectionKind.objectLiteral) return false; + const options = typeToObject(optionsType); referenceAnnotation.replace(annotations, [options]); return true; } case 'entity': { - const optionsType = meta.type.types[1]; - if (!optionsType || optionsType.type.kind !== ReflectionKind.objectLiteral) return false; - const options = typeToObject(optionsType.type); + const optionsType = meta.params[0]; + if (!optionsType || optionsType.kind !== ReflectionKind.objectLiteral) return false; + const options = typeToObject(optionsType); entityAnnotation.replace(annotations, [options]); return true; } case 'mapName': { - const name = typeToObject(meta.type.types[1].type); - const serializer = meta.type.types[2] ? typeToObject(meta.type.types[2].type) : undefined; + if (!meta.params[0]) return false; + const name = typeToObject(meta.params[0]); + const serializer = meta.params[1] ? typeToObject(meta.params[1]) : undefined; if ('string' === typeof name && (!serializer || 'string' === typeof serializer)) { mapNameAnnotation.replace(annotations, [{ name, serializer }]); @@ -1898,42 +1965,42 @@ export const typeDecorators: TypeDecorator[] = [ uuidAnnotation.register(annotations, true); return true; case 'embedded': { - const optionsType = meta.type.types[1]; - if (!optionsType || optionsType.type.kind !== ReflectionKind.objectLiteral) return false; - const options = typeToObject(optionsType.type); + const optionsType = meta.params[0]; + if (!optionsType || optionsType.kind !== ReflectionKind.objectLiteral) return false; + const options = typeToObject(optionsType); embeddedAnnotation.replace(annotations, [options]); return true; } case 'group': { - const nameType = meta.type.types[1]; - if (!nameType || nameType.type.kind !== ReflectionKind.literal || 'string' !== typeof nameType.type.literal) return false; - groupAnnotation.register(annotations, nameType.type.literal); + const nameType = meta.params[0]; + if (!nameType || nameType.kind !== ReflectionKind.literal || 'string' !== typeof nameType.literal) return false; + groupAnnotation.register(annotations, nameType.literal); return true; } case 'index': { - const optionsType = meta.type.types[1]; - if (!optionsType || optionsType.type.kind !== ReflectionKind.objectLiteral) return false; - const options = typeToObject(optionsType.type); + const optionsType = meta.params[0]; + if (!optionsType || optionsType.kind !== ReflectionKind.objectLiteral) return false; + const options = typeToObject(optionsType); indexAnnotation.replace(annotations, [options]); return true; } case 'database': { - const nameType = meta.type.types[1]; - if (!nameType || nameType.type.kind !== ReflectionKind.literal || 'string' !== typeof nameType.type.literal) return false; - const optionsType = meta.type.types[2]; - if (!optionsType || optionsType.type.kind !== ReflectionKind.objectLiteral) return false; - const options = typeToObject(optionsType.type); - databaseAnnotation.register(annotations, { name: nameType.type.literal, options }); + const nameType = meta.params[0]; + if (!nameType || nameType.kind !== ReflectionKind.literal || 'string' !== typeof nameType.literal) return false; + const optionsType = meta.params[1]; + if (!optionsType || optionsType.kind !== ReflectionKind.objectLiteral) return false; + const options = typeToObject(optionsType); + databaseAnnotation.register(annotations, { name: nameType.literal, options }); return true; } case 'excluded': { - const nameType = meta.type.types[1]; - if (!nameType || nameType.type.kind !== ReflectionKind.literal || 'string' !== typeof nameType.type.literal) return false; - excludedAnnotation.register(annotations, nameType.type.literal); + const nameType = meta.params[0]; + if (!nameType || nameType.kind !== ReflectionKind.literal || 'string' !== typeof nameType.literal) return false; + excludedAnnotation.register(annotations, nameType.literal); return true; } case 'reset': { - const name = typeToObject(meta.type.types[1].type); + const name = typeToObject(meta.params[0]); if ('string' !== typeof name) return false; const map: { [name: string]: AnnotationDefinition } = { primaryKey: primaryKeyAnnotation, @@ -1954,9 +2021,9 @@ export const typeDecorators: TypeDecorator[] = [ return true; } case 'data': { - const nameType = meta.type.types[1]; - if (!nameType || nameType.type.kind !== ReflectionKind.literal || 'string' !== typeof nameType.type.literal) return false; - const dataType = meta.type.types[2]; + const nameType = meta.params[0]; + if (!nameType || nameType.kind !== ReflectionKind.literal || 'string' !== typeof nameType.literal) return false; + const dataType = meta.params[1]; if (!dataType) return false; annotations[dataAnnotation.symbol] ||= []; @@ -1967,16 +2034,16 @@ export const typeDecorators: TypeDecorator[] = [ annotations[dataAnnotation.symbol].push(data); } - data[nameType.type.literal] = dataType.type.kind === ReflectionKind.literal ? dataType.type.literal : dataType.type; + data[nameType.literal] = dataType.kind === ReflectionKind.literal ? dataType.literal : dataType; return true; } case 'backReference': { - const optionsType = meta.type.types[1]; - if (!optionsType || optionsType.type.kind !== ReflectionKind.objectLiteral) return false; + const optionsType = meta.params[0]; + if (!optionsType || optionsType.kind !== ReflectionKind.objectLiteral) return false; - const options = typeToObject(optionsType.type); - const member = findMember('via', resolveTypeMembers(optionsType.type)); + const options = typeToObject(optionsType); + const member = findMember('via', resolveTypeMembers(optionsType)); backReferenceAnnotation.register(annotations, { mappedBy: options.mappedBy, via: member && member.kind === ReflectionKind.propertySignature && (member.type.kind === ReflectionKind.objectLiteral || member.type.kind === ReflectionKind.class) ? member.type : undefined, @@ -1984,21 +2051,20 @@ export const typeDecorators: TypeDecorator[] = [ return true; } case 'validator': { - const nameType = meta.type.types[1]; - if (!nameType || nameType.type.kind !== ReflectionKind.literal || 'string' !== typeof nameType.type.literal) return false; - const name = nameType.type.literal; + const nameType = meta.params[0]; + if (!nameType || nameType.kind !== ReflectionKind.literal || 'string' !== typeof nameType.literal) return false; + const name = nameType.literal; - const argsType = meta.type.types[2]; - if (!argsType || argsType.type.kind !== ReflectionKind.tuple) return false; - const args: Type[] = argsType.type.types.map(v => v.type); + const argsType = meta.params[1]; + if (!argsType || argsType.kind !== ReflectionKind.tuple) return false; + const args: Type[] = argsType.types.map(v => v.type); const options: AnnotationType = { name, args }; validationAnnotation.register(annotations, options); return true; } default: { - const optionsType = meta.type.types.slice(1).map(v => v.type) as Type[]; - metaAnnotation.register(annotations, { name: id.type.literal as string, options: optionsType }); + metaAnnotation.register(annotations, { name: meta.id, options: meta.params }); return true; } } diff --git a/packages/type/src/validator.ts b/packages/type/src/validator.ts index 605eaa0f5..a88b16e36 100644 --- a/packages/type/src/validator.ts +++ b/packages/type/src/validator.ts @@ -5,7 +5,7 @@ import { stringifyType, Type } from './reflection/type.js'; import { entity } from './decorator.js'; import { serializer, Serializer } from './serializer.js'; -export type ValidatorMeta = { __meta?: ['validator', Name, Args] } +export type ValidatorMeta = { __meta?: never & ['validator', Name, Args] } export type ValidateFunction = (value: any, type: Type, options: any) => ValidatorError | void; export type Validate[2] = unknown> = ValidatorMeta<'function', [T, Options]>; @@ -27,7 +27,7 @@ export type Maximum = ValidatorMeta<'maximum', [T]>; /** Includes 0. Use PositiveNoZero to exclude 0. */ -export type Positive = ValidatorMeta<'positive', [true]>; +export type Positive = ValidatorMeta<'positive', unknown & [true]>; /** * Includes 0. Use NegativeNoZero to exclude 0. diff --git a/packages/type/tests/advanced.spec.ts b/packages/type/tests/advanced.spec.ts index f5ea980ff..1e8804c16 100644 --- a/packages/type/tests/advanced.spec.ts +++ b/packages/type/tests/advanced.spec.ts @@ -13,6 +13,7 @@ import { typeOf } from '../src/reflection/reflection.js'; import { assertType, ReflectionKind, stringifyResolvedType, Type } from '../src/reflection/type.js'; import { serialize } from '../src/serializer-facade.js'; import { expectEqualType } from './utils.js'; + test('array stack', () => { type Pop = T extends [...infer U, unknown] ? U : never type Push = [...T, U] diff --git a/packages/type/tests/compiler.spec.ts b/packages/type/tests/compiler.spec.ts index 0e2a0b482..95d8b2812 100644 --- a/packages/type/tests/compiler.spec.ts +++ b/packages/type/tests/compiler.spec.ts @@ -1541,7 +1541,7 @@ test('interface extends generic', () => { test('interface extends decorator', () => { const code = ` - type PrimaryKey = { __meta?: ['primaryKey'] }; + type PrimaryKey = { __meta?: never & ['primaryKey'] }; interface User extends PrimaryKey { id: number; diff --git a/packages/type/tests/integration.spec.ts b/packages/type/tests/integration.spec.ts index 273b68f39..63336401b 100644 --- a/packages/type/tests/integration.spec.ts +++ b/packages/type/tests/integration.spec.ts @@ -1324,8 +1324,8 @@ test('value object single field', () => { expect(price2.type.classType).toBe(Price); }); -test('type decorator with union', () => { - type HttpQuery = T & { __meta?: ['httpQuery'] }; +test('type annotation with union', () => { + type HttpQuery = T & { __meta?: never & ['httpQuery'] }; type a = HttpQuery; const type = typeOf(); @@ -1981,7 +1981,7 @@ test('default function expression', () => { }); -test('type decorator first position', () => { +test('type annotation first position', () => { class author { } diff --git a/packages/type/tests/performance-issue1.spec.ts b/packages/type/tests/performance-issue1.spec.ts index d57a4374b..36d64efb8 100644 --- a/packages/type/tests/performance-issue1.spec.ts +++ b/packages/type/tests/performance-issue1.spec.ts @@ -1001,9 +1001,9 @@ export class HttpResponse extends ServerResponse { export type HttpRequestQuery = { [name: string]: string }; export type HttpRequestResolvedParameters = { [name: string]: any }; -export type HttpBody = T & { __meta?: ['httpBody'] }; -export type HttpQuery = T & { __meta?: ['httpQuery', Options] }; -export type HttpQueries = T & { __meta?: ['httpQueries', Options] }; +export type HttpBody = T & { __meta?: never & ['httpBody'] }; +export type HttpQuery = T & { __meta?: never & ['httpQuery', Options] }; +export type HttpQueries = T & { __meta?: never & ['httpQueries', Options] }; export class RequestBuilder { protected contentBuffer: Buffer = Buffer.alloc(0); diff --git a/packages/type/tests/serializer.spec.ts b/packages/type/tests/serializer.spec.ts index 02ff17086..e9d8b4c04 100644 --- a/packages/type/tests/serializer.spec.ts +++ b/packages/type/tests/serializer.spec.ts @@ -1150,7 +1150,7 @@ test('patch', () => { }); test('extend with custom type', () => { - type StringifyTransport = { __meta?: ['stringifyTransport'] }; + type StringifyTransport = { __meta?: never & ['stringifyTransport'] }; function isStringifyTransportType(type: Type): boolean { return !!metaAnnotation.getForName(type, 'stringifyTransport'); diff --git a/packages/type/tests/type-serialization.spec.ts b/packages/type/tests/type-serialization.spec.ts index c273d8de0..a28e60438 100644 --- a/packages/type/tests/type-serialization.spec.ts +++ b/packages/type/tests/type-serialization.spec.ts @@ -38,7 +38,7 @@ test('serialize basics', () => { expect(serializeType(typeOf())).toEqual([{ kind: ReflectionKind.literal, literal: { type: 'regex', regex: '/abc/g' } }]); }); -test('serialize type decorators', () => { +test('serialize type annotations', () => { type t = string & PrimaryKey; expect(serializeType(typeOf())).toEqual([ { kind: ReflectionKind.string, typeName: 't', decorators: [1] }, @@ -239,7 +239,7 @@ test('roundTrip class generic', () => { } }); -test('type decorators', () => { +test('type annotations', () => { { type t = string & PrimaryKey; const type = roundTrip(); diff --git a/packages/type/tests/type.spec.ts b/packages/type/tests/type.spec.ts index 57b21a9ab..d1892bb6c 100644 --- a/packages/type/tests/type.spec.ts +++ b/packages/type/tests/type.spec.ts @@ -19,10 +19,10 @@ import { resolveTypeMembers, stringifyResolvedType, stringifyType, - Type, + Type, TypeAnnotation, TypeClass, TypeObjectLiteral, - TypeProperty, + TypeProperty, typeToObject, UUID, validationAnnotation } from '../src/reflection/type.js'; @@ -56,26 +56,47 @@ test('stringify date/set/map', () => { expect(stringifyType(typeOf>())).toBe('Set'); }); -test('type decorator', () => { - type MyAnnotation = { __meta?: ['myAnnotation'] }; +test('type annotation', () => { + type MyAnnotation = { __meta?: never & ['myAnnotation'] }; type Username = string & MyAnnotation; const type = typeOf(); const data = metaAnnotation.getForName(type, 'myAnnotation'); expect(data).toEqual([]); }); +test('type annotation with option', () => { + type MyAnnotation