-
Notifications
You must be signed in to change notification settings - Fork 0
/
command.ts
121 lines (101 loc) · 2.98 KB
/
command.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
export interface StateCommand<Identity extends string, T> {
identity: string;
readonly silent?: boolean;
clone(): StateCommand<Identity, T>;
withPayload<TPayload>(): StateCommand<
Identity,
unknown extends T ? TPayload : T | TPayload
>;
execute<TValue extends T>(
payload: TValue,
): ExecutedStateCommand<Identity, TValue, this>;
with<TMetadata extends object>(metadata: TMetadata): this & TMetadata;
}
export type ExecutedStateCommand<
Identity extends string,
T,
TStateCommand extends StateCommand<Identity, T>,
> = Readonly<
Omit<TStateCommand, 'execute' | 'with' | 'clone' | 'withPayload'> & {
consumerValue: T;
executionDate: string;
}
>;
export type GenericStateCommand = StateCommand<string, unknown>;
export type ExecutedGenericStateCommand = ExecutedStateCommand<
string,
unknown,
StateCommand<any, any>
>;
type GetCommandInfo<T extends GenericStateCommand> = T extends StateCommand<
infer TIdentity,
infer TPayload
>
? { identity: TIdentity; payload: TPayload }
: never;
export type CommandIdentity<T extends GenericStateCommand> =
GetCommandInfo<T>['identity'];
export type CommandPayload<T extends GenericStateCommand> =
GetCommandInfo<T>['payload'];
class StateCommandImpl<Identity extends string, T>
implements StateCommand<Identity, T>
{
readonly silent?: boolean | undefined;
constructor(public readonly identity: Identity) {}
clone(): StateCommand<Identity, T> {
return this.#clone(this, false);
}
withPayload<TPayload>(): StateCommand<
Identity,
unknown extends T ? TPayload : T | TPayload
> {
return this as {} as StateCommand<
Identity,
unknown extends T ? TPayload : T | TPayload
>;
}
execute<TValue extends T>(
payload: TValue,
): Readonly<
Omit<this, 'execute' | 'with' | 'clone' | 'withPayload'> & {
consumerValue: TValue;
executionDate: string;
}
> {
return this.#extend(
this,
{
consumerValue: payload,
executionDate: new Date().toISOString(),
},
true,
);
}
with<TMetadata extends object>(metadata: TMetadata): this & TMetadata {
return this.#extend(this, metadata, false);
}
#extend<TStateCommand extends StateCommand<any, any>, T>(
command: TStateCommand,
metadata: T,
readonly: boolean,
) {
const clonedCommand = this.#clone(command, readonly);
return Object.assign(clonedCommand, metadata);
}
#clone<TStateCommand extends StateCommand<any, any>>(
command: TStateCommand,
readonly: boolean,
): TStateCommand {
const base = readonly ? {} : createCommand(command.identity);
return Object.assign(base, JSON.parse(JSON.stringify(command)));
}
}
export function createCommand<Identity extends string, T = unknown>(
identity: Identity,
): StateCommand<Identity, T> {
return new StateCommandImpl(identity);
}
export type MapCommandToActions<T extends Record<string, GenericStateCommand>> =
{
[K in keyof T]: (payload: CommandPayload<T[K]>) => void;
};