· 8 years ago · Feb 17, 2018, 06:58 AM
1'use strict';
2
3function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
4
5var sqlite3 = _interopDefault(require('sqlite3'));
6var fs = _interopDefault(require('fs'));
7var path = _interopDefault(require('path'));
8
9/**
10 * SQLite client library for Node.js applications
11 *
12 * This source code is licensed under the MIT license found in the
13 * LICENSE.txt file in the root directory of this source tree.
14 */
15
16function prepareParams(args, { offset = 0, excludeLastArg = false } = {}) {
17 const hasOneParam = args.length === offset + 1 + (excludeLastArg ? 1 : 0);
18 if (hasOneParam) {
19 return args[offset];
20 }
21 return Array.prototype.slice.call(args, offset, args.length - (excludeLastArg ? 1 : 0));
22}
23
24/**
25 * SQLite client library for Node.js applications
26 *
27 * Copyright © 2016 Kriasoft, LLC. All rights reserved.
28 *
29 * This source code is licensed under the MIT license found in the
30 * LICENSE.txt file in the root directory of this source tree.
31 */
32
33class Statement {
34
35 constructor(stmt, Promise) {
36 this.stmt = stmt;
37 this.Promise = Promise;
38 }
39
40 get sql() {
41 return this.stmt.sql;
42 }
43
44 get lastID() {
45 return this.stmt.lastID;
46 }
47
48 get changes() {
49 return this.stmt.changes;
50 }
51
52 bind() {
53 const params = prepareParams(arguments);
54 return new this.Promise((resolve, reject) => {
55 this.stmt.bind(params, err => {
56 if (err) {
57 reject(err);
58 } else {
59 resolve(this);
60 }
61 });
62 });
63 }
64
65 reset() {
66 return new this.Promise(resolve => {
67 this.stmt.reset(() => {
68 resolve(this);
69 });
70 });
71 }
72
73 finalize() {
74 return new this.Promise((resolve, reject) => {
75 this.stmt.finalize(err => {
76 if (err) {
77 reject(err);
78 } else {
79 resolve();
80 }
81 });
82 });
83 }
84
85 run() {
86 const params = prepareParams(arguments);
87 return new this.Promise((resolve, reject) => {
88 this.stmt.run(params, err => {
89 if (err) {
90 reject(err);
91 } else {
92 resolve(this);
93 }
94 });
95 });
96 }
97
98 get() {
99 const params = prepareParams(arguments);
100 return new this.Promise((resolve, reject) => {
101 this.stmt.get(params, (err, row) => {
102 if (err) {
103 reject(err);
104 } else {
105 resolve(row);
106 }
107 });
108 });
109 }
110
111 all() {
112 const params = prepareParams(arguments);
113 return new this.Promise((resolve, reject) => {
114 this.stmt.all(params, (err, rows) => {
115 if (err) {
116 reject(err);
117 } else {
118 resolve(rows);
119 }
120 });
121 });
122 }
123
124 each() {
125 const params = prepareParams(arguments, { excludeLastArg: true });
126 const callback = arguments[arguments.length - 1];
127 return new this.Promise((resolve, reject) => {
128 this.stmt.each(params, callback, (err, rowsCount = 0) => {
129 if (err) {
130 reject(err);
131 } else {
132 resolve(rowsCount);
133 }
134 });
135 });
136 }
137
138}
139
140var asyncToGenerator = function (fn) {
141 return function () {
142 var gen = fn.apply(this, arguments);
143 return new Promise(function (resolve, reject) {
144 function step(key, arg) {
145 try {
146 var info = gen[key](arg);
147 var value = info.value;
148 } catch (error) {
149 reject(error);
150 return;
151 }
152
153 if (info.done) {
154 resolve(value);
155 } else {
156 return Promise.resolve(value).then(function (value) {
157 step("next", value);
158 }, function (err) {
159 step("throw", err);
160 });
161 }
162 }
163
164 return step("next");
165 });
166 };
167};
168
169/**
170 * SQLite client library for Node.js applications
171 *
172 * Copyright © 2016 Kriasoft, LLC. All rights reserved.
173 *
174 * This source code is licensed under the MIT license found in the
175 * LICENSE.txt file in the root directory of this source tree.
176 */
177
178// eslint-disable-next-line no-unused-vars,import/no-unresolved,import/extensions
179class Database {
180 /**
181 * Initializes a new instance of the database client.
182 * @param {sqlite3.Database} driver An instance of SQLite3 driver library.
183 * @param {{Promise: PromiseConstructor}} promiseLibrary ES6 Promise library to use.
184 */
185 constructor(driver, promiseLibrary) {
186 this.driver = driver;
187 this.Promise = promiseLibrary.Promise;
188 }
189
190 /**
191 * Close the database.
192 */
193 close() {
194 return new this.Promise((resolve, reject) => {
195 this.driver.close(err => {
196 if (err) {
197 reject(err);
198 } else {
199 resolve();
200 }
201 });
202 });
203 }
204
205 /**
206 * Register listeners for Sqlite3 events
207 *
208 * @param {'trace'|'profile'|'error'|'open'|'close'} eventName
209 * @param {() => void} listener trigger listener function
210 */
211 on(eventName, listener) {
212 this.driver.on(eventName, listener);
213 }
214
215 run(sql) {
216 const params = prepareParams(arguments, { offset: 1 });
217 const Promise = this.Promise;
218 return new Promise((resolve, reject) => {
219 this.driver.run(sql, params, function runExecResult(err) {
220 if (err) {
221 reject(err);
222 } else {
223 // Per https://github.com/mapbox/node-sqlite3/wiki/API#databaserunsql-param--callback
224 // when run() succeeds, the `this' object is a driver statement object. Wrap it as a
225 // Statement.
226 resolve(new Statement(this, Promise));
227 }
228 });
229 });
230 }
231
232 get(sql) {
233 const params = prepareParams(arguments, { offset: 1 });
234 return new this.Promise((resolve, reject) => {
235 this.driver.get(sql, params, (err, row) => {
236 if (err) {
237 reject(err);
238 } else {
239 resolve(row);
240 }
241 });
242 });
243 }
244
245 all(sql) {
246 const params = prepareParams(arguments, { offset: 1 });
247 return new this.Promise((resolve, reject) => {
248 this.driver.all(sql, params, (err, rows) => {
249 if (err) {
250 reject(err);
251 } else {
252 resolve(rows);
253 }
254 });
255 });
256 }
257
258 /**
259 * Runs all the SQL queries in the supplied string. No result rows are retrieved.
260 */
261 exec(sql) {
262 return new this.Promise((resolve, reject) => {
263 this.driver.exec(sql, err => {
264 if (err) {
265 reject(err);
266 } else {
267 resolve(this);
268 }
269 });
270 });
271 }
272
273 each(sql) {
274 const params = prepareParams(arguments, { offset: 1, excludeLastArg: true });
275 const callback = arguments[arguments.length - 1];
276 return new this.Promise((resolve, reject) => {
277 this.driver.each(sql, params, callback, (err, rowsCount = 0) => {
278 if (err) {
279 reject(err);
280 } else {
281 resolve(rowsCount);
282 }
283 });
284 });
285 }
286
287 prepare(sql) {
288 const params = prepareParams(arguments, { offset: 1 });
289 return new this.Promise((resolve, reject) => {
290 const stmt = this.driver.prepare(sql, params, err => {
291 if (err) {
292 reject(err);
293 } else {
294 resolve(new Statement(stmt, this.Promise));
295 }
296 });
297 });
298 }
299
300 /**
301 * Set a configuration option for the database.
302 */
303 configure(option, value) {
304 this.driver.configure(option, value);
305 }
306
307 /**
308 * Migrates database schema to the latest version
309 */
310 migrate({ force, table = 'migrations', migrationsPath = './migrations' } = {}) {
311 var _this = this;
312
313 return asyncToGenerator(function* () {
314 /* eslint-disable no-await-in-loop */
315 const location = path.resolve(migrationsPath);
316
317 // Get the list of migration files, for example:
318 // { id: 1, name: 'initial', filename: '001-initial.sql' }
319 // { id: 2, name: 'feature', fielname: '002-feature.sql' }
320 const migrations = yield new _this.Promise(function (resolve, reject) {
321 fs.readdir(location, function (err, files) {
322 if (err) {
323 reject(err);
324 } else {
325 resolve(files.map(function (x) {
326 return x.match(/^(\d+).(.*?)\.sql$/);
327 }).filter(function (x) {
328 return x !== null;
329 }).map(function (x) {
330 return { id: Number(x[1]), name: x[2], filename: x[0] };
331 }).sort(function (a, b) {
332 return Math.sign(a.id - b.id);
333 }));
334 }
335 });
336 });
337
338 if (!migrations.length) {
339 throw new Error(`No migration files found in '${location}'.`);
340 }
341
342 // Ge the list of migrations, for example:
343 // { id: 1, name: 'initial', filename: '001-initial.sql', up: ..., down: ... }
344 // { id: 2, name: 'feature', fielname: '002-feature.sql', up: ..., down: ... }
345 yield Promise.all(migrations.map(function (migration) {
346 return new _this.Promise(function (resolve, reject) {
347 const filename = path.join(location, migration.filename);
348 fs.readFile(filename, 'utf-8', function (err, data) {
349 if (err) {
350 reject(err);
351 } else {
352 const [up, down] = data.split(/^--\s+?down\b/mi);
353 if (!down) {
354 const message = `The ${migration.filename} file does not contain '-- Down' separator.`;
355 reject(new Error(message));
356 } else {
357 /* eslint-disable no-param-reassign */
358 migration.up = up.replace(/^-- .*?$/gm, '').trim(); // Remove comments
359 migration.down = down.trim(); // and trim whitespaces
360 /* eslint-enable no-param-reassign */
361 resolve();
362 }
363 }
364 });
365 });
366 }));
367
368 // Create a database table for migrations meta data if it doesn't exist
369 yield _this.run(`CREATE TABLE IF NOT EXISTS "${table}" (
370 id INTEGER PRIMARY KEY,
371 name TEXT NOT NULL,
372 up TEXT NOT NULL,
373 down TEXT NOT NULL
374)`);
375
376 // Get the list of already applied migrations
377 let dbMigrations = yield _this.all(`SELECT id, name, up, down FROM "${table}" ORDER BY id ASC`);
378
379 // Undo migrations that exist only in the database but not in files,
380 // also undo the last migration if the `force` option was set to `last`.
381 const lastMigration = migrations[migrations.length - 1];
382 for (const migration of dbMigrations.slice().sort(function (a, b) {
383 return Math.sign(b.id - a.id);
384 })) {
385 if (!migrations.some(function (x) {
386 return x.id === migration.id;
387 }) || force === 'last' && migration.id === lastMigration.id) {
388 yield _this.run('BEGIN');
389 try {
390 yield _this.exec(migration.down);
391 yield _this.run(`DELETE FROM "${table}" WHERE id = ?`, migration.id);
392 yield _this.run('COMMIT');
393 dbMigrations = dbMigrations.filter(function (x) {
394 return x.id !== migration.id;
395 });
396 } catch (err) {
397 yield _this.run('ROLLBACK');
398 throw err;
399 }
400 } else {
401 break;
402 }
403 }
404
405 // Apply pending migrations
406 const lastMigrationId = dbMigrations.length ? dbMigrations[dbMigrations.length - 1].id : 0;
407 for (const migration of migrations) {
408 if (migration.id > lastMigrationId) {
409 yield _this.run('BEGIN');
410 try {
411 yield _this.exec(migration.up);
412 yield _this.run(`INSERT INTO "${table}" (id, name, up, down) VALUES (?, ?, ?, ?)`, migration.id, migration.name, migration.up, migration.down);
413 yield _this.run('COMMIT');
414 } catch (err) {
415 yield _this.run('ROLLBACK');
416 throw err;
417 }
418 }
419 }
420
421 /* eslint-enable no-await-in-loop */
422 return _this;
423 })();
424 }
425}
426
427/**
428 * SQLite client library for Node.js applications
429 *
430 * Copyright © 2016 Kriasoft, LLC. All rights reserved.
431 *
432 * This source code is licensed under the MIT license found in the
433 * LICENSE.txt file in the root directory of this source tree.
434 */
435
436const promise = global.Promise;
437const db = new Database(null, { Promise: promise });
438
439/**
440 * Opens SQLite database.
441 *
442 * @returns Promise<Database> A promise that resolves to an instance of SQLite database client.
443 */
444db.open = (filename, {
445 mode = null,
446 verbose = false,
447 Promise = promise,
448 cached = false } = {}) => {
449 let driver;
450 let DBDriver = sqlite3.Database;
451
452 if (cached) {
453 DBDriver = sqlite3.cached.Database;
454 }
455
456 if (verbose) {
457 sqlite3.verbose();
458 }
459
460 return new Promise((resolve, reject) => {
461 if (mode !== null) {
462 driver = new DBDriver(filename, mode, err => {
463 if (err) {
464 reject(err);
465 } else {
466 resolve();
467 }
468 });
469 } else {
470 driver = new DBDriver(filename, err => {
471 if (err) {
472 reject(err);
473 } else {
474 resolve();
475 }
476 });
477 }
478 }).then(() => {
479 db.driver = driver;
480 db.Promise = Promise;
481 return new Database(driver, { Promise });
482 });
483};
484
485module.exports = db;
486//# sourceMappingURL=main.js.map