@anzenjs/core
Advanced tools
Comparing version 0.2.0 to 0.2.1
@@ -7,12 +7,21 @@ /// <reference types="node" /> | ||
_: T; | ||
schemaResolvable: SchemaResolvable; | ||
schemaResolvable: SchemaLike; | ||
} | ||
export declare function List<T extends SchemaResolvable>(schemaResolvable: T): ListSchema<ResolveType<T>>; | ||
declare const optionalSymbol: unique symbol; | ||
export interface OptionalSchema<T extends unknown = unknown> { | ||
symbol: typeof optionalSymbol; | ||
export declare function List<T extends SchemaLike>(schemaResolvable: T): ListSchema<ResolveType<T>>; | ||
declare const tupleSymbol: unique symbol; | ||
export interface TupleSchema<Args extends [unknown, ...unknown[]] = [unknown, ...unknown[]]> { | ||
symbol: typeof tupleSymbol; | ||
_: Args; | ||
childSchemas: SchemaLike[]; | ||
} | ||
export declare function Tuple<Resolvables extends [SchemaLike, ...SchemaLike[]]>(...schemaResolvables: Resolvables): TupleSchema<{ | ||
[key in keyof Resolvables]: ResolveType<Resolvables[key]>; | ||
}>; | ||
declare const enumSymbol: unique symbol; | ||
export interface EnumSchema<T extends unknown = unknown> { | ||
symbol: typeof enumSymbol; | ||
_: T; | ||
schemaResolvable: SchemaResolvable; | ||
enumObject: any; | ||
} | ||
export declare function Optional<T extends SchemaResolvable>(schemaResolvable: T): OptionalSchema<ResolveType<T>>; | ||
export declare function Enum<T>(enumObject: T): EnumSchema<T[keyof T]>; | ||
export declare type Provider<T> = () => T; | ||
@@ -23,6 +32,4 @@ export declare type Thunk<T> = T | Provider<T>; | ||
} | ||
export declare type SchemaResolvable = joi.Schema | typeof Boolean | typeof Number | typeof String | typeof Date | typeof Buffer | ListSchema | OptionalSchema | Thunk<Constructor>; | ||
export interface SchemaResolvableArray extends Array<SchemaResolvable> { | ||
} | ||
export declare type ResolveType<T extends SchemaResolvable> = T extends joi.Schema ? unknown : T extends typeof Boolean ? boolean : T extends typeof Number ? number : T extends typeof String ? string : T extends typeof Date ? Date : T extends typeof Buffer ? Buffer : T extends ListSchema<infer U> ? Array<U> : T extends OptionalSchema<infer U> ? U | undefined | null : T extends Thunk<Constructor<infer U>> ? U : never; | ||
export declare type SchemaLike = typeof Boolean | typeof Number | typeof String | typeof Date | typeof Buffer | ListSchema | TupleSchema | EnumSchema | Thunk<Constructor>; | ||
export declare type ResolveType<T> = T extends joi.Schema ? unknown : T extends typeof Boolean ? boolean : T extends typeof Number ? number : T extends typeof String ? string : T extends typeof Date ? Date : T extends typeof Buffer ? Buffer : T extends ListSchema<infer U> ? Array<U> : T extends TupleSchema<infer U> ? U : T extends EnumSchema<infer U> ? U : T extends Thunk<Constructor<infer U>> ? U : never; | ||
export declare type ResolveJoiSchemaType<T> = T extends boolean ? joi.BooleanSchema : T extends number ? joi.NumberSchema : T extends string ? joi.StringSchema : T extends Date ? joi.DateSchema : T extends Buffer ? joi.BinarySchema : T extends unknown[] ? joi.ArraySchema : T extends object ? joi.ObjectSchema : joi.AnySchema; | ||
@@ -32,4 +39,5 @@ interface Transformer<T, U> { | ||
} | ||
export declare const resolveSchema: Transformer<SchemaResolvable, joi.Schema>; | ||
export declare function transform<T>(input: any, ctor: Constructor<T>, joiOptions?: joi.ValidationOptions): T; | ||
export declare function resolveMaybeSchema(schema: SchemaLike | undefined): joi.Schema; | ||
export declare const resolveSchema: Transformer<SchemaLike, joi.Schema>; | ||
export declare function transform<T extends SchemaLike>(input: any, schema: T, joiOptions?: joi.ValidationOptions): ResolveType<T>; | ||
export {}; |
126
dist/core.js
@@ -20,6 +20,6 @@ "use strict"; | ||
exports.List = List; | ||
const optionalSymbol = Symbol('Optional'); | ||
function Optional(schemaResolvable) { | ||
const tupleSymbol = Symbol('tuple'); | ||
function Tuple(...schemaResolvables) { | ||
return { | ||
symbol: optionalSymbol, | ||
symbol: tupleSymbol, | ||
get _() { | ||
@@ -29,6 +29,18 @@ /* istanbul ignore next */ | ||
}, | ||
schemaResolvable, | ||
childSchemas: schemaResolvables, | ||
}; | ||
} | ||
exports.Optional = Optional; | ||
exports.Tuple = Tuple; | ||
const enumSymbol = Symbol('Enum'); | ||
function Enum(enumObject) { | ||
return { | ||
symbol: enumSymbol, | ||
get _() { | ||
/* istanbul ignore next */ | ||
throw new Error('static reference only'); | ||
}, | ||
enumObject, | ||
}; | ||
} | ||
exports.Enum = Enum; | ||
function cache(transfomer) { | ||
@@ -51,13 +63,13 @@ const resultCache = new WeakMap(); | ||
if (!keys) { | ||
throw new Error('try to resolve class without @Schema decorator'); | ||
throw new Error('try to resolve class without any @Property decorator'); | ||
} | ||
const schemaMap = {}; | ||
for (const key of keys) { | ||
const schemaResolvable = Reflect.getMetadata('schemaResolvable:field', ctor, key); | ||
let schema = exports.resolveSchema(schemaResolvable); | ||
const schema = Reflect.getMetadata('schemaResolvable:field', ctor, key); | ||
let joiSchema = resolveMaybeSchema(schema); | ||
const transformers = Reflect.getMetadata('schemaResolvable:transformers', ctor, key); | ||
if (transformers) { | ||
schema = transformers.reduce((prev, transformer) => transformer(prev), schema); | ||
joiSchema = transformers.reduce((prev, transformer) => transformer(prev), joiSchema); | ||
} | ||
schemaMap[key] = schema; | ||
schemaMap[key] = joiSchema; | ||
} | ||
@@ -69,11 +81,11 @@ return joi_1.default.object(schemaMap); | ||
} | ||
function isOptional(schemaResolvable) { | ||
return schemaResolvable.symbol === optionalSymbol; | ||
function isTuple(schemaResolvable) { | ||
return schemaResolvable.symbol === tupleSymbol; | ||
} | ||
function isEnum(schemaResolvable) { | ||
return schemaResolvable.symbol === enumSymbol; | ||
} | ||
function isSchemaClass(schemaResolvable) { | ||
return !!Reflect.getMetadata('schema', schemaResolvable); | ||
} | ||
function isJoiSchema(schemaResolvable) { | ||
return !!schemaResolvable.isJoi; | ||
} | ||
function isBuffer(schemaResolvable) { | ||
@@ -96,9 +108,9 @@ return schemaResolvable === Buffer; | ||
} | ||
if (isJoiSchema(resolvable)) { | ||
if (isList(resolvable)) { | ||
return undefined; | ||
} | ||
if (isList(resolvable)) { | ||
if (isTuple(resolvable)) { | ||
return undefined; | ||
} | ||
if (isOptional(resolvable)) { | ||
if (isEnum(resolvable)) { | ||
return undefined; | ||
@@ -114,2 +126,9 @@ } | ||
} | ||
function resolveMaybeSchema(schema) { | ||
if (!schema) { | ||
return joi_1.default.any().optional(); | ||
} | ||
return exports.resolveSchema(schema); | ||
} | ||
exports.resolveMaybeSchema = resolveMaybeSchema; | ||
exports.resolveSchema = cache((resolvable) => { | ||
@@ -129,46 +148,51 @@ switch (resolvable) { | ||
} | ||
if (isJoiSchema(resolvable)) { | ||
return resolvable; | ||
} | ||
if (isList(resolvable)) { | ||
return joi_1.default.array().items(exports.resolveSchema(resolvable.schemaResolvable)); | ||
return joi_1.default.array().items(resolveMaybeSchema(resolvable.schemaResolvable)); | ||
} | ||
if (isOptional(resolvable)) { | ||
return exports.resolveSchema(resolvable.schemaResolvable) | ||
.optional() | ||
.allow(null); | ||
if (isTuple(resolvable)) { | ||
return joi_1.default.array().ordered(resolvable.childSchemas.map(exports.resolveSchema)); | ||
} | ||
if (isEnum(resolvable)) { | ||
return joi_1.default.only(Object.values(resolvable.enumObject)); | ||
} | ||
if (utils_1.isClass(resolvable)) { | ||
if (isSchemaClass(resolvable)) { | ||
return classToSchema(resolvable); | ||
} | ||
return joi_1.default.object().type(resolvable); | ||
return classToSchema(resolvable); | ||
} | ||
return exports.resolveSchema(resolvable()); | ||
return resolveMaybeSchema(resolvable()); | ||
}); | ||
function convert(value, ctor) { | ||
if (!value) { | ||
function convertToClass(value, classType) { | ||
if (value instanceof classType) { | ||
return value; | ||
} | ||
if (value instanceof ctor) { | ||
return value; | ||
} | ||
const keys = Reflect.getMetadata('schemaProperties', ctor); | ||
const keys = Reflect.getMetadata('schemaProperties', classType); | ||
if (!keys) { | ||
throw new Error('try to resolve class without @Schema decorator'); | ||
throw new Error('try to resolve class without @Property decorator'); | ||
} | ||
const ret = Object.create(ctor.prototype); | ||
const ret = Object.create(classType.prototype); | ||
for (const key of keys) { | ||
const schemaResolvable = Reflect.getMetadata('schemaResolvable:field', ctor, key); | ||
const classType = getClass(schemaResolvable); | ||
if (classType) { | ||
ret[key] = convert(value[key], classType); | ||
} | ||
else { | ||
ret[key] = value[key]; | ||
} | ||
const schemaResolvable = Reflect.getMetadata('schemaResolvable:field', classType, key); | ||
ret[key] = convert(value[key], schemaResolvable); | ||
} | ||
return ret; | ||
} | ||
function transform(input, ctor, joiOptions = { | ||
function convert(value, schema) { | ||
if (!value) { | ||
return value; | ||
} | ||
if (!schema) { | ||
return value; | ||
} | ||
const classType = getClass(schema); | ||
if (classType) { | ||
return convertToClass(value, classType); | ||
} | ||
if (isList(schema)) { | ||
return value.map(item => convert(item, schema.schemaResolvable)); | ||
} | ||
if (isTuple(schema)) { | ||
return schema.childSchemas.map((schema, index) => convert(value[index], schema)); | ||
} | ||
return value; | ||
} | ||
function transform(input, schema, joiOptions = { | ||
allowUnknown: true, | ||
@@ -179,10 +203,10 @@ stripUnknown: true, | ||
}) { | ||
const schema = classToSchema(ctor); | ||
const { value, error } = schema.validate(input, joiOptions); | ||
const joiSchema = exports.resolveSchema(schema); | ||
const { value, error } = joiSchema.validate(input, joiOptions); | ||
if (error) { | ||
throw error; | ||
} | ||
return convert(value, ctor); | ||
return convert(value, schema); | ||
} | ||
exports.transform = transform; | ||
//# sourceMappingURL=core.js.map |
@@ -14,2 +14,3 @@ "use strict"; | ||
require("reflect-metadata"); | ||
const helpers_1 = require("./helpers"); | ||
const decorators_1 = require("./decorators"); | ||
@@ -271,3 +272,3 @@ const core_1 = require("./core"); | ||
}); | ||
it('should sucess with object constructor from A', () => { | ||
it('should success with object constructor from A', () => { | ||
const a = core_1.transform(new A('foo'), A); | ||
@@ -278,7 +279,8 @@ expect(a).toEqual({ foo: 'foo' }); | ||
}); | ||
describe('test @Optional', () => { | ||
describe('test Optional', () => { | ||
let A = class A { | ||
}; | ||
__decorate([ | ||
decorators_1.Property(core_1.Optional(String)), | ||
helpers_1.Optional(), | ||
decorators_1.Property(String), | ||
__metadata("design:type", Object) | ||
@@ -289,3 +291,3 @@ ], A.prototype, "foo", void 0); | ||
], A); | ||
it('should sucess with string value', () => { | ||
it('should success with string value', () => { | ||
const a = core_1.transform({ foo: 'foo' }, A); | ||
@@ -295,8 +297,6 @@ expect(a).toEqual({ foo: 'foo' }); | ||
}); | ||
it('should sucess with null value', () => { | ||
const a = core_1.transform({ foo: null }, A); | ||
expect(a).toEqual({ foo: null }); | ||
expect(a).toBeInstanceOf(A); | ||
it('should fail with null value', () => { | ||
expect(() => core_1.transform({ foo: null }, A)).toThrow(); | ||
}); | ||
it('should sucess with undefined value', () => { | ||
it('should success with undefined value', () => { | ||
const a = core_1.transform({ foo: undefined }, A); | ||
@@ -306,3 +306,3 @@ expect(a).toEqual({ foo: undefined }); | ||
}); | ||
it('should sucess with empty value', () => { | ||
it('should success with empty value', () => { | ||
const a = core_1.transform({}, A); | ||
@@ -313,2 +313,72 @@ expect(a).toEqual({}); | ||
}); | ||
describe('test Tuple', () => { | ||
let Nested = class Nested { | ||
}; | ||
__decorate([ | ||
decorators_1.Property(Number), | ||
__metadata("design:type", Number) | ||
], Nested.prototype, "id", void 0); | ||
Nested = __decorate([ | ||
decorators_1.Schema() | ||
], Nested); | ||
let A = class A { | ||
}; | ||
__decorate([ | ||
decorators_1.Property(core_1.Tuple(String, Nested)), | ||
__metadata("design:type", Array) | ||
], A.prototype, "foo", void 0); | ||
A = __decorate([ | ||
decorators_1.Schema() | ||
], A); | ||
it('should success with correct value', () => { | ||
const a = core_1.transform({ foo: ['foo', { id: 0 }] }, A); | ||
expect(a).toEqual({ foo: ['foo', { id: 0 }] }); | ||
expect(a).toBeInstanceOf(A); | ||
expect(a.foo[1]).toBeInstanceOf(Nested); | ||
}); | ||
}); | ||
describe('test Enum', () => { | ||
let Color; | ||
(function (Color) { | ||
Color["Red"] = "Red"; | ||
Color["Blue"] = "Blue"; | ||
})(Color || (Color = {})); | ||
let A = class A { | ||
}; | ||
__decorate([ | ||
decorators_1.Property(core_1.Enum(Color)), | ||
__metadata("design:type", String) | ||
], A.prototype, "color", void 0); | ||
A = __decorate([ | ||
decorators_1.Schema() | ||
], A); | ||
it('should success with correct value', () => { | ||
const a = core_1.transform({ color: Color.Blue }, A); | ||
expect(a).toEqual({ color: Color.Blue }); | ||
expect(a).toBeInstanceOf(A); | ||
}); | ||
it('should success with string value', () => { | ||
const a = core_1.transform({ color: 'Blue' }, A); | ||
expect(a).toEqual({ color: Color.Blue }); | ||
expect(a).toBeInstanceOf(A); | ||
}); | ||
it('should fail with invalid value', () => { | ||
expect(() => core_1.transform({ color: 'Yellow' }, A)).toThrow(); | ||
}); | ||
}); | ||
describe('test non-schema class', () => { | ||
class NonSchemaObject { | ||
constructor(foo) { | ||
this.foo = foo; | ||
} | ||
} | ||
it('should failed with raw object', () => { | ||
expect(() => core_1.transform({ foo: 'foo' }, NonSchemaObject)).toThrow(); | ||
}); | ||
it('should success with correct object', () => { | ||
const out = core_1.transform(new NonSchemaObject('foo'), NonSchemaObject); | ||
expect(out).toBeInstanceOf(NonSchemaObject); | ||
expect(out).toEqual({ foo: 'foo' }); | ||
}); | ||
}); | ||
//# sourceMappingURL=core.spec.js.map |
import 'reflect-metadata'; | ||
import { SchemaResolvable, ResolveType, ResolveJoiSchemaType } from './core'; | ||
import { SchemaLike, ResolveType, ResolveJoiSchemaType } from './core'; | ||
export interface SchemaPropertyDecorator<Value extends unknown> { | ||
<T extends { | ||
[key in Key]: Value; | ||
[key in Key]: ExtendGuard<T[key], Value>; | ||
}, Key extends (keyof T) & (string | symbol)>(target: T, key: Key): void; | ||
} | ||
export interface OptionalSchemaPropertyDecorator<T extends unknown> { | ||
<Key extends string>(target: { | ||
[key in Key]?: T | null | undefined; | ||
}, key: Key): void; | ||
} | ||
export declare type ExtendGuard<T, U> = U extends T ? T : U; | ||
export declare function Schema(): ClassDecorator; | ||
export declare function Property(): SchemaPropertyDecorator<unknown>; | ||
export declare function Property<T extends SchemaResolvable>(schemaResolvable: T): SchemaPropertyDecorator<ResolveType<T>>; | ||
export declare function Property<T extends SchemaLike>(schema: T): SchemaPropertyDecorator<ResolveType<T>>; | ||
export interface JoiSchemaTransformer<T extends unknown> { | ||
(schema: ResolveJoiSchemaType<T>): ResolveJoiSchemaType<T>; | ||
} | ||
export declare function SchemaConfig<T extends unknown>(transformer: JoiSchemaTransformer<T>): OptionalSchemaPropertyDecorator<T>; | ||
export declare function SchemaConfig<T extends unknown>(transformer: JoiSchemaTransformer<T>): SchemaPropertyDecorator<T>; |
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
require("reflect-metadata"); | ||
const joi_1 = __importDefault(require("joi")); | ||
function Schema() { | ||
@@ -12,5 +8,5 @@ return target => Reflect.defineMetadata('schema', true, target); | ||
exports.Schema = Schema; | ||
function Property(schemaResolvable = joi_1.default.any().optional()) { | ||
function Property(schema) { | ||
return (target, key) => { | ||
Reflect.defineMetadata('schemaResolvable:field', schemaResolvable, target.constructor, key); | ||
Reflect.defineMetadata('schemaResolvable:field', schema, target.constructor, key); | ||
const properties = Reflect.getMetadata('schemaProperties', target.constructor) || []; | ||
@@ -17,0 +13,0 @@ Reflect.defineMetadata('schemaProperties', new Set([...properties, key]), target.constructor); |
/// <reference types="node" /> | ||
import { UriOptions, EmailOptions, GuidOptions } from 'joi'; | ||
export declare function Integer(): import("./decorators").OptionalSchemaPropertyDecorator<number>; | ||
export declare function Min(min: number): import("./decorators").OptionalSchemaPropertyDecorator<string | number | unknown[]>; | ||
export declare function Max(max: number): import("./decorators").OptionalSchemaPropertyDecorator<string | number | unknown[]>; | ||
export declare function Length(length: number): import("./decorators").OptionalSchemaPropertyDecorator<string | Buffer | unknown[]>; | ||
export declare function Positive(): import("./decorators").OptionalSchemaPropertyDecorator<number>; | ||
export declare function Email(options?: EmailOptions): import("./decorators").OptionalSchemaPropertyDecorator<string>; | ||
export declare function Uri(options?: UriOptions): import("./decorators").OptionalSchemaPropertyDecorator<string>; | ||
export declare function Guid(options?: GuidOptions): import("./decorators").OptionalSchemaPropertyDecorator<string>; | ||
export declare function Default<T extends unknown>(value: T): import("./decorators").OptionalSchemaPropertyDecorator<T>; | ||
export declare function Single(enabled?: boolean): import("./decorators").OptionalSchemaPropertyDecorator<unknown[]>; | ||
export declare function Integer(): import("./decorators").SchemaPropertyDecorator<number>; | ||
export declare function Min(min: number): import("./decorators").SchemaPropertyDecorator<string | number | unknown[]>; | ||
export declare function Max(max: number): import("./decorators").SchemaPropertyDecorator<string | number | unknown[]>; | ||
export declare function Length(length: number): import("./decorators").SchemaPropertyDecorator<string | unknown[] | Buffer>; | ||
export declare function Positive(): import("./decorators").SchemaPropertyDecorator<number>; | ||
export declare function Email(options?: EmailOptions): import("./decorators").SchemaPropertyDecorator<string>; | ||
export declare function Uri(options?: UriOptions): import("./decorators").SchemaPropertyDecorator<string>; | ||
export declare function Guid(options?: GuidOptions): import("./decorators").SchemaPropertyDecorator<string>; | ||
export declare function Default<T extends unknown>(value: T): import("./decorators").SchemaPropertyDecorator<T>; | ||
export declare function Single(enabled?: boolean): import("./decorators").SchemaPropertyDecorator<unknown[]>; | ||
export declare function Optional(): import("./decorators").SchemaPropertyDecorator<undefined>; | ||
export declare function Allow<T extends unknown>(value: T): import("./decorators").SchemaPropertyDecorator<T>; |
@@ -44,2 +44,10 @@ "use strict"; | ||
exports.Single = Single; | ||
function Optional() { | ||
return decorators_1.SchemaConfig(schema => schema.optional()); | ||
} | ||
exports.Optional = Optional; | ||
function Allow(value) { | ||
return decorators_1.SchemaConfig(schema => schema.allow(value)); | ||
} | ||
exports.Allow = Allow; | ||
//# sourceMappingURL=helpers.js.map |
{ | ||
"name": "@anzenjs/core", | ||
"version": "0.2.0", | ||
"version": "0.2.1", | ||
"description": "Joi-powered type-safe decorator library to ensure runtime type safe", | ||
@@ -15,5 +15,6 @@ "license": "MIT", | ||
"ci:test": "run-s lint test:cov", | ||
"publish:patch": "npm version patch && npm publish --access public", | ||
"publish:minor": "npm version minor && npm publish --access public", | ||
"publish:major": "npm version major && npm publish --access public" | ||
"clean": "rm -rf dist src/coverage", | ||
"publish:patch": "npm run clean && npm run build && npm version patch && npm publish --access public", | ||
"publish:minor": "npm run clean && npm run build && npm version minor && npm publish --access public", | ||
"publish:major": "npm run clean && npm run build && npm version major && npm publish --access public" | ||
}, | ||
@@ -20,0 +21,0 @@ "dependencies": { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
50933
783