@basmilius/http-client 1.13.0 → 2.1.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.
Files changed (90) hide show
  1. package/README.md +12 -34
  2. package/dist/decorator/dto/clone.d.ts +0 -3
  3. package/dist/decorator/dto/fill.d.ts +0 -3
  4. package/dist/decorator/dto/helper/areEqual.d.ts +0 -4
  5. package/dist/decorator/dto/helper/assertDto.d.ts +0 -3
  6. package/dist/decorator/dto/helper/cloneDto.d.ts +0 -3
  7. package/dist/decorator/dto/helper/executeIfDtoDirtyAndMarkClean.d.ts +0 -3
  8. package/dist/decorator/dto/helper/isDto.d.ts +0 -3
  9. package/dist/decorator/dto/helper/isDtoClean.d.ts +0 -3
  10. package/dist/decorator/dto/helper/isDtoDirty.d.ts +0 -3
  11. package/dist/decorator/dto/helper/relateDtoTo.d.ts +0 -3
  12. package/dist/decorator/dto/helper/relateValueTo.d.ts +0 -3
  13. package/dist/decorator/dto/helper/trackDto.d.ts +0 -3
  14. package/dist/decorator/dto/helper/unrelateDtoFrom.d.ts +0 -3
  15. package/dist/decorator/dto/helper/unrelateValueFrom.d.ts +0 -3
  16. package/dist/decorator/dto/index.d.ts +1 -4
  17. package/dist/decorator/dto/toJSON.d.ts +0 -3
  18. package/dist/decorator/index.d.ts +1 -1
  19. package/dist/index.js +4 -0
  20. package/dist/{http-client.js.map → index.js.map} +2 -2
  21. package/package.json +14 -13
  22. package/src/adapter/HttpAdapter.ts +67 -0
  23. package/src/adapter/index.ts +1 -0
  24. package/src/decorator/adapter.ts +10 -0
  25. package/src/decorator/bound.ts +5 -0
  26. package/src/decorator/debounce.ts +30 -0
  27. package/src/decorator/dto/arrayProxy.ts +65 -0
  28. package/src/decorator/dto/classProxy.ts +49 -0
  29. package/src/decorator/dto/clone.ts +24 -0
  30. package/src/decorator/dto/constant.ts +7 -0
  31. package/src/decorator/dto/fill.ts +18 -0
  32. package/src/decorator/dto/helper/areEqual.ts +13 -0
  33. package/src/decorator/dto/helper/assertDto.ts +11 -0
  34. package/src/decorator/dto/helper/circularProtect.ts +35 -0
  35. package/src/decorator/dto/helper/cloneDto.ts +9 -0
  36. package/src/decorator/dto/helper/executeIfDtoDirtyAndMarkClean.ts +16 -0
  37. package/src/decorator/dto/helper/index.ts +37 -0
  38. package/src/decorator/dto/helper/instance.ts +5 -0
  39. package/src/decorator/dto/helper/isDto.ts +9 -0
  40. package/src/decorator/dto/helper/isDtoClean.ts +10 -0
  41. package/src/decorator/dto/helper/isDtoDirty.ts +10 -0
  42. package/src/decorator/dto/helper/markDtoClean.ts +29 -0
  43. package/src/decorator/dto/helper/markDtoDirty.ts +26 -0
  44. package/src/decorator/dto/helper/relateDtoTo.ts +13 -0
  45. package/src/decorator/dto/helper/relateValueTo.ts +22 -0
  46. package/src/decorator/dto/helper/trackDto.ts +13 -0
  47. package/src/decorator/dto/helper/triggerDto.ts +18 -0
  48. package/src/decorator/dto/helper/unrelateDtoFrom.ts +15 -0
  49. package/src/decorator/dto/helper/unrelateValueFrom.ts +22 -0
  50. package/src/decorator/dto/index.ts +78 -0
  51. package/src/decorator/dto/instance.ts +33 -0
  52. package/src/decorator/dto/instanceProxy.ts +89 -0
  53. package/src/decorator/dto/map.ts +4 -0
  54. package/src/decorator/dto/refProxy.ts +61 -0
  55. package/src/decorator/dto/serialize/deserialize.ts +5 -0
  56. package/src/decorator/dto/serialize/deserializeArray.ts +5 -0
  57. package/src/decorator/dto/serialize/deserializeDateTime.ts +6 -0
  58. package/src/decorator/dto/serialize/deserializeDto.ts +28 -0
  59. package/src/decorator/dto/serialize/deserializeObject.ts +9 -0
  60. package/src/decorator/dto/serialize/deserializeUnknown.ts +31 -0
  61. package/src/decorator/dto/serialize/index.ts +7 -0
  62. package/src/decorator/dto/serialize/isSerializedDateTime.ts +5 -0
  63. package/src/decorator/dto/serialize/isSerializedDto.ts +5 -0
  64. package/src/decorator/dto/serialize/serialize.ts +5 -0
  65. package/src/decorator/dto/serialize/serializeArray.ts +19 -0
  66. package/src/decorator/dto/serialize/serializeDateTime.ts +9 -0
  67. package/src/decorator/dto/serialize/serializeDto.ts +21 -0
  68. package/src/decorator/dto/serialize/serializeObject.ts +9 -0
  69. package/src/decorator/dto/serialize/serializeUnknown.ts +34 -0
  70. package/src/decorator/dto/serialize/types.ts +3 -0
  71. package/src/decorator/dto/serialize/uuid.ts +3 -0
  72. package/src/decorator/dto/symbols.ts +11 -0
  73. package/src/decorator/dto/toJSON.ts +20 -0
  74. package/src/decorator/index.ts +31 -0
  75. package/src/dto/BlobResponse.ts +20 -0
  76. package/src/dto/Paginated.ts +38 -0
  77. package/src/dto/RequestError.ts +33 -0
  78. package/src/dto/ValidationError.ts +38 -0
  79. package/src/dto/index.ts +4 -0
  80. package/src/http/BaseResponse.ts +31 -0
  81. package/src/http/BaseService.ts +8 -0
  82. package/src/http/HttpClient.ts +41 -0
  83. package/src/http/QueryString.ts +75 -0
  84. package/src/http/RequestBuilder.ts +222 -0
  85. package/src/http/helpers.ts +17 -0
  86. package/src/http/index.ts +19 -0
  87. package/src/index.ts +5 -0
  88. package/src/type/index.ts +74 -0
  89. package/LICENSE +0 -21
  90. package/dist/http-client.js +0 -1095
@@ -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,9 @@
1
+ import assertDto from './assertDto';
2
+
3
+ /**
4
+ * Clones the given dto.
5
+ */
6
+ export default function <T>(obj: T): T {
7
+ assertDto(obj);
8
+ return obj.clone() as T;
9
+ }
@@ -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,5 @@
1
+ import type DtoInstance from '../instance';
2
+
3
+ export default function <T>(dto: DtoInstance<T>): DtoInstance<T> {
4
+ return (dto as any)?.value ?? dto;
5
+ }
@@ -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,10 @@
1
+ import { DIRTY } from '../symbols';
2
+ import assertDto from './assertDto';
3
+
4
+ /**
5
+ * Checks if the given dto is clean.
6
+ */
7
+ export default function (obj: unknown): boolean {
8
+ assertDto(obj);
9
+ return !obj[DIRTY];
10
+ }
@@ -0,0 +1,10 @@
1
+ import { DIRTY } from '../symbols';
2
+ import assertDto from './assertDto';
3
+
4
+ /**
5
+ * Checks if the given dto is dirty.
6
+ */
7
+ export default function (obj: unknown): boolean {
8
+ assertDto(obj);
9
+ return obj[DIRTY];
10
+ }
@@ -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
+ }
@@ -0,0 +1,89 @@
1
+ import { ENABLE_GET_LOGGING, ENABLE_SET_LOGGING } from './constant';
2
+ import { areEqual, markDtoDirty, relateValueTo, trackDto, triggerDto, unrelateValueFrom } from './helper';
3
+ import { DESCRIPTORS, NAME, PROPERTIES, PROXY } from './symbols';
4
+ import arrayProxy from './arrayProxy';
5
+ import type DtoInstance from './instance';
6
+
7
+ export default {
8
+ /**
9
+ * Trap for when a dto property is being accessed. The property
10
+ * access is being tracked for further updates. If the dto has
11
+ * any child dtos, a relationship will be added between them.
12
+ */
13
+ get(target: DtoInstance<unknown>, key: string | symbol, receiver: any): unknown {
14
+ if (key === PROXY) {
15
+ return true;
16
+ }
17
+
18
+ if (typeof key === 'symbol') {
19
+ return Reflect.get(target, key, receiver);
20
+ }
21
+
22
+ const descriptor = target[DESCRIPTORS][key];
23
+
24
+ if (!descriptor || !descriptor.get) {
25
+ return Reflect.get(target, key, receiver);
26
+ }
27
+
28
+ const value = descriptor.get.call(target);
29
+
30
+ ENABLE_GET_LOGGING && console.log(`%c@dto %c${target[NAME]} %cget`, 'color: #0891b2', 'color: #059669', 'color: #1d4ed8', key);
31
+ trackDto(target, key);
32
+
33
+ relateValueTo(target, key, value);
34
+
35
+ return value;
36
+ },
37
+
38
+ /**
39
+ * Trap for when a descriptor of a dto property is requested.
40
+ */
41
+ getOwnPropertyDescriptor(target: DtoInstance<unknown>, key: string | symbol): PropertyDescriptor | undefined {
42
+ return target[DESCRIPTORS][key];
43
+ },
44
+
45
+ /**
46
+ * Trap for when the keys of a dto are requested.
47
+ */
48
+ ownKeys(target: DtoInstance<unknown>) {
49
+ return target[PROPERTIES];
50
+ },
51
+
52
+ /**
53
+ * Trap for when a dto property is being updated. This will
54
+ * mark the dto dirty and trigger an update. If an array is
55
+ * passed, that array will be made reactive as well.
56
+ */
57
+ set(target: DtoInstance<unknown>, key: string | symbol, value: unknown, receiver: any): boolean {
58
+ if (typeof key === 'symbol') {
59
+ return Reflect.set(target, key, value, receiver);
60
+ }
61
+
62
+ const descriptor = target[DESCRIPTORS][key];
63
+
64
+ if (!descriptor || !descriptor.set) {
65
+ return Reflect.set(target, key, value, receiver);
66
+ }
67
+
68
+ const oldValue = descriptor.get?.call(target) ?? undefined;
69
+
70
+ if (areEqual(value, oldValue)) {
71
+ return true;
72
+ }
73
+
74
+ unrelateValueFrom(target, oldValue);
75
+
76
+ if (Array.isArray(value) && !value[PROXY]) {
77
+ value = new Proxy(value, arrayProxy);
78
+ }
79
+
80
+ ENABLE_SET_LOGGING && console.log(`%c@dto %c${target[NAME]} %cset`, 'color: #0891b2', 'color: #059669', 'color: #1d4ed8', key, {value});
81
+ descriptor.set.call(target, value);
82
+
83
+ relateValueTo(target, key, value);
84
+ markDtoDirty(target, key);
85
+ triggerDto(target, key, value, oldValue);
86
+
87
+ return true;
88
+ }
89
+ } satisfies ProxyHandler<DtoInstance<unknown>>;
@@ -0,0 +1,4 @@
1
+ import type { Constructor } from '@basmilius/utils';
2
+ import type DtoInstance from './instance';
3
+
4
+ export const DTO_CLASS_MAP: Record<string, Constructor<DtoInstance<unknown>>> = {};
@@ -0,0 +1,61 @@
1
+ import type { Ref } from 'vue';
2
+ import { PROXY } from './symbols';
3
+ import type DtoInstance from './instance';
4
+
5
+ export default {
6
+ /**
7
+ * Trap for when a ref property is being accessed. The property
8
+ * access is being tracked for further updates. If the requested
9
+ * property is not a part of {Ref}, the get is proxied to the
10
+ * underlying dto instance.
11
+ *
12
+ * A little trick with __v_isRef is done here, all the features
13
+ * of refs are used by our dto, but we don't want Vue to treat
14
+ * it as a ref. We return false here to trick Vue.
15
+ */
16
+ get(target: Ref<DtoInstance<unknown>>, key: string | symbol, receiver: any): unknown {
17
+ if (key === '__v_isRef') {
18
+ return false;
19
+ }
20
+
21
+ if (key === PROXY) {
22
+ return true;
23
+ }
24
+
25
+ if (key in target) {
26
+ return Reflect.get(target, key, receiver);
27
+ }
28
+
29
+ return Reflect.get(target.value, key);
30
+ },
31
+
32
+ /**
33
+ * Trap for when a descriptor of a property is requested, that
34
+ * request is proxied to the underlying dto.
35
+ */
36
+ getOwnPropertyDescriptor(target: Ref<DtoInstance<unknown>>, key: string | symbol): PropertyDescriptor | undefined {
37
+ return Reflect.getOwnPropertyDescriptor(target.value, key);
38
+ },
39
+
40
+ /**
41
+ * Trap for when the keys of the ref are requested, that request
42
+ * is proxied to the underlying dto.
43
+ */
44
+ ownKeys(target: Ref<DtoInstance<unknown>>) {
45
+ return Reflect.ownKeys(target.value);
46
+ },
47
+
48
+ /**
49
+ * Trap for when a ref property is being updated. If the property
50
+ * is not part of {Ref}, the set is proxied to the underlying dto
51
+ * instance. In that proxy, the dto will be marked dirty and an
52
+ * update is triggered.
53
+ */
54
+ set(target: Ref<DtoInstance<unknown>>, key: string | symbol, value: unknown, receiver: any): boolean {
55
+ if (key in target) {
56
+ return Reflect.set(target, key, value, receiver);
57
+ }
58
+
59
+ return Reflect.set(target.value, key, value);
60
+ }
61
+ } satisfies ProxyHandler<Ref<DtoInstance<unknown>>>;
@@ -0,0 +1,5 @@
1
+ import deserializeUnknown from './deserializeUnknown';
2
+
3
+ export default function (serialized: string): unknown {
4
+ return deserializeUnknown(JSON.parse(serialized));
5
+ }