-
Notifications
You must be signed in to change notification settings - Fork 0
/
interactive.ts
93 lines (78 loc) · 2.94 KB
/
interactive.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
import { tap, equals, pick, identity, always } from 'ramda';
import yargs from 'yargs';
import Future, { FutureInstance } from 'fluture';
import difflet from 'difflet';
import { Message } from './message';
import * as Terminal from './interactive/terminal';
export type TransactionRecord<In, Err, Out> = [
{ type: string, data: In },
{ result: Out } | { result: null, error: Err }
];
export type TransactionRecordOut<In, Err, Out> = [
Message<In>,
{ result: Out } | { error: Err }
];
let entries: TransactionRecordOut<any, any, any>[] = [];
let stack: TransactionRecord<any, any, any>[] | null = null;
const flags: any = yargs(process.argv).argv;
const diffView = difflet({ indent: 2 });
const clean = pick(['status', 'headers', 'data']);
if (flags.out) {
Terminal.writeHook(flags.out, () => JSON.stringify(entries, null, 2));
}
if (flags.in) {
stack = Terminal.readLog(flags.in);
}
const logger = <In, Err, Out>(cmd: Message<In>): [(e: Err) => Err, (r: Out) => Out] => [
tap((error: Err) => entries.push([cmd, { result: null, error: clean((error as any).response) }])),
tap((result: Out) => entries.push([cmd, { result: clean(result) }]))
];
const log = flags.out
? <In, Err, Out>(cmd: Message<In>) => Future.bimap(...logger<In, Err, Out>(cmd))
: always(identity);
const handleEmptyStack = <In, Err, Out>(cmd: Message<In>, tx: FutureInstance<Err, Out>) => (
new Future<Err, Out>((reject, resolve) => {
Terminal.prompt(
[`Unexpected ${cmd.constructor.name} command, transcript stack is empty.`, ''],
'Execute live effect?'
).then((answer: string) => (
(/^y/i).test(answer)
? tx.fork(reject, resolve)
: reject(new Error('Unexpected empty transcript stack') as any as Err)
));
})
);
const compare = <In, Err, Out>(
cmd: Message<In>,
tx: FutureInstance<Err, Out>,
[req, res]: TransactionRecord<In, Err, Out>
): FutureInstance<Err, Out> => (
equals(req, cmd.toJSON())
? log(cmd)(!!res.result ? Future.of(res.result) : Future.reject((res as any).error as Err))
: new Future<Err, Out>((reject, resolve) => {
Terminal.prompt(
[
'', '', `Executed command \`${cmd.constructor.name}\` differs from transcript:`,
'', '', diffView.compare(req, cmd.toJSON()), ''
],
'Execute live effect?'
).then((answer: string) => (
(/^y/i).test(answer)
? tx.fork(reject, resolve)
: res.result
? resolve(logger[1](res.result))
: reject(logger[0]((res as any).error) as Err)
));
})
);
const applyInner = <In, Err, Out>(cmd: Message<In>, tx: FutureInstance<Err, Out>) => (
!stack
? tx
: stack.length === 0
? handleEmptyStack(cmd, tx)
: compare(cmd, tx, stack.shift()!)
);
type ExecFn<In, Err, Out> = (cmd: Message<In>) => FutureInstance<Err, Out>;
export const apply = <In, Err, Out>(exec: ExecFn<In, Err, Out>): ExecFn<In, Err, Out> => (
(cmd: Message<In>) => applyInner(cmd, log(cmd)(exec(cmd)))
);