Commit e0c4d3ff authored by nanahira's avatar nanahira

use own class transform thing

parent 1496a79f
......@@ -15,13 +15,11 @@
"encoded-buffer": "^0.2.6",
"generic-pool": "^3.9.0",
"ioredis": "^5.2.3",
"lodash": "^4.17.21",
"lru-cache": "^7.13.1",
"typed-reflector": "^1.0.11"
"typed-reflector": "^1.0.12"
},
"devDependencies": {
"@types/jest": "^28.1.8",
"@types/lodash": "^4.14.182",
"@types/node": "^18.7.23",
"@typescript-eslint/eslint-plugin": "^5.38.1",
"@typescript-eslint/parser": "^5.38.1",
......@@ -1257,12 +1255,6 @@
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"node_modules/@types/lodash": {
"version": "4.14.182",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
"integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==",
"dev": true
},
"node_modules/@types/node": {
"version": "18.7.23",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.23.tgz",
......@@ -4724,9 +4716,10 @@
}
},
"node_modules/typed-reflector": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/typed-reflector/-/typed-reflector-1.0.11.tgz",
"integrity": "sha512-OhryVYaR+tBEW9Yt2PsPqAniNfbVk1idKbnLxBCBPUSHVRm+Ajik/QxifoJUuGoaXAZDLW9JlJTO6ctXGZX9gQ==",
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/typed-reflector/-/typed-reflector-1.0.12.tgz",
"integrity": "sha512-GMlwkgutVGKvnWXglo/SuMMJsxnw++ThIzJhFM7O01FD0pBYV+QiFQChlHkh+6vL4AtfDj3GdFaDPqssnn+wCQ==",
"license": "MIT",
"dependencies": {
"reflect-metadata": "^0.1.13"
}
......@@ -5889,12 +5882,6 @@
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"@types/lodash": {
"version": "4.14.182",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
"integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==",
"dev": true
},
"@types/node": {
"version": "18.7.23",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.23.tgz",
......@@ -8407,9 +8394,9 @@
"dev": true
},
"typed-reflector": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/typed-reflector/-/typed-reflector-1.0.11.tgz",
"integrity": "sha512-OhryVYaR+tBEW9Yt2PsPqAniNfbVk1idKbnLxBCBPUSHVRm+Ajik/QxifoJUuGoaXAZDLW9JlJTO6ctXGZX9gQ==",
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/typed-reflector/-/typed-reflector-1.0.12.tgz",
"integrity": "sha512-GMlwkgutVGKvnWXglo/SuMMJsxnw++ThIzJhFM7O01FD0pBYV+QiFQChlHkh+6vL4AtfDj3GdFaDPqssnn+wCQ==",
"requires": {
"reflect-metadata": "^0.1.13"
}
......
......@@ -3,12 +3,11 @@ import { AnyClass, AragamiOptions, Awaitable, ClassType } from './def';
import { RedisDriver } from './drivers/redis';
import { MemoryDriver } from './drivers/memory';
import { reflector } from './metadata';
import { instanceToPlain, plainToInstance } from 'class-transformer';
import { encode, decode } from 'encoded-buffer';
import { makeArray, MayBeArray } from './utility/utility';
import _ from 'lodash';
import { PartialDeep } from './utility/partial-deep';
import { wrapClass } from './utility/encode-decode';
export class Aragami {
readonly driver: BaseDriver;
......@@ -40,7 +39,7 @@ export class Aragami {
return o;
}
if (prototype) {
o = plainToInstance(prototype, o);
o = wrapClass(prototype, o);
}
const keyTransformer = reflector.get('AragamiCacheKey', o);
if (!keyTransformer) {
......@@ -57,11 +56,11 @@ export class Aragami {
}
private encode(o: any) {
return encode(instanceToPlain(o));
return encode(o);
}
private decode<T>(cl: ClassType<T>, value: Buffer) {
return plainToInstance(cl, decode(value)[0]);
return wrapClass(cl, decode(value)[0]);
}
async get<T>(cl: ClassType<T>, key: string) {
......@@ -99,7 +98,7 @@ export class Aragami {
return o;
}
if (prototype) {
o = plainToInstance(prototype, o);
o = wrapClass(prototype, o);
}
const buf = this.encode(o);
await this.driver.set(
......@@ -185,11 +184,11 @@ export class Aragami {
const baseKey = this.getBaseKey(o);
const keyTransformers = reflector.getArray('AragamiLockKeys', o);
const actualKeys = await Promise.all(keyTransformers.map((fn) => fn(o)));
return _.compact(
actualKeys.flatMap((mayBeKeyArray) =>
return actualKeys
.flatMap((mayBeKeyArray) =>
makeArray(mayBeKeyArray).map((key) => `${baseKey}:${key}`),
),
);
)
.filter((s) => !!s);
}
async lock<R>(
......@@ -278,7 +277,7 @@ export class Aragami {
return o;
}
if (prototype) {
o = plainToInstance(prototype, o);
o = wrapClass(prototype, o);
}
const buf = this.encode(o);
const key =
......
import { Metadata } from './metadata';
import { Awaitable, TypedMethodDecorator } from './def';
import { Awaitable, ClassType, TypedMethodDecorator } from './def';
import { MayBeArray } from './utility/utility';
import { Type } from 'class-transformer';
import { MergePropertyDecorators } from './utility/merge';
export const CacheTTL = (ttl: number): ClassDecorator & MethodDecorator =>
Metadata.set('AragamiCacheTTL', ttl);
......@@ -42,3 +44,6 @@ export const LockKey = DrainKey<MayBeArray<string>>(
(cb) => Metadata.append('AragamiLockKeys', cb),
(o, key) => `${key}_${o}`,
);
export const CastType = <T>(cb: () => ClassType<T>): PropertyDecorator =>
MergePropertyDecorators([Metadata.set('AragamiType', cb), Type(cb)]);
import { MetadataSetter, Reflector } from 'typed-reflector';
import { MayBeArray } from './utility/utility';
import { Awaitable } from './def';
import { AnyClass, Awaitable } from './def';
interface MetadataMap {
AragamiCacheTTL: number;
AragamiCachePrefix: string;
AragamiCacheKey: (obj: any) => Awaitable<string>;
AragamiType: () => AnyClass;
}
interface MetadataArrayMap {
......
import { AnyClass, ClassType } from '../def';
import { reflector } from '../metadata';
export const dewrapClass = (value: any) => {
return value;
};
function isClass(target: any) {
if (typeof target !== 'function') {
return false;
}
return (
target.constructor === Function &&
(target === Date || target.toString().startsWith('class '))
);
}
export const wrapClass = <T>(
cl: ClassType<T>,
value: any,
visited = new Set(),
): T => {
if (
!isClass(cl) ||
value instanceof cl ||
value == null ||
visited.has(value)
) {
return value;
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (cl === Date) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return new Date(value) as any;
}
visited.add(value);
const instance = new cl();
const runField = (key: string, val: any) => {
const meta = reflector.get('AragamiType', cl, key);
let fieldCl: AnyClass;
if (meta) {
fieldCl = meta();
} else {
// use design:type
fieldCl = Reflect.getMetadata('design:type', cl.prototype, key);
}
return wrapClass(fieldCl, val, visited);
};
for (const key of Object.keys(value)) {
const field = value[key];
if (Array.isArray(field)) {
instance[key] = field.map((v: any) => runField(key, v));
} else {
instance[key] = runField(key, field);
}
}
return instance;
};
export function MergePropertyDecorators(
decs: PropertyDecorator[],
): PropertyDecorator {
return (obj, key) => {
for (const dec of decs) {
dec(obj, key);
}
};
}
export function MergeMethodDecorators(
decs: MethodDecorator[],
): MethodDecorator {
return (obj, key, descriptor) => {
for (const dec of decs) {
dec(obj, key, descriptor);
}
};
}
export function MergeClassDecorators(decs: ClassDecorator[]): ClassDecorator {
return (obj) => {
for (const dec of decs) {
dec(obj);
}
};
}
export function MergeParameterDecorators(
decs: ParameterDecorator[],
): ParameterDecorator {
return (obj, key, index) => {
for (const dec of decs) {
dec(obj, key, index);
}
};
}
export function MergeClassOrMethodDecorators(
decs: (ClassDecorator & MethodDecorator)[],
): ClassDecorator & MethodDecorator {
return (obj, key?, descriptor?) => {
if (descriptor) {
for (const dec of decs) {
dec(obj, key, descriptor);
}
} else {
for (const dec of decs) {
dec(obj);
}
}
};
}
......@@ -2,7 +2,6 @@ import { Metadata, reflector } from './metadata';
import { makeArray, MayBeArray } from './utility/utility';
import { Awaitable, ClassType, TypedMethodDecorator } from './def';
import { Aragami } from './aragami';
import _ from 'lodash';
export const WithKey = (
factory: (param: any, obj: any, key: string) => Awaitable<any> = (param) =>
......@@ -37,8 +36,8 @@ export class WrapDecoratorBuilder {
);
const keys = (
await Promise.all(
_.compact(
lockKeyParams.map(async (fun, i) => {
lockKeyParams
.map(async (fun, i) => {
if (!fun) return;
const keyResult = (await fun(
args[i],
......@@ -46,8 +45,8 @@ export class WrapDecoratorBuilder {
key as string,
)) as MayBeArray<any>;
return makeArray(keyResult);
}),
),
})
.filter((s) => !!s),
)
).flat();
return aragami.lock(keys, () => oldFun.apply(this, args));
......
......@@ -26,6 +26,10 @@ describe('Aragami.', () => {
async getName() {
return `n.${this.name}`;
}
getAge() {
return this.age;
}
}
const user = new User();
......@@ -40,6 +44,8 @@ describe('Aragami.', () => {
const tmpUser = await aragami.set(User, { name: 'Nanahira', age: 17 });
expect(tmpUser).toEqual({ name: 'Nanahira', age: 17 });
expect(tmpUser).toBeInstanceOf(User);
expect(tmpUser.getName()).resolves.toBe('n.Nanahira');
expect(tmpUser.getAge()).toBe(17);
await expect(aragami.del(tmpUser)).resolves.toBeTruthy();
const userGet = await aragami.get(User, 'n.John');
expect(userGet).toEqual({ name: 'John', age: 30 });
......
import { CastType } from '../src/decorators';
import { wrapClass } from '../src/utility/encode-decode';
class Address {
street: string;
city: string;
formatAddress() {
return `${this.street}, ${this.city}`;
}
}
class User {
name: string;
@CastType(() => Address)
address: Address;
@CastType(() => Date)
createdAt: Date;
@CastType(() => Address)
addresses: Address[];
}
describe('wrapClass', () => {
it('should instantiate nested classes with CastType', () => {
const input = {
name: 'John',
address: {
street: '123 St',
city: 'NYC',
},
createdAt: '2023-01-01T00:00:00.000Z',
addresses: [
{ street: 'Street 1', city: 'City 1' },
{ street: 'Street 2', city: 'City 2' },
],
};
const result = wrapClass(User, input);
expect(result).toBeInstanceOf(User);
expect(result.address).toBeInstanceOf(Address);
expect(result.createdAt).toBeInstanceOf(Date);
expect(result.addresses[0]).toBeInstanceOf(Address);
expect(result.address.street).toBe('123 St');
expect(result.addresses[1].city).toBe('City 2');
expect(result.createdAt.toISOString()).toBe('2023-01-01T00:00:00.000Z');
expect(result.address.formatAddress()).toBe('123 St, NYC');
expect(result.addresses[0].formatAddress()).toBe('Street 1, City 1');
expect(result.addresses[1].formatAddress()).toBe('Street 2, City 2');
});
it('should return the same instance if already an instance of the class', () => {
const user = new User();
user.name = 'Existing';
const result = wrapClass(User, user);
expect(result).toBe(user);
});
it('should handle null or undefined gracefully', () => {
expect(wrapClass(User, null)).toBeNull();
expect(wrapClass(User, undefined)).toBeUndefined();
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment