Skip to content

Commit 6d5ca97

Browse files
committed
fix(metadata): resolve Reflect API from global instead of window
And add optional API to provide custom implementation of Reflect API. Also throw meaningful error in case when Reflect API is not found. closes #9
1 parent d2ed877 commit 6d5ca97

File tree

3 files changed

+149
-118
lines changed

3 files changed

+149
-118
lines changed
Lines changed: 117 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,110 +1,117 @@
1-
import * as t from 'io-ts';
2-
3-
import { AnyOf, AsRuntimeType, RuntimeType, TypeOf } from './runtime-types';
4-
import { typeOf } from './type-factories';
5-
import { MapTo, StringHashMap, Type } from './types';
6-
import { chainFns, identity, isBuiltinType, isPrimitive } from './util';
7-
8-
const Reflect = (window as any).Reflect;
9-
10-
const propMetaKey = '__PROPERTY_META__';
11-
12-
export type TypeFactory<T> = (type: t.Type<T>) => t.Type<T>;
13-
14-
export interface TypeMetadata<T extends RuntimeType> {
15-
type?: T;
16-
typeFactory?: TypeFactory<T>;
17-
isRequired?: boolean;
18-
}
19-
20-
export class ResolvedTypeMetadata<T> implements TypeMetadata<T> {
21-
meta: AsRuntimeType<T> = {} as any;
22-
23-
static isResolvedMetadata(obj: any): obj is ResolvedTypeMetadata<any> {
24-
return !!obj && obj instanceof ResolvedTypeMetadata;
25-
}
26-
27-
constructor(
28-
public type: T,
29-
public isRequired = false,
30-
public typeFactory: TypeFactory<T> = identity,
31-
) {}
32-
}
33-
34-
/**
35-
* Resolves full metadata on `type`
36-
* @internal
37-
*/
38-
export function resolveMetadataOf<T>(type: Type<T>): MapTo<T, ResolvedTypeMetadata<T>> {
39-
return resolveMetaRecursive(type);
40-
}
41-
42-
function resolveMetaRecursive(obj: any) {
43-
if (!obj || isPrimitive(obj) || isBuiltinType(obj)) {
44-
return obj;
45-
}
46-
47-
if (obj instanceof AnyOf) {
48-
return obj.type.map(resolveMetaRecursive);
49-
}
50-
51-
if (obj instanceof TypeOf) {
52-
return resolveMetaRecursive(obj.type);
53-
}
54-
55-
const metaInfo = getPropertyTypes(obj);
56-
57-
if (!metaInfo) {
58-
return Object;
59-
}
60-
61-
if (typeof metaInfo === 'object') {
62-
Object.keys(metaInfo).forEach(key => {
63-
const meta = metaInfo[key];
64-
const metadata = new ResolvedTypeMetadata(meta.type, meta.isRequired, meta.typeFactory);
65-
metadata.meta = resolveMetaRecursive(meta.type);
66-
metaInfo[key] = metadata;
67-
});
68-
}
69-
70-
return metaInfo;
71-
}
72-
73-
export function setPropertyType(
74-
target: Object,
75-
prop: string | symbol,
76-
options: TypeMetadata<any> = {},
77-
) {
78-
if (!target[propMetaKey]) {
79-
Object.defineProperty(target, propMetaKey, {
80-
configurable: true,
81-
enumerable: false,
82-
value: Object.create(null),
83-
});
84-
}
85-
86-
const types = target[propMetaKey];
87-
88-
const type = options.type || typeOf(readPropType(target, prop));
89-
90-
types[prop] = mergePropertyMeta(types[prop], { ...options, type });
91-
}
92-
93-
export function getPropertyTypes<T>(target: Type<T>): StringHashMap<TypeMetadata<T>> {
94-
return target.prototype[propMetaKey];
95-
}
96-
97-
export function readPropType(target: Object, prop: string | symbol): any {
98-
return Reflect.getMetadata('design:type', target, prop);
99-
}
100-
101-
export function mergePropertyMeta<T>(
102-
meta1: TypeMetadata<T> = {},
103-
meta2: TypeMetadata<T> = {},
104-
): TypeMetadata<T> {
105-
return {
106-
...meta1,
107-
...meta2,
108-
typeFactory: chainFns(meta1.typeFactory, meta2.typeFactory),
109-
};
110-
}
1+
import * as t from 'io-ts';
2+
3+
import { getReflect } from './reflect';
4+
import { AnyOf, AsRuntimeType, RuntimeType, TypeOf } from './runtime-types';
5+
import { typeOf } from './type-factories';
6+
import { MapTo, StringHashMap, Type } from './types';
7+
import { chainFns, identity, isBuiltinType, isPrimitive } from './util';
8+
9+
const propMetaKey = '__PROPERTY_META__';
10+
11+
export type TypeFactory<T> = (type: t.Type<T>) => t.Type<T>;
12+
13+
export interface TypeMetadata<T extends RuntimeType> {
14+
type?: T;
15+
typeFactory?: TypeFactory<T>;
16+
isRequired?: boolean;
17+
}
18+
19+
export class ResolvedTypeMetadata<T> implements TypeMetadata<T> {
20+
meta: AsRuntimeType<T> = {} as any;
21+
22+
static isResolvedMetadata(obj: any): obj is ResolvedTypeMetadata<any> {
23+
return !!obj && obj instanceof ResolvedTypeMetadata;
24+
}
25+
26+
constructor(
27+
public type: T,
28+
public isRequired = false,
29+
public typeFactory: TypeFactory<T> = identity,
30+
) {}
31+
}
32+
33+
/**
34+
* Resolves full metadata on `type`
35+
* @internal
36+
*/
37+
export function resolveMetadataOf<T>(
38+
type: Type<T>,
39+
): MapTo<T, ResolvedTypeMetadata<T>> {
40+
return resolveMetaRecursive(type);
41+
}
42+
43+
function resolveMetaRecursive(obj: any) {
44+
if (!obj || isPrimitive(obj) || isBuiltinType(obj)) {
45+
return obj;
46+
}
47+
48+
if (obj instanceof AnyOf) {
49+
return obj.type.map(resolveMetaRecursive);
50+
}
51+
52+
if (obj instanceof TypeOf) {
53+
return resolveMetaRecursive(obj.type);
54+
}
55+
56+
const metaInfo = getPropertyTypes(obj);
57+
58+
if (!metaInfo) {
59+
return Object;
60+
}
61+
62+
if (typeof metaInfo === 'object') {
63+
Object.keys(metaInfo).forEach(key => {
64+
const meta = metaInfo[key];
65+
const metadata = new ResolvedTypeMetadata(
66+
meta.type,
67+
meta.isRequired,
68+
meta.typeFactory,
69+
);
70+
metadata.meta = resolveMetaRecursive(meta.type);
71+
metaInfo[key] = metadata;
72+
});
73+
}
74+
75+
return metaInfo;
76+
}
77+
78+
export function setPropertyType(
79+
target: Object,
80+
prop: string | symbol,
81+
options: TypeMetadata<any> = {},
82+
) {
83+
if (!target[propMetaKey]) {
84+
Object.defineProperty(target, propMetaKey, {
85+
configurable: true,
86+
enumerable: false,
87+
value: Object.create(null),
88+
});
89+
}
90+
91+
const types = target[propMetaKey];
92+
93+
const type = options.type || typeOf(readPropType(target, prop));
94+
95+
types[prop] = mergePropertyMeta(types[prop], { ...options, type });
96+
}
97+
98+
export function getPropertyTypes<T>(
99+
target: Type<T>,
100+
): StringHashMap<TypeMetadata<T>> {
101+
return target.prototype[propMetaKey];
102+
}
103+
104+
export function readPropType(target: Object, prop: string | symbol): any {
105+
return getReflect().getMetadata('design:type', target, prop);
106+
}
107+
108+
export function mergePropertyMeta<T>(
109+
meta1: TypeMetadata<T> = {},
110+
meta2: TypeMetadata<T> = {},
111+
): TypeMetadata<T> {
112+
return {
113+
...meta1,
114+
...meta2,
115+
typeFactory: chainFns(meta1.typeFactory, meta2.typeFactory),
116+
};
117+
}

projects/gen-io-ts/src/lib/reflect.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export interface RequiredReflectApi {
2+
getMetadata(type: string, obj: Object, prop: string | symbol): any;
3+
}
4+
5+
let _reflectImpl: RequiredReflectApi;
6+
7+
export function provideReflect(reflectImpl: RequiredReflectApi) {
8+
_reflectImpl = reflectImpl;
9+
}
10+
11+
/** @internal */
12+
export function getReflect(): RequiredReflectApi {
13+
const reflect = _reflectImpl || (global as any).Reflect;
14+
15+
if (!reflect) {
16+
throw new Error(
17+
'Reflect API is not available and was not provided! ' +
18+
'Please import polyfill or use `provideReflect()` to manually provide Reflect API',
19+
);
20+
}
21+
22+
return reflect;
23+
}

projects/gen-io-ts/src/public_api.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
/*
2-
* Public API Surface of gen-io-ts
3-
*/
4-
5-
export * from './lib/generator';
6-
export * from './lib/property';
7-
export * from './lib/runtime-types';
8-
export * from './lib/type-factories';
1+
/*
2+
* Public API Surface of gen-io-ts
3+
*/
4+
5+
export * from './lib/generator';
6+
export * from './lib/property';
7+
export * from './lib/runtime-types';
8+
export * from './lib/type-factories';
9+
export { RequiredReflectApi, provideReflect } from './lib/reflect';

0 commit comments

Comments
 (0)