/
index.ts
199 lines (169 loc) · 4.82 KB
/
index.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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
import leveldown from "leveldown";
import * as _ from "lodash";
import * as makeDir from "make-dir";
import * as path from "path";
import { PicoFramework } from "pico-framework";
import { rsNext } from "./io.picolabs.next";
import * as krl from "./krl";
import { RulesetEnvironment } from "./KrlCtx";
import { getRotatingFileStream, KrlLogger } from "./KrlLogger";
import { schedulerStartup } from "./modules/schedule";
import { RulesetRegistry } from "./RulesetRegistry";
import { server } from "./server";
const homeDir = require("home-dir");
const version = require("../package.json").version;
/**
* Configuration options that may be set by the user
*/
export interface PicoEngineConfiguration {
/**
* The absolute path to the folder where the engine should store the database and logs.
*
* Default: "~/.pico-engine/"
*/
home?: string;
/**
* The port number the http server should listen on.
*
* If you want an available port assigned to you, set it to 0
*
* Default: 3000
*/
port?: number;
/**
* The base url others should use when addressing your engine.
*
* Default: "http://localhost:3000"
*/
base_url?: string;
/**
* Provide any custom krl modules
*/
modules?: { [domain: string]: krl.Module };
/**
* Trust event.time input. Used for testing
*/
useEventInputTime?: boolean;
log?: KrlLogger;
}
export interface PicoEngine {
version: string;
home: string;
port: number;
base_url: string;
pf: PicoFramework;
uiECI: string;
rsRegistry: RulesetRegistry;
}
export async function startEngine(
configuration: PicoEngineConfiguration = {}
): Promise<PicoEngine> {
let home = configuration.home;
let port = configuration.port;
let base_url = configuration.base_url;
if (typeof home !== "string") {
home = homeDir(".pico-engine") as string;
}
await makeDir(home);
const filePath = path.resolve(home, "pico-engine.log");
const log = configuration.log
? configuration.log
: new KrlLogger(getRotatingFileStream(filePath), "");
const rsRegistry = new RulesetRegistry(home);
const rsEnvironment = new RulesetEnvironment(log);
if (configuration.modules) {
_.each(configuration.modules, function(mod, domain) {
rsEnvironment.modules[domain] = mod;
});
}
const pf = new PicoFramework({
leveldown: leveldown(path.resolve(home, "db")) as any,
environment: rsEnvironment,
rulesetLoader(rid, version) {
return rsRegistry.load(rid, version);
},
onStartupRulesetInitError(pico, rid, version, config, error) {
// TODO mark it as not installed and raise an error event
// throw error;
console.error("TODO raise error", pico.id, rid, version, config, error);
},
onFrameworkEvent(ev) {
switch (ev.type) {
case "startup":
break;
case "startupDone":
log.debug("pico-framework started");
break;
case "txnQueued":
log.debug(ev.type, {
picoId: ev.picoId,
txnId: ev.txn.id,
txn: ev.txn
});
break;
case "txnStart":
case "txnDone":
case "txnError":
log.debug(ev.type, { picoId: ev.picoId, txnId: ev.txn.id });
break;
}
},
useEventInputTime: configuration.useEventInputTime
});
pf.addRuleset(rsNext);
const schdlr = schedulerStartup(pf);
rsEnvironment.addScheduledEvent = schdlr.addScheduledEvent;
rsEnvironment.removeScheduledEvent = schdlr.removeScheduledEvent;
await schdlr.start();
await pf.start();
await pf.rootPico.install("io.picolabs.next", "0.0.0");
const uiChannel = await Object.values(pf.rootPico.channels).find(chann => {
return (
"engine,ui" ===
chann.tags
.slice(0)
.sort()
.join(",")
);
});
const uiECI = uiChannel ? uiChannel.id : "";
const app = server(pf, uiECI, rsRegistry);
if ((!port || !_.isInteger(port) || port < 1) && port !== 0) {
port = 3000;
}
await new Promise(resolve => {
const listener = app.listen(port, () => {
if (listener) {
const addr = listener.address();
if (addr && typeof addr !== "string" && _.isInteger(addr.port)) {
// Get the actual port i.e. if they set port to 0 nodejs will assign you an available port
port = addr.port;
}
}
resolve();
});
});
if (typeof base_url !== "string") {
base_url = `http://localhost:${port}`;
}
log.info(`Listening at ${base_url}`);
pf.event({
eci: uiECI,
domain: "engine",
name: "started",
data: { attrs: {} },
time: 0 // TODO remove this typescript requirement
}).catch(error => {
log.error("Error signaling engine:started event", { error });
// TODO signal all errors engine:error
});
return {
version,
home,
port,
base_url,
pf,
uiECI,
rsRegistry
};
}