patron-oop 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE.md +7 -0
  3. package/README.md +9 -0
  4. package/commitizen.cjs +50 -0
  5. package/dist/patron.d.ts +173 -0
  6. package/dist/patron.js +363 -0
  7. package/dist/patron.js.map +1 -0
  8. package/dist/patron.mjs +346 -0
  9. package/dist/patron.mjs.map +1 -0
  10. package/eslint.config.mjs +36 -0
  11. package/package.json +48 -0
  12. package/rollup.config.js +35 -0
  13. package/src/Cache.test.ts +20 -0
  14. package/src/Cache.ts +31 -0
  15. package/src/CacheType.ts +4 -0
  16. package/src/Chain.test.ts +72 -0
  17. package/src/Chain.ts +79 -0
  18. package/src/ChainType.ts +7 -0
  19. package/src/Factory.test.ts +16 -0
  20. package/src/Factory.ts +21 -0
  21. package/src/FactoryDynamic.ts +11 -0
  22. package/src/FactoryType.ts +3 -0
  23. package/src/FactoryWithFactories.ts +25 -0
  24. package/src/Guest.test.ts +13 -0
  25. package/src/Guest.ts +11 -0
  26. package/src/GuestAware.ts +11 -0
  27. package/src/GuestAwareType.ts +5 -0
  28. package/src/GuestCast.ts +20 -0
  29. package/src/GuestExecutorType.ts +3 -0
  30. package/src/GuestInTheMiddle.test.ts +71 -0
  31. package/src/GuestInTheMiddle.ts +20 -0
  32. package/src/GuestPool.test.ts +41 -0
  33. package/src/GuestPool.ts +46 -0
  34. package/src/GuestSync.test.ts +11 -0
  35. package/src/GuestSync.ts +14 -0
  36. package/src/GuestType.ts +10 -0
  37. package/src/GuestValueType.ts +5 -0
  38. package/src/Patron.test.ts +20 -0
  39. package/src/Patron.ts +17 -0
  40. package/src/PatronOnce.test.ts +18 -0
  41. package/src/PatronOnce.ts +30 -0
  42. package/src/PatronPool.test.ts +26 -0
  43. package/src/PatronPool.ts +76 -0
  44. package/src/PoolType.ts +7 -0
  45. package/src/Source.test.ts +13 -0
  46. package/src/Source.ts +20 -0
  47. package/src/SourceType.ts +4 -0
  48. package/src/index.ts +16 -0
  49. package/tsconfig.json +26 -0
package/src/Chain.ts ADDED
@@ -0,0 +1,79 @@
1
+ import { Cache } from "./Cache";
2
+ import { Guest } from "./Guest";
3
+ import { GuestPool } from "./GuestPool";
4
+ import { ChainType } from "./ChainType";
5
+ import { GuestInTheMiddle } from "./GuestInTheMiddle";
6
+ import { GuestType } from "./GuestType";
7
+
8
+ export class Chain<T> implements ChainType<T> {
9
+ private theChain: Cache<Record<string, unknown>>;
10
+
11
+ private keysKnown = new Set();
12
+
13
+ private keysFilled = new Set();
14
+
15
+ private filledChainPool = new GuestPool(this);
16
+
17
+ public constructor() {
18
+ this.theChain = new Cache<Record<string, unknown>>(this, {});
19
+ }
20
+
21
+ public resultArray(guest: GuestType<T>) {
22
+ this.filledChainPool.add(
23
+ new GuestInTheMiddle(guest, (value: Record<string, unknown>) =>
24
+ Object.values(value),
25
+ ),
26
+ );
27
+ if (this.isChainFilled()) {
28
+ this.theChain.receiving(
29
+ new Guest((chain) => {
30
+ this.filledChainPool.receive(Object.values(chain));
31
+ }),
32
+ );
33
+ }
34
+
35
+ return this;
36
+ }
37
+
38
+ public result(guest: GuestType<T>) {
39
+ if (this.isChainFilled()) {
40
+ this.filledChainPool.add(guest);
41
+ this.theChain.receiving(
42
+ new Guest((chain) => {
43
+ this.filledChainPool.receive(chain);
44
+ }),
45
+ );
46
+ } else {
47
+ this.filledChainPool.add(guest);
48
+ }
49
+ return this;
50
+ }
51
+
52
+ public receiveKey<R>(key: string): GuestType<R> {
53
+ this.keysKnown.add(key);
54
+ return new Guest((value) => {
55
+ // Обернул в очередь чтобы можно было синхронно наполнить очередь известных ключей
56
+ queueMicrotask(() => {
57
+ this.theChain.receiving(
58
+ new Guest((chain) => {
59
+ this.keysFilled.add(key);
60
+ const lastChain = {
61
+ ...chain,
62
+ [key]: value,
63
+ };
64
+ this.theChain.receive(lastChain);
65
+ if (this.isChainFilled()) {
66
+ this.filledChainPool.receive(lastChain);
67
+ }
68
+ }),
69
+ );
70
+ });
71
+ });
72
+ }
73
+
74
+ private isChainFilled() {
75
+ return (
76
+ this.keysFilled.size > 0 && this.keysFilled.size === this.keysKnown.size
77
+ );
78
+ }
79
+ }
@@ -0,0 +1,7 @@
1
+ import { GuestType } from "./GuestType";
2
+
3
+ export interface ChainType<T = unknown> {
4
+ result(guest: GuestType<T>): this;
5
+ resultArray(guest: GuestType<T>): this;
6
+ receiveKey<R>(key: string): GuestType<R>;
7
+ }
@@ -0,0 +1,16 @@
1
+ import { expect, test } from "vitest";
2
+ import { Factory } from "./Factory";
3
+ import { Guest } from "./Guest";
4
+ import { Source } from "./Source";
5
+
6
+ test("factory", () => {
7
+ const sourceFactory = new Factory(Source);
8
+
9
+ const source = sourceFactory.create(42);
10
+
11
+ source.receiving(
12
+ new Guest((value) => {
13
+ expect(value).toBe(42);
14
+ }),
15
+ );
16
+ });
package/src/Factory.ts ADDED
@@ -0,0 +1,21 @@
1
+ import { FactoryType } from "./FactoryType";
2
+
3
+ interface Constructable<T> {
4
+ new (...args: unknown[]): T;
5
+ }
6
+
7
+ interface Prototyped<T> {
8
+ prototype: T;
9
+ }
10
+
11
+ export class Factory<T> implements FactoryType<T> {
12
+ public constructor(private constructorFn: Prototyped<T>) {}
13
+
14
+ public create<R extends unknown[], CT = null>(
15
+ ...args: R
16
+ ): CT extends null ? T : CT {
17
+ return new (this.constructorFn as Constructable<T>)(
18
+ ...args,
19
+ ) as CT extends null ? T : CT;
20
+ }
21
+ }
@@ -0,0 +1,11 @@
1
+ import { FactoryType } from "./FactoryType";
2
+
3
+ export class FactoryDynamic<T> implements FactoryType<T> {
4
+ public constructor(private creationFn: (...args: unknown[]) => T) {}
5
+
6
+ public create<R extends unknown[], CT = null>(
7
+ ...args: R
8
+ ): CT extends null ? T : CT {
9
+ return this.creationFn(...args) as CT extends null ? T : CT;
10
+ }
11
+ }
@@ -0,0 +1,3 @@
1
+ export interface FactoryType<T> {
2
+ create<R extends unknown[], CT = null>(...args: R): CT extends null ? T : CT;
3
+ }
@@ -0,0 +1,25 @@
1
+ import { FactoryType } from "./FactoryType";
2
+
3
+ interface Constructable<T> {
4
+ new (...args: unknown[]): T;
5
+ }
6
+
7
+ interface Prototyped<T> {
8
+ prototype: T;
9
+ }
10
+
11
+ export class FactoryWithFactories<T> implements FactoryType<T> {
12
+ public constructor(
13
+ private constructorFn: Prototyped<T>,
14
+ private factories: Record<string, unknown> = {},
15
+ ) {}
16
+
17
+ public create<R extends unknown[], CT = null>(
18
+ ...args: R
19
+ ): CT extends null ? T : CT {
20
+ return new (this.constructorFn as Constructable<T>)(
21
+ ...args,
22
+ this.factories,
23
+ ) as CT extends null ? T : CT;
24
+ }
25
+ }
@@ -0,0 +1,13 @@
1
+ import { expect, test } from "vitest";
2
+ import { Guest } from "./Guest";
3
+ import { Source } from "./Source";
4
+
5
+ test("guest dynamic", () => {
6
+ const one = new Source(1);
7
+
8
+ one.receiving(
9
+ new Guest((value) => {
10
+ expect(value).toBe(1);
11
+ }),
12
+ );
13
+ });
package/src/Guest.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { GuestExecutorType } from "./GuestExecutorType";
2
+ import { GuestType, ReceiveOptions } from "./GuestType";
3
+
4
+ export class Guest<T> implements GuestType<T> {
5
+ public constructor(private receiver: GuestExecutorType<T>) {}
6
+
7
+ public receive(value: T, options?: ReceiveOptions) {
8
+ this.receiver(value, options);
9
+ return this;
10
+ }
11
+ }
@@ -0,0 +1,11 @@
1
+ import { GuestType } from "./GuestType";
2
+ import { GuestAwareType } from "./GuestAwareType";
3
+
4
+ export class GuestAware<T = unknown> implements GuestAwareType<T> {
5
+ public constructor(private guestReceiver: (guest: GuestType<T>) => void) {}
6
+
7
+ public receiving(guest: GuestType<T>): GuestType<T> {
8
+ this.guestReceiver(guest);
9
+ return guest;
10
+ }
11
+ }
@@ -0,0 +1,5 @@
1
+ import { GuestType } from "./GuestType";
2
+
3
+ export interface GuestAwareType<T = unknown> {
4
+ receiving(guest: GuestType<T>): unknown;
5
+ }
@@ -0,0 +1,20 @@
1
+ import { GuestType, ReceiveOptions } from "./GuestType";
2
+
3
+ export class GuestCast<T> implements GuestType<T> {
4
+ public constructor(
5
+ private sourceGuest: GuestType<unknown>,
6
+ private targetGuest: GuestType<T>,
7
+ ) {}
8
+
9
+ introduction() {
10
+ if (!this.sourceGuest.introduction) {
11
+ return "guest";
12
+ }
13
+ return this.sourceGuest.introduction();
14
+ }
15
+
16
+ receive(value: T, options?: ReceiveOptions): this {
17
+ this.targetGuest.receive(value, options);
18
+ return this;
19
+ }
20
+ }
@@ -0,0 +1,3 @@
1
+ import { ReceiveOptions } from "./GuestType";
2
+
3
+ export type GuestExecutorType<T> = (value: T, options?: ReceiveOptions) => void;
@@ -0,0 +1,71 @@
1
+ import { expect, test } from "vitest";
2
+ import { Guest } from "./Guest";
3
+ import { GuestInTheMiddle } from "./GuestInTheMiddle";
4
+ import { Patron } from "./Patron";
5
+ import { Chain } from "./Chain";
6
+ import { Source } from "./Source";
7
+
8
+ test("test guest in the middle", () => {
9
+ const one = new Source(1);
10
+
11
+ let accumValue = 0;
12
+ const guest = new Guest((value: number) => {
13
+ accumValue += value;
14
+ });
15
+ one.receiving(
16
+ new GuestInTheMiddle(guest, (value) => {
17
+ guest.receive(value + 3);
18
+ }),
19
+ );
20
+
21
+ expect(accumValue).toBe(4);
22
+ });
23
+
24
+ test("test patron in the middle", () => {
25
+ const one = new Source(1);
26
+
27
+ let accumValue = 0;
28
+ const guest = new Patron(
29
+ new Guest((value: number) => {
30
+ accumValue += value;
31
+ }),
32
+ );
33
+ one.receiving(
34
+ new GuestInTheMiddle(guest, (value) => {
35
+ guest.receive(value + 3);
36
+ }),
37
+ );
38
+ one.receive(3);
39
+
40
+ setTimeout(() => {
41
+ one.receive(3);
42
+ });
43
+
44
+ setTimeout(() => {
45
+ expect(accumValue).toBe(16);
46
+ });
47
+ });
48
+
49
+ test("test chain in the middle", () => {
50
+ const one = new Source(1);
51
+ const two = new Source(2);
52
+ const chain = new Chain<{ one: number; two: number }>();
53
+
54
+ one.receiving(new Patron(chain.receiveKey("one")));
55
+ two.receiving(new Patron(chain.receiveKey("two")));
56
+
57
+ one.receive(3);
58
+ one.receive(4);
59
+
60
+ const guest = new Patron(
61
+ new Guest((value: { one: number; two: number; three: number }) => {
62
+ expect(Object.values(value).length).toBe(3);
63
+ }),
64
+ );
65
+
66
+ chain.result(
67
+ new GuestInTheMiddle(guest, (value) => {
68
+ guest.receive({ ...value, three: 99 });
69
+ }),
70
+ );
71
+ });
@@ -0,0 +1,20 @@
1
+ import { GuestType, ReceiveOptions } from "./GuestType";
2
+
3
+ export class GuestInTheMiddle<T> implements GuestType<T> {
4
+ public constructor(
5
+ private baseGuest: GuestType<unknown>,
6
+ private middleFn: (value: T, options?: ReceiveOptions) => void,
7
+ ) {}
8
+
9
+ introduction() {
10
+ if (!this.baseGuest.introduction) {
11
+ return "guest";
12
+ }
13
+ return this.baseGuest.introduction();
14
+ }
15
+
16
+ receive(value: T, options?: ReceiveOptions): this {
17
+ this.middleFn(value, options);
18
+ return this;
19
+ }
20
+ }
@@ -0,0 +1,41 @@
1
+ import { expect, test } from "vitest";
2
+ import { Guest } from "./Guest";
3
+ import { Patron } from "./Patron";
4
+ import { GuestPool } from "./GuestPool";
5
+
6
+ test("patron pool with guests", () => {
7
+ const pool = new GuestPool(null);
8
+ let receivedCount = 0;
9
+
10
+ // 2 + 2
11
+ pool.add(
12
+ new Patron(
13
+ new Guest<number>((value) => {
14
+ receivedCount += value;
15
+ }),
16
+ ),
17
+ );
18
+ // 2 + 2
19
+ pool.add(
20
+ new Patron(
21
+ new Guest<number>((value) => {
22
+ receivedCount += value;
23
+ }),
24
+ ),
25
+ );
26
+ // 2
27
+ pool.add(
28
+ new Guest<number>((value) => {
29
+ receivedCount += value;
30
+ }),
31
+ );
32
+ pool.receive(2);
33
+
34
+ setTimeout(() => {
35
+ pool.receive(2);
36
+ });
37
+
38
+ setTimeout(() => {
39
+ expect(receivedCount).toBe(10);
40
+ }, 10);
41
+ });
@@ -0,0 +1,46 @@
1
+ import { PatronPool } from "./PatronPool";
2
+ import { PoolType } from "./PoolType";
3
+ import { GuestType, ReceiveOptions } from "./GuestType";
4
+
5
+ export class GuestPool<T> implements GuestType<T>, PoolType<T> {
6
+ private guests = new Set<GuestType<T>>();
7
+
8
+ private patronPool: PatronPool<T>;
9
+
10
+ public constructor(initiator: unknown) {
11
+ this.patronPool = new PatronPool(initiator);
12
+ }
13
+
14
+ public receive(value: T, options?: ReceiveOptions): this {
15
+ this.deliverToGuests(value, options);
16
+ this.patronPool.receive(value, options);
17
+ return this;
18
+ }
19
+
20
+ public add(guest: GuestType<T>): this {
21
+ if (!guest.introduction || guest.introduction() === "guest") {
22
+ this.guests.add(guest);
23
+ }
24
+ this.patronPool.add(guest);
25
+ return this;
26
+ }
27
+
28
+ public remove(patron: GuestType<T>): this {
29
+ this.guests.delete(patron);
30
+ this.patronPool.remove(patron);
31
+ return this;
32
+ }
33
+
34
+ public distribute(receiving: T, possiblePatron: GuestType<T>): this {
35
+ this.add(possiblePatron);
36
+ this.receive(receiving);
37
+ return this;
38
+ }
39
+
40
+ private deliverToGuests(value: T, options?: ReceiveOptions) {
41
+ this.guests.forEach((target) => {
42
+ target.receive(value, options);
43
+ });
44
+ this.guests.clear();
45
+ }
46
+ }
@@ -0,0 +1,11 @@
1
+ import { expect, test } from "vitest";
2
+ import { Source } from "./Source";
3
+ import { GuestSync } from "./GuestSync";
4
+
5
+ test("guest sync", () => {
6
+ const source = new Source(123);
7
+ const syncGuest = new GuestSync(222);
8
+ expect(syncGuest.value()).toBe(222);
9
+ source.receiving(syncGuest);
10
+ expect(syncGuest.value()).toBe(123);
11
+ });
@@ -0,0 +1,14 @@
1
+ import { GuestValueType } from "./GuestValueType";
2
+
3
+ export class GuestSync<T> implements GuestValueType<T> {
4
+ public constructor(private theValue: T) {}
5
+
6
+ public receive(value: T): this {
7
+ this.theValue = value;
8
+ return this;
9
+ }
10
+
11
+ public value() {
12
+ return this.theValue;
13
+ }
14
+ }
@@ -0,0 +1,10 @@
1
+ type GuestIntroduction = "guest" | "patron";
2
+
3
+ export interface ReceiveOptions {
4
+ data?: unknown;
5
+ }
6
+
7
+ export interface GuestType<T = unknown> {
8
+ receive(value: T, options?: ReceiveOptions): this;
9
+ introduction?(): GuestIntroduction;
10
+ }
@@ -0,0 +1,5 @@
1
+ import { GuestType } from "./GuestType";
2
+
3
+ export interface GuestValueType<T = unknown> extends GuestType<T> {
4
+ value(): T;
5
+ }
@@ -0,0 +1,20 @@
1
+ import { expect, test } from "vitest";
2
+ import { Patron } from "./Patron";
3
+ import { Guest } from "./Guest";
4
+ import { Source } from "./Source";
5
+
6
+ test("patron always guest", () => {
7
+ const one = new Source(1);
8
+ let patronCalledTimes = 0;
9
+ const patron = new Patron(
10
+ new Guest(() => {
11
+ patronCalledTimes += 1;
12
+ }),
13
+ );
14
+ one.receiving(patron);
15
+ one.receive(2);
16
+
17
+ queueMicrotask(() => {
18
+ expect(patronCalledTimes).toBe(2);
19
+ });
20
+ });
package/src/Patron.ts ADDED
@@ -0,0 +1,17 @@
1
+ import { GuestType, ReceiveOptions } from "./GuestType";
2
+
3
+ /**
4
+ * Патрон - это постоянный посетитель
5
+ */
6
+ export class Patron<T> implements GuestType<T> {
7
+ public constructor(private willBePatron: GuestType<T>) {}
8
+
9
+ public introduction() {
10
+ return "patron" as const;
11
+ }
12
+
13
+ public receive(value: T, options?: ReceiveOptions): this {
14
+ this.willBePatron.receive(value, options);
15
+ return this;
16
+ }
17
+ }
@@ -0,0 +1,18 @@
1
+ import { expect, test } from "vitest";
2
+ import { Source } from "./Source";
3
+ import { PatronOnce } from "./PatronOnce";
4
+ import { Guest } from "./Guest";
5
+
6
+ test("patron once", () => {
7
+ const source = new Source(42);
8
+ let calls = 0;
9
+ const patron = new PatronOnce(
10
+ new Guest(() => {
11
+ calls += 1;
12
+ }),
13
+ );
14
+ source.receiving(patron);
15
+ source.receive(22);
16
+
17
+ expect(calls).toBe(1);
18
+ });
@@ -0,0 +1,30 @@
1
+ import { GuestType, ReceiveOptions } from "./GuestType";
2
+ import { PoolType } from "./PoolType";
3
+
4
+ type PoolAware = {
5
+ pool?: PoolType;
6
+ };
7
+
8
+ export class PatronOnce<T> implements GuestType<T> {
9
+ private received = false;
10
+
11
+ public constructor(private baseGuest: GuestType<T>) {}
12
+
13
+ public introduction() {
14
+ return "patron" as const;
15
+ }
16
+
17
+ public receive(value: T, options?: ReceiveOptions): this {
18
+ if (!this.received) {
19
+ this.baseGuest.receive(value, options);
20
+ }
21
+
22
+ const data = options?.data as PoolAware;
23
+ // Если есть пул, то удаляем себя из пула
24
+ if (data?.pool) {
25
+ data.pool.remove(this);
26
+ }
27
+
28
+ return this;
29
+ }
30
+ }
@@ -0,0 +1,26 @@
1
+ import { expect, test } from "vitest";
2
+ import { Guest } from "./Guest";
3
+ import { PatronPool } from "./PatronPool";
4
+ import { Patron } from "./Patron";
5
+
6
+ test("patron pool", () => {
7
+ const pool = new PatronPool(null);
8
+ let receivedCount = 0;
9
+
10
+ pool.add(
11
+ new Patron(
12
+ new Guest<number>((value) => {
13
+ receivedCount += value;
14
+ }),
15
+ ),
16
+ );
17
+ pool.add(
18
+ new Patron(
19
+ new Guest<number>((value) => {
20
+ receivedCount += value;
21
+ expect(receivedCount).toBe(4);
22
+ }),
23
+ ),
24
+ );
25
+ pool.receive(2);
26
+ });
@@ -0,0 +1,76 @@
1
+ import { PoolType } from "./PoolType";
2
+ import { GuestType, ReceiveOptions } from "./GuestType";
3
+
4
+ const poolSets = new Map<PoolType, Set<GuestType>>();
5
+
6
+ /**
7
+ * Удалить патрон из всех пулов
8
+ */
9
+ export const removePatronFromPools = (patron: GuestType) => {
10
+ poolSets.forEach((pool) => {
11
+ pool.delete(patron);
12
+ });
13
+ };
14
+
15
+ export class PatronPool<T> implements PoolType<T> {
16
+ private patrons = new Set<GuestType<T>>();
17
+
18
+ public receive: (value: T, options?: ReceiveOptions) => this;
19
+
20
+ public constructor(private initiator: unknown) {
21
+ poolSets.set(this, this.patrons);
22
+
23
+ let lastMicrotask: (() => void) | null = null;
24
+ const doReceive = (value: T, options?: ReceiveOptions) => {
25
+ this.patrons.forEach((target) => {
26
+ this.sendValueToGuest(value, target, options);
27
+ });
28
+ };
29
+ this.receive = (value: T, options?: ReceiveOptions) => {
30
+ const currentMicroTask = () => {
31
+ if (currentMicroTask === lastMicrotask) {
32
+ doReceive(value, options);
33
+ }
34
+ };
35
+ lastMicrotask = currentMicroTask;
36
+ queueMicrotask(currentMicroTask);
37
+ return this;
38
+ };
39
+ }
40
+
41
+ public add(shouldBePatron: GuestType<T>) {
42
+ if (
43
+ shouldBePatron.introduction &&
44
+ shouldBePatron.introduction() === "patron"
45
+ ) {
46
+ this.patrons.add(shouldBePatron);
47
+ }
48
+ return this;
49
+ }
50
+
51
+ public remove(patron: GuestType<T>) {
52
+ this.patrons.delete(patron);
53
+ return this;
54
+ }
55
+
56
+ public distribute(receiving: T, possiblePatron: GuestType<T>): this {
57
+ this.add(possiblePatron);
58
+ this.sendValueToGuest(receiving, possiblePatron, {});
59
+ return this;
60
+ }
61
+
62
+ private sendValueToGuest(
63
+ value: T,
64
+ guest: GuestType<T>,
65
+ options?: ReceiveOptions,
66
+ ) {
67
+ guest.receive(value, {
68
+ ...options,
69
+ data: {
70
+ ...((options?.data as Record<string, unknown>) ?? {}),
71
+ initiator: this.initiator,
72
+ pool: this,
73
+ },
74
+ });
75
+ }
76
+ }
@@ -0,0 +1,7 @@
1
+ import { GuestType } from "./GuestType";
2
+
3
+ export interface PoolType<T = unknown> extends GuestType<T> {
4
+ add(guest: GuestType<T>): this;
5
+ distribute(receiving: T, possiblePatron: GuestType<T>): this;
6
+ remove(patron: GuestType<T>): this;
7
+ }