@basmilius/http-client 1.13.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -34
- package/dist/decorator/dto/clone.d.ts +0 -3
- package/dist/decorator/dto/fill.d.ts +0 -3
- package/dist/decorator/dto/helper/areEqual.d.ts +0 -4
- package/dist/decorator/dto/helper/assertDto.d.ts +0 -3
- package/dist/decorator/dto/helper/cloneDto.d.ts +0 -3
- package/dist/decorator/dto/helper/executeIfDtoDirtyAndMarkClean.d.ts +0 -3
- package/dist/decorator/dto/helper/isDto.d.ts +0 -3
- package/dist/decorator/dto/helper/isDtoClean.d.ts +0 -3
- package/dist/decorator/dto/helper/isDtoDirty.d.ts +0 -3
- package/dist/decorator/dto/helper/relateDtoTo.d.ts +0 -3
- package/dist/decorator/dto/helper/relateValueTo.d.ts +0 -3
- package/dist/decorator/dto/helper/trackDto.d.ts +0 -3
- package/dist/decorator/dto/helper/unrelateDtoFrom.d.ts +0 -3
- package/dist/decorator/dto/helper/unrelateValueFrom.d.ts +0 -3
- package/dist/decorator/dto/index.d.ts +1 -4
- package/dist/decorator/dto/toJSON.d.ts +0 -3
- package/dist/decorator/index.d.ts +1 -1
- package/dist/dto/Paginated.d.ts +2 -2
- package/dist/index.js +4 -0
- package/dist/{http-client.js.map → index.js.map} +4 -4
- package/package.json +14 -13
- package/src/adapter/HttpAdapter.ts +67 -0
- package/src/adapter/index.ts +1 -0
- package/src/decorator/adapter.ts +10 -0
- package/src/decorator/bound.ts +5 -0
- package/src/decorator/debounce.ts +30 -0
- package/src/decorator/dto/arrayProxy.ts +65 -0
- package/src/decorator/dto/classProxy.ts +49 -0
- package/src/decorator/dto/clone.ts +24 -0
- package/src/decorator/dto/constant.ts +7 -0
- package/src/decorator/dto/fill.ts +18 -0
- package/src/decorator/dto/helper/areEqual.ts +13 -0
- package/src/decorator/dto/helper/assertDto.ts +11 -0
- package/src/decorator/dto/helper/circularProtect.ts +35 -0
- package/src/decorator/dto/helper/cloneDto.ts +9 -0
- package/src/decorator/dto/helper/executeIfDtoDirtyAndMarkClean.ts +16 -0
- package/src/decorator/dto/helper/index.ts +37 -0
- package/src/decorator/dto/helper/instance.ts +5 -0
- package/src/decorator/dto/helper/isDto.ts +9 -0
- package/src/decorator/dto/helper/isDtoClean.ts +10 -0
- package/src/decorator/dto/helper/isDtoDirty.ts +10 -0
- package/src/decorator/dto/helper/markDtoClean.ts +29 -0
- package/src/decorator/dto/helper/markDtoDirty.ts +26 -0
- package/src/decorator/dto/helper/relateDtoTo.ts +13 -0
- package/src/decorator/dto/helper/relateValueTo.ts +22 -0
- package/src/decorator/dto/helper/trackDto.ts +13 -0
- package/src/decorator/dto/helper/triggerDto.ts +18 -0
- package/src/decorator/dto/helper/unrelateDtoFrom.ts +15 -0
- package/src/decorator/dto/helper/unrelateValueFrom.ts +22 -0
- package/src/decorator/dto/index.ts +78 -0
- package/src/decorator/dto/instance.ts +33 -0
- package/src/decorator/dto/instanceProxy.ts +89 -0
- package/src/decorator/dto/map.ts +4 -0
- package/src/decorator/dto/refProxy.ts +61 -0
- package/src/decorator/dto/serialize/deserialize.ts +5 -0
- package/src/decorator/dto/serialize/deserializeArray.ts +5 -0
- package/src/decorator/dto/serialize/deserializeDateTime.ts +6 -0
- package/src/decorator/dto/serialize/deserializeDto.ts +28 -0
- package/src/decorator/dto/serialize/deserializeObject.ts +9 -0
- package/src/decorator/dto/serialize/deserializeUnknown.ts +31 -0
- package/src/decorator/dto/serialize/index.ts +7 -0
- package/src/decorator/dto/serialize/isSerializedDateTime.ts +5 -0
- package/src/decorator/dto/serialize/isSerializedDto.ts +5 -0
- package/src/decorator/dto/serialize/serialize.ts +5 -0
- package/src/decorator/dto/serialize/serializeArray.ts +19 -0
- package/src/decorator/dto/serialize/serializeDateTime.ts +9 -0
- package/src/decorator/dto/serialize/serializeDto.ts +21 -0
- package/src/decorator/dto/serialize/serializeObject.ts +9 -0
- package/src/decorator/dto/serialize/serializeUnknown.ts +34 -0
- package/src/decorator/dto/serialize/types.ts +3 -0
- package/src/decorator/dto/serialize/uuid.ts +3 -0
- package/src/decorator/dto/symbols.ts +11 -0
- package/src/decorator/dto/toJSON.ts +20 -0
- package/src/decorator/index.ts +31 -0
- package/src/dto/BlobResponse.ts +20 -0
- package/src/dto/Paginated.ts +38 -0
- package/src/dto/RequestError.ts +33 -0
- package/src/dto/ValidationError.ts +38 -0
- package/src/dto/index.ts +4 -0
- package/src/http/BaseResponse.ts +31 -0
- package/src/http/BaseService.ts +8 -0
- package/src/http/HttpClient.ts +41 -0
- package/src/http/QueryString.ts +75 -0
- package/src/http/RequestBuilder.ts +222 -0
- package/src/http/helpers.ts +17 -0
- package/src/http/index.ts +19 -0
- package/src/index.ts +5 -0
- package/src/type/index.ts +74 -0
- package/LICENSE +0 -21
- package/dist/http-client.js +0 -1095
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { markDtoDirty, trackDto, triggerDto } from './helper';
|
|
2
|
+
import { PARENT, PARENT_KEY, PROXY } from './symbols';
|
|
3
|
+
import type DtoInstance from './instance';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
/**
|
|
7
|
+
* Trap for when a property is deleted from the target. This
|
|
8
|
+
* will mark the parent dto as dirty and trigger an update.
|
|
9
|
+
*/
|
|
10
|
+
deleteProperty(target: unknown[], key: string | symbol): boolean {
|
|
11
|
+
Reflect.deleteProperty(target, key);
|
|
12
|
+
|
|
13
|
+
if (ignored(target, key)) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const dto = target[PARENT] as DtoInstance<unknown>;
|
|
18
|
+
dto && triggerDto(dto, target[PARENT_KEY], dto[target[PARENT_KEY]]);
|
|
19
|
+
dto && markDtoDirty(dto, target[PARENT_KEY]);
|
|
20
|
+
|
|
21
|
+
return true;
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Trap for when a property of the target is being accessed. The
|
|
26
|
+
* property access is being tracked for further updates.
|
|
27
|
+
*/
|
|
28
|
+
get(target: unknown[], key: string | symbol, receiver: any): unknown {
|
|
29
|
+
if (key === PROXY) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (ignored(target, key)) {
|
|
34
|
+
return Reflect.get(target, key, receiver);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const dto = target[PARENT] as DtoInstance<unknown>;
|
|
38
|
+
dto && trackDto(dto, target[PARENT_KEY]);
|
|
39
|
+
|
|
40
|
+
return Reflect.get(target, key);
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Trap for when a property of the target is being updated. This
|
|
45
|
+
* will mark the parent dto as dirty and trigger an update.
|
|
46
|
+
*/
|
|
47
|
+
set(target: unknown[], key: string | symbol, value: unknown, receiver: any) {
|
|
48
|
+
if (ignored(target, key)) {
|
|
49
|
+
return Reflect.set(target, key, value, receiver);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const dto = target[PARENT] as DtoInstance<unknown>;
|
|
53
|
+
dto && triggerDto(dto, target[PARENT_KEY], dto[target[PARENT_KEY]]);
|
|
54
|
+
dto && markDtoDirty(dto, target[PARENT_KEY]);
|
|
55
|
+
|
|
56
|
+
return Reflect.set(target, key, value);
|
|
57
|
+
}
|
|
58
|
+
} satisfies ProxyHandler<unknown[]>;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Checks if the given key should be ignored by the proxy.
|
|
62
|
+
*/
|
|
63
|
+
function ignored(target: unknown[], key: string | symbol): key is symbol {
|
|
64
|
+
return typeof key === 'symbol' || typeof target[key] === 'function' || key === 'length';
|
|
65
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Constructor } from '@basmilius/utils';
|
|
2
|
+
import { customRef, markRaw } from 'vue';
|
|
3
|
+
import { ARGS, DIRTY, TRACK, TRIGGER } from './symbols';
|
|
4
|
+
import type DtoInstance from './instance';
|
|
5
|
+
import arrayProxy from './arrayProxy';
|
|
6
|
+
import instanceProxy from './instanceProxy';
|
|
7
|
+
import refProxy from './refProxy';
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
/**
|
|
11
|
+
* Trap for when a dto is being constructed. Reactivity is provided
|
|
12
|
+
* to all arguments and a proxied custom ref is returned that references
|
|
13
|
+
* the actual dto instance.
|
|
14
|
+
*/
|
|
15
|
+
construct(target: Constructor, argsArray: any[], newTarget: Function): DtoInstance<unknown> {
|
|
16
|
+
// note(Bas): This will apply reactivity to any array arguments.
|
|
17
|
+
argsArray = argsArray.map(arg => {
|
|
18
|
+
if (!Array.isArray(arg)) {
|
|
19
|
+
return arg;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return new Proxy(arg, arrayProxy);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const ref = customRef((track, trigger) => {
|
|
26
|
+
const instance = markRaw(Reflect.construct(target, argsArray, newTarget));
|
|
27
|
+
instance[ARGS] = argsArray;
|
|
28
|
+
instance[DIRTY] = false;
|
|
29
|
+
instance[TRACK] = track;
|
|
30
|
+
instance[TRIGGER] = trigger;
|
|
31
|
+
|
|
32
|
+
const proxied = new Proxy(instance, instanceProxy);
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
// note(Bas): track that the dto itself is being accessed.
|
|
36
|
+
get: () => {
|
|
37
|
+
track();
|
|
38
|
+
return proxied;
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
// note(Bas): setter is never used, but we don't want to
|
|
42
|
+
// cause any errors.
|
|
43
|
+
set: () => void 0
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return new Proxy(ref, refProxy) as unknown as DtoInstance<unknown>;
|
|
48
|
+
}
|
|
49
|
+
} satisfies ProxyHandler<Constructor>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { assertDto, isDto } from './helper';
|
|
2
|
+
import { ARGS, DESCRIPTORS, NAME } from './symbols';
|
|
3
|
+
import { DTO_CLASS_MAP } from './map';
|
|
4
|
+
import type DtoInstance from './instance';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Returns a clone of the dto.
|
|
8
|
+
*/
|
|
9
|
+
export default function <T>(this: DtoInstance<T>): DtoInstance<T> {
|
|
10
|
+
const instance = this;
|
|
11
|
+
assertDto(instance);
|
|
12
|
+
|
|
13
|
+
const clazz = DTO_CLASS_MAP[instance[NAME]];
|
|
14
|
+
const clone = new clazz(...instance[ARGS]);
|
|
15
|
+
|
|
16
|
+
Object.entries(this[DESCRIPTORS])
|
|
17
|
+
.filter(([, descriptor]) => !!descriptor.set)
|
|
18
|
+
.map(([name]) => name)
|
|
19
|
+
.forEach(key => clone[key] = isDto(this[key])
|
|
20
|
+
? this[key].clone()
|
|
21
|
+
: this[key]);
|
|
22
|
+
|
|
23
|
+
return clone as DtoInstance<T>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const ENABLE_CIRCULAR_LOGGING = false;
|
|
2
|
+
export const ENABLE_DIRTY_LOGGING = false;
|
|
3
|
+
export const ENABLE_REACTIVE_LOGGING = false;
|
|
4
|
+
export const ENABLE_GET_LOGGING = false;
|
|
5
|
+
export const ENABLE_SET_LOGGING = false;
|
|
6
|
+
export const ENABLE_SERIALIZATION_LOGGING = false;
|
|
7
|
+
export const OVERRIDE_CONSOLE_LOG = false;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { isDto } from './helper';
|
|
2
|
+
import { DESCRIPTORS } from './symbols';
|
|
3
|
+
import type DtoInstance from './instance';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Fills the dto with the given data.
|
|
7
|
+
*/
|
|
8
|
+
export default function (this: DtoInstance<unknown>, data: Record<string, unknown>): void {
|
|
9
|
+
for (let key in data) {
|
|
10
|
+
const descriptor = this[DESCRIPTORS][key];
|
|
11
|
+
|
|
12
|
+
if (isDto(this[key]) && typeof data[key] === 'object') {
|
|
13
|
+
this[key].fill(data[key] as Record<string, unknown>);
|
|
14
|
+
} else if (descriptor && descriptor.set) {
|
|
15
|
+
this[key] = data[key];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import isDto from './isDto';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Checks if the two given values are equal. When both values are a
|
|
5
|
+
* dto, the check is done by firstly converthing them to JSON.
|
|
6
|
+
*/
|
|
7
|
+
export default function (a: unknown, b: unknown): boolean {
|
|
8
|
+
if (!isDto(a) || !isDto(b)) {
|
|
9
|
+
return a === b;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type DtoInstance from '../instance';
|
|
2
|
+
import isDto from './isDto';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Asserts that the given object is a dto.
|
|
6
|
+
*/
|
|
7
|
+
export default function (obj: unknown): asserts obj is DtoInstance<never> {
|
|
8
|
+
if (!isDto(obj)) {
|
|
9
|
+
throw new Error('@dto assert given object is not a class decorated with @Dto.');
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ENABLE_CIRCULAR_LOGGING } from '../constant';
|
|
2
|
+
import type DtoInstance from '../instance';
|
|
3
|
+
|
|
4
|
+
type CircularMap = WeakMap<DtoInstance<unknown>, (string | symbol)[]>;
|
|
5
|
+
const CIRCULAR_MAP = Symbol();
|
|
6
|
+
|
|
7
|
+
export default function <T extends (...args: any[]) => unknown>(fn: T, arg1: number = 0, arg2?: number): T {
|
|
8
|
+
return function (...args: any[]): unknown {
|
|
9
|
+
const hasMap = CIRCULAR_MAP in fn;
|
|
10
|
+
const map: CircularMap = fn[CIRCULAR_MAP] ??= new WeakMap();
|
|
11
|
+
const primary = args[arg1];
|
|
12
|
+
const secondary = arg2 !== undefined ? args[arg2] : 'self';
|
|
13
|
+
|
|
14
|
+
if (typeof primary !== 'object') {
|
|
15
|
+
return fn.call(this, ...args);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!map.has(primary)) {
|
|
19
|
+
map.set(primary, []);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (map.get(primary).includes(secondary)) {
|
|
23
|
+
ENABLE_CIRCULAR_LOGGING && console.log(`%c@dto %ccircular protect %cdetected a circular reference`, 'color: #0891b2', 'color: #059669', 'color: #1d4ed8', {fn, primary, secondary});
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
map.get(primary).push(secondary);
|
|
28
|
+
|
|
29
|
+
const result = fn.call(this, ...args);
|
|
30
|
+
|
|
31
|
+
!hasMap && (delete fn[CIRCULAR_MAP]);
|
|
32
|
+
|
|
33
|
+
return result;
|
|
34
|
+
} as T;
|
|
35
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type DtoInstance from '../instance';
|
|
2
|
+
import isDto from './isDto';
|
|
3
|
+
import isDtoDirty from './isDtoDirty';
|
|
4
|
+
import markDtoClean from './markDtoClean';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Executes the given function if the given dto is marked dirty.
|
|
8
|
+
*/
|
|
9
|
+
export default async function <T, R = void>(obj: T, fn: (dto: T & DtoInstance<T>) => Promise<R>): Promise<void> {
|
|
10
|
+
if (!isDto(obj) || !isDtoDirty(obj)) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
await fn(obj);
|
|
15
|
+
markDtoClean(obj);
|
|
16
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import areEqual from './areEqual';
|
|
2
|
+
import assertDto from './assertDto';
|
|
3
|
+
import circularProtect from './circularProtect';
|
|
4
|
+
import cloneDto from './cloneDto';
|
|
5
|
+
import executeIfDtoDirtyAndMarkClean from './executeIfDtoDirtyAndMarkClean';
|
|
6
|
+
import instance from './instance';
|
|
7
|
+
import isDto from './isDto';
|
|
8
|
+
import isDtoClean from './isDtoClean';
|
|
9
|
+
import isDtoDirty from './isDtoDirty';
|
|
10
|
+
import markDtoClean from './markDtoClean';
|
|
11
|
+
import markDtoDirty from './markDtoDirty';
|
|
12
|
+
import relateDtoTo from './relateDtoTo';
|
|
13
|
+
import relateValueTo from './relateValueTo';
|
|
14
|
+
import trackDto from './trackDto';
|
|
15
|
+
import triggerDto from './triggerDto';
|
|
16
|
+
import unrelateDtoFrom from './unrelateDtoFrom';
|
|
17
|
+
import unrelateValueFrom from './unrelateValueFrom';
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
areEqual,
|
|
21
|
+
assertDto,
|
|
22
|
+
circularProtect,
|
|
23
|
+
cloneDto,
|
|
24
|
+
executeIfDtoDirtyAndMarkClean,
|
|
25
|
+
instance,
|
|
26
|
+
isDto,
|
|
27
|
+
isDtoClean,
|
|
28
|
+
isDtoDirty,
|
|
29
|
+
markDtoClean,
|
|
30
|
+
markDtoDirty,
|
|
31
|
+
relateDtoTo,
|
|
32
|
+
relateValueTo,
|
|
33
|
+
trackDto,
|
|
34
|
+
triggerDto,
|
|
35
|
+
unrelateDtoFrom,
|
|
36
|
+
unrelateValueFrom
|
|
37
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { NAME } from '../symbols';
|
|
2
|
+
import type DtoInstance from '../instance';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Checks if the given object is a dto.
|
|
6
|
+
*/
|
|
7
|
+
export default function (obj: unknown): obj is DtoInstance<unknown> {
|
|
8
|
+
return obj && typeof obj === 'object' && !!obj[NAME];
|
|
9
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ENABLE_DIRTY_LOGGING } from '../constant';
|
|
2
|
+
import { CHILDREN, DIRTY, NAME } from '../symbols';
|
|
3
|
+
import assertDto from './assertDto';
|
|
4
|
+
import circularProtect from './circularProtect';
|
|
5
|
+
import isDtoDirty from './isDtoDirty';
|
|
6
|
+
import triggerDto from './triggerDto';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Marks the given dto clean.
|
|
10
|
+
*/
|
|
11
|
+
const markDtoClean: (obj: unknown) => void = circularProtect(function (obj: unknown): void {
|
|
12
|
+
assertDto(obj);
|
|
13
|
+
|
|
14
|
+
if (obj[DIRTY]) {
|
|
15
|
+
obj[DIRTY] = false;
|
|
16
|
+
ENABLE_DIRTY_LOGGING && console.log(`%c@dto %c${obj[NAME]} %cdirty`, 'color: #0891b2', 'color: #059669', 'color: #1d4ed8', 'marked clean', {obj});
|
|
17
|
+
triggerDto(obj, DIRTY, false, true);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!obj[CHILDREN] || obj[CHILDREN].length === 0) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
obj[CHILDREN]
|
|
25
|
+
.filter(isDtoDirty)
|
|
26
|
+
.forEach(markDtoClean);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export default markDtoClean;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ENABLE_DIRTY_LOGGING } from '../constant';
|
|
2
|
+
import { DIRTY, NAME, PARENT, PARENT_KEY } from '../symbols';
|
|
3
|
+
import assertDto from './assertDto';
|
|
4
|
+
import circularProtect from './circularProtect';
|
|
5
|
+
import triggerDto from './triggerDto';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Marks the given dto dirty.
|
|
9
|
+
*/
|
|
10
|
+
const markDtoDirty: (obj: unknown, key?: string | number) => void = circularProtect(function (obj: unknown, key?: string | number): void {
|
|
11
|
+
assertDto(obj);
|
|
12
|
+
|
|
13
|
+
if (!obj[DIRTY]) {
|
|
14
|
+
obj[DIRTY] = true;
|
|
15
|
+
ENABLE_DIRTY_LOGGING && console.log(`%c@dto %c${obj[NAME]} %cdirty`, 'color: #0891b2', 'color: #059669', 'color: #1d4ed8', 'marked dirty', {obj, key});
|
|
16
|
+
triggerDto(obj, DIRTY, true, false);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!obj[PARENT]) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
markDtoDirty(obj[PARENT], obj[PARENT_KEY]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export default markDtoDirty;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { CHILDREN, PARENT, PARENT_KEY } from '../symbols';
|
|
2
|
+
import type DtoInstance from '../instance';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a parent-child relationship between the given two dtos.
|
|
6
|
+
*/
|
|
7
|
+
export default function (dto: DtoInstance<unknown>, parent: DtoInstance<unknown>, key: string): void {
|
|
8
|
+
parent[CHILDREN] ??= [];
|
|
9
|
+
!parent[CHILDREN].includes(dto) && parent[CHILDREN].push(dto);
|
|
10
|
+
|
|
11
|
+
dto[PARENT] !== parent && (dto[PARENT] = parent);
|
|
12
|
+
dto[PARENT_KEY] !== key && (dto[PARENT_KEY] = key);
|
|
13
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { PARENT, PARENT_KEY } from '../symbols';
|
|
2
|
+
import type DtoInstance from '../instance';
|
|
3
|
+
import isDto from './isDto';
|
|
4
|
+
import relateDtoTo from './relateDtoTo';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates relationships between the given value and dto.
|
|
8
|
+
*/
|
|
9
|
+
export default function (dto: DtoInstance<unknown>, key: string, value: unknown): void {
|
|
10
|
+
if (isDto(value)) {
|
|
11
|
+
relateDtoTo(value, dto, key);
|
|
12
|
+
} else if (Array.isArray(value)) {
|
|
13
|
+
if (value.some(isDto)) {
|
|
14
|
+
value
|
|
15
|
+
.filter(isDto)
|
|
16
|
+
.forEach(val => relateDtoTo(val, dto, key));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
value[PARENT] = dto;
|
|
20
|
+
value[PARENT_KEY] = key;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ENABLE_REACTIVE_LOGGING } from '../constant';
|
|
2
|
+
import { NAME, TRACK } from '../symbols';
|
|
3
|
+
import type DtoInstance from '../instance';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Tracking for when a dto property is being accessed.
|
|
7
|
+
*/
|
|
8
|
+
export default function trackDto(dto: DtoInstance<unknown>, key: string): void {
|
|
9
|
+
const track = dto[TRACK];
|
|
10
|
+
track(dto, key);
|
|
11
|
+
|
|
12
|
+
ENABLE_REACTIVE_LOGGING && console.log(`%c@dto %c${dto[NAME]} %ctrack`, 'color: #0891b2', 'color: #059669', 'color: #1d4ed8', key, {dto});
|
|
13
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ENABLE_REACTIVE_LOGGING } from '../constant';
|
|
2
|
+
import { NAME, PARENT, PARENT_KEY, TRIGGER } from '../symbols';
|
|
3
|
+
import type DtoInstance from '../instance';
|
|
4
|
+
import circularProtect from './circularProtect';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Trigger for when a dto property is being updated.
|
|
8
|
+
*/
|
|
9
|
+
const triggerDto: (dto: DtoInstance<unknown>, key: string | symbol, value: unknown, oldValue?: unknown) => void = circularProtect(function (dto: DtoInstance<unknown>, key: string | symbol, value: unknown, oldValue?: unknown): void {
|
|
10
|
+
const trigger = dto[TRIGGER];
|
|
11
|
+
trigger(dto, key, value, oldValue);
|
|
12
|
+
|
|
13
|
+
ENABLE_REACTIVE_LOGGING && console.log(`%c@dto %c${dto[NAME]} %ctrigger`, 'color: #0891b2', 'color: #059669', 'color: #1d4ed8', key, {dto, value, oldValue});
|
|
14
|
+
|
|
15
|
+
dto[PARENT] && triggerDto(dto[PARENT], dto[PARENT_KEY], dto[PARENT][dto[PARENT_KEY]]);
|
|
16
|
+
}, 0, 1);
|
|
17
|
+
|
|
18
|
+
export default triggerDto;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CHILDREN, PARENT, PARENT_KEY } from '../symbols';
|
|
2
|
+
import type DtoInstance from '../instance';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Removes a parent-child relationship between the given two dtos.
|
|
6
|
+
*/
|
|
7
|
+
export default function (dto: DtoInstance<unknown>, parent: DtoInstance<unknown>): void {
|
|
8
|
+
if (CHILDREN in parent) {
|
|
9
|
+
const index = parent[CHILDREN].indexOf(dto);
|
|
10
|
+
parent[CHILDREN].splice(index, 1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
dto[PARENT] = undefined;
|
|
14
|
+
dto[PARENT_KEY] = undefined;
|
|
15
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { PARENT, PARENT_KEY } from '../symbols';
|
|
2
|
+
import type DtoInstance from '../instance';
|
|
3
|
+
import isDto from './isDto';
|
|
4
|
+
import unrelateDtoFrom from './unrelateDtoFrom';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Removes relationships between the given value and dto.
|
|
8
|
+
*/
|
|
9
|
+
export default function (dto: DtoInstance<unknown>, value: unknown): void {
|
|
10
|
+
if (isDto(value)) {
|
|
11
|
+
unrelateDtoFrom(value, dto);
|
|
12
|
+
} else if (Array.isArray(value)) {
|
|
13
|
+
if (value.some(isDto)) {
|
|
14
|
+
value
|
|
15
|
+
.filter(isDto)
|
|
16
|
+
.forEach(val => unrelateDtoFrom(val, dto));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
value[PARENT] = undefined;
|
|
20
|
+
value[PARENT_KEY] = undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { type Constructor, getPrototypeChain, setObjectMethod, setObjectValue } from '@basmilius/utils';
|
|
2
|
+
import { OVERRIDE_CONSOLE_LOG } from './constant';
|
|
3
|
+
import { instance, isDto } from './helper';
|
|
4
|
+
import { DTO_CLASS_MAP } from './map';
|
|
5
|
+
import { DESCRIPTORS, NAME, PROPERTIES } from './symbols';
|
|
6
|
+
import classProxy from './classProxy';
|
|
7
|
+
import clone from './clone';
|
|
8
|
+
import fill from './fill';
|
|
9
|
+
import type DtoInstance from './instance';
|
|
10
|
+
import toJSON from './toJSON';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Provides reactivity to the decorated class.
|
|
14
|
+
*/
|
|
15
|
+
export default function <T extends Constructor>(clazz: T): T {
|
|
16
|
+
validate(clazz);
|
|
17
|
+
|
|
18
|
+
const descriptors = Object.freeze(getPrototypeChain(clazz));
|
|
19
|
+
const properties = Object.keys(descriptors);
|
|
20
|
+
|
|
21
|
+
setObjectValue(clazz.prototype, DESCRIPTORS, descriptors);
|
|
22
|
+
setObjectValue(clazz.prototype, NAME, clazz.name);
|
|
23
|
+
setObjectValue(clazz.prototype, PROPERTIES, properties);
|
|
24
|
+
setObjectValue(clazz, Symbol.hasInstance, (instance: unknown) => typeof instance === 'object' && instance?.[NAME] === clazz.name);
|
|
25
|
+
|
|
26
|
+
setObjectMethod(clazz, 'clone', clone<T>);
|
|
27
|
+
setObjectMethod(clazz, 'fill', fill);
|
|
28
|
+
setObjectMethod(clazz, 'toJSON', toJSON);
|
|
29
|
+
|
|
30
|
+
return proxy(clazz);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type {
|
|
34
|
+
DtoInstance
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function proxy<T extends Constructor>(clazz: T): T {
|
|
38
|
+
const proxied = new Proxy(clazz, classProxy) as T;
|
|
39
|
+
|
|
40
|
+
DTO_CLASS_MAP[clazz.name] = proxied as unknown as Constructor<DtoInstance<unknown>>;
|
|
41
|
+
|
|
42
|
+
return proxied;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function validate(clazz: Function): void {
|
|
46
|
+
const parent = Object.getPrototypeOf(clazz.prototype);
|
|
47
|
+
|
|
48
|
+
if (NAME in parent) {
|
|
49
|
+
throw new Error(`⛔️ @dto ${clazz.name} cannot extend parent class which is also decorated with @dto ${parent[NAME]}.`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (OVERRIDE_CONSOLE_LOG) {
|
|
54
|
+
const _error = console.error.bind(console);
|
|
55
|
+
const _info = console.info.bind(console);
|
|
56
|
+
const _log = console.log.bind(console);
|
|
57
|
+
const _warn = console.warn.bind(console);
|
|
58
|
+
|
|
59
|
+
const override = (fn: Function) => (...args: unknown[]) => {
|
|
60
|
+
for (let i = args.length - 1; i >= 0; --i) {
|
|
61
|
+
const arg = args[i];
|
|
62
|
+
|
|
63
|
+
if (!isDto(arg)) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const dto = instance(arg);
|
|
68
|
+
args.splice(i, 1, `📦 ${dto[NAME]}`, dto.toJSON());
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return fn(...args);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
console.error = override(_error);
|
|
75
|
+
console.info = override(_info);
|
|
76
|
+
console.log = override(_log);
|
|
77
|
+
console.warn = override(_warn);
|
|
78
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Descriptors } from '@basmilius/utils';
|
|
2
|
+
import type { ARGS, CHILDREN, DESCRIPTORS, DIRTY, NAME, PARENT, PARENT_KEY, PROPERTIES, TRACK, TRIGGER } from './symbols';
|
|
3
|
+
|
|
4
|
+
export default interface DtoInstance<T> extends Function {
|
|
5
|
+
[ARGS]: any[];
|
|
6
|
+
[CHILDREN]?: DtoInstance<unknown>[];
|
|
7
|
+
[DESCRIPTORS]: Descriptors;
|
|
8
|
+
[DIRTY]: boolean;
|
|
9
|
+
[NAME]: string;
|
|
10
|
+
[PARENT]?: DtoInstance<unknown>;
|
|
11
|
+
[PARENT_KEY]?: string;
|
|
12
|
+
[PROPERTIES]: string[];
|
|
13
|
+
[TRACK]: (instance: DtoInstance<unknown>, key: string) => void;
|
|
14
|
+
[TRIGGER]: (instance: DtoInstance<unknown>, key: string | symbol, value: unknown, oldValue?: unknown) => void;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Clones the DTO starting with the original arguments
|
|
18
|
+
* and replaces them with the current values.
|
|
19
|
+
*/
|
|
20
|
+
clone(): DtoInstance<T>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Fills the DTO with the given data.
|
|
24
|
+
*/
|
|
25
|
+
fill(data: Record<string, unknown>): void;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Gets all getters of the DTO and returns them as
|
|
29
|
+
* an object, so {@see JSON.stringify} knows what
|
|
30
|
+
* to do with our DTO.
|
|
31
|
+
*/
|
|
32
|
+
toJSON(): Record<string, unknown>;
|
|
33
|
+
}
|