-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathdb.js
228 lines (188 loc) · 6.48 KB
/
db.js
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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
const Path = require('path');
const { emitError } = require('./errors');
const config = require('./config');
const log = require('./log');
const DbTypesGenerator = require('./typescript/db-typings');
async function connect (connector, attempt = 0) {
try {
await connector.connect();
log.success(`Connected to ${connector._name} database`);
attempt = 0;
} catch (error) {
log.error(`Connection to ${connector._name} database was failed`, error);
emitError({
isSystem: true,
type: 'DatabaseConnectionError',
name: connector._name,
error
});
setTimeout(() => {
log.info(`Reconnecting to ${connector._name}... Attempt #${attempt}`);
connect(connector, attempt + 1);
}, 5000);
}
}
function makeModel (connector, modelName, schema) {
for (const field in schema) {
const descriptor = schema[field];
if (descriptor instanceof ModelField) {
schema[field] = descriptor.info;
}
}
const model = connector.makeModel(modelName, schema);
if (model.init) model.init();
Object.defineProperty(connector, modelName, { value: model, writable: false });
return model;
}
exports.makeModel = makeModel;
function loadModel (connector, modelName, schema) {
if ('default' in schema) {
schema = schema.default;
}
if (connector.makeModel) {
makeModel(connector, modelName, schema);
} else {
// todo? deprecate
const model = connector.getModel(modelName, schema);
Object.defineProperty(connector, modelName, { value: model, writable: false });
}
}
// #paste :modelsMap
function* iterModels (dbConfigName) {
// #region :iterModels
const MODELS_BASE_PATH = Path.join(process.cwd(), 'models', dbConfigName);
for (const entry of readdirSync(MODELS_BASE_PATH)) {
if (!entry.endsWith('.js')) continue;
const name = entry.slice(0, -3);
const path = Path.join(MODELS_BASE_PATH, entry);
if (!existsSync(path)) {
log.warn(`Database model "${name}" not found for "${dbConfigName}" configuration`);
return;
}
yield { name, path };
}
// #endregion
}
function maintainConnector (connector, dbConfig) {
if (getFlag('--ts-build')) {
return connector;
}
connect(connector);
const types = new DbTypesGenerator(connector);
for (const { name, path, schema } of iterModels(dbConfig._name)) {
try {
// todo: schema types
loadModel(connector, name, schema || require(path));
} catch (error) {
log.error(`Cannot load "${name}" model for "${dbConfig._name}" configuration`, error);
process.exit(-1);
}
types.add(name, connector[name]);
}
types.write(dbConfig._name);
return connector;
}
const connections = {};
const drivers = {};
exports.registerDriver = (DriverClass, driverName) => {
// todo: DRY
for (const key in config.db) {
if (key == driverName || key.startsWith(driverName + '.')) {
const dbConfig = config.db[key];
// todo! write docs
if (dbConfig.template) continue;
const connector = new DriverClass(dbConfig);
connector._self = DriverClass;
connector._name = key;
dbConfig._name = key;
connections[key] = maintainConnector(connector, dbConfig);
}
}
DriverClass.connect = (configKey, options) => {
if (options && !options.identifier && !options.name) {
return log.warn('Templated database connection must have `identifier` field');
}
// Key of configuration in config.db object
configKey = driverName + (configKey ? ('.' + configKey) : '');
// Unique name of current connection (equals configKey when not templated)
const connectionName = options
? (driverName + '.' + (options.identifier || options.name))
: configKey;
// Reusing connections
if (connectionName in connections) {
return connections[connectionName];
}
if (configKey in config.db) {
let dbConfig = config.db[configKey];
if (options) {
// Spread is used to make mutable copy without side-effects
dbConfig = { ...dbConfig, ...options };
delete dbConfig.identifier;
}
dbConfig._name = configKey;
const connector = new DriverClass(dbConfig);
connector._self = DriverClass;
connector._name = connectionName;
return connections[connectionName] = maintainConnector(connector, dbConfig);
} else {
log.error(`Database configuration "${configKey}" not found`);
}
};
drivers[driverName] = DriverClass;
return DriverClass;
}
exports.connectDatabase = (configKey, options) => {
const [driverName, connectionName] = configKey.split('.', 2);
const DriverClass = drivers[driverName];
if (DriverClass) return DriverClass.connect(connectionName, options);
else log.error(`Database driver "${driverName}" not registered`);
}
const { EventEmitter } = require('events');
const { existsSync, readdirSync } = require('fs');
const { getFlag } = require('./utils');
exports.DatabaseDriver = class DatabaseDriver extends EventEmitter {}
class ModelField {
info = {};
constructor (type) {
this.info.type = type;
this.info.required = false;
}
get required () {
this.info.required = true;
return this;
}
default (value) {
this.info.default = value;
return this;
}
}
class ModelInt extends ModelField {
constructor () { super('int'); }
}
exports.int = () => new ModelInt();
class ModelLong extends ModelField {
constructor () { super('long'); }
}
exports.long = () => new ModelLong();
class ModelString extends ModelField {
constructor (length) {
super('string');
this.info.length = length;
}
}
exports.string = length => new ModelString(length);
class ModelText extends ModelField {
constructor () { super('text'); }
}
exports.text = () => new ModelText();
class ModelJson extends ModelField {
constructor () { super('json'); }
}
exports.json = () => new ModelJson();
class ModelEnum extends ModelField {
constructor (values) {
super('enum');
this.info.values = values;
}
}
exports.enumerable = (...values) => new ModelEnum(values);