Initial release
[websub-hub] / src / db / schema-version-helper.js
1 'use strict';
2
3 const fs = require('fs');
4 const path = require('path');
5
6 /**
7 * Utility functions for wrangling schema migrations.
8 */
9
10 /**
11 * @typedef {Object} SchemaVersionObject
12 * @property {Number} major
13 * @property {Number} minor
14 * @property {Number} patch
15 */
16
17
18 /**
19 * Split a dotted version string into parts.
20 * @param {String} v
21 * @returns {SchemaVersionObject}
22 */
23 function schemaVersionStringToObject(v) {
24 const [ major, minor, patch ] = v.split('.', 3).map((x) => parseInt(x, 10));
25 return { major, minor, patch };
26 }
27
28
29 /**
30 * Render a version object numerically.
31 * @param {SchemaVersionObject} v
32 * @returns {Number}
33 */
34 function schemaVersionObjectToNumber(v) {
35 const vScale = 1000;
36 return parseInt(v.major) * vScale * vScale + parseInt(v.minor) * vScale + parseInt(v.patch);
37 }
38
39
40 /**
41 * Convert dotted version string into number.
42 * @param {String} v
43 * @returns {Number}
44 */
45 function schemaVersionStringToNumber(v) {
46 return schemaVersionObjectToNumber(schemaVersionStringToObject(v));
47 }
48
49
50 /**
51 * Version string comparison, for sorting.
52 * @param {String} a
53 * @param {String} b
54 * @returns {Number}
55 */
56 function schemaVersionStringCmp(a, b) {
57 return schemaVersionStringToNumber(a) - schemaVersionStringToNumber(b);
58 }
59
60
61 /**
62 * Check if an entry in a directory is a directory containing a migration file.
63 * @param {String} schemaDir
64 * @param {String} name
65 * @returns {Boolean}
66 */
67 function isSchemaMigrationDirectory(schemaDir, name) {
68 // eslint-disable-next-line security/detect-non-literal-fs-filename
69 const nameStat = fs.statSync(path.join(schemaDir, name));
70 if (nameStat.isDirectory()) {
71 let applyStat;
72 try {
73 // eslint-disable-next-line security/detect-non-literal-fs-filename
74 applyStat = fs.statSync(path.join(schemaDir, name, 'apply.sql'));
75 return applyStat.isFile();
76 } catch (e) {
77 return false;
78 }
79 }
80 return false;
81 }
82
83
84 /**
85 * Return an array of schema migration directory names within engineDir.
86 * @param {String} engineDir
87 * @returns {String[]}
88 */
89 function allSchemaVersions(engineDir) {
90 const schemaDir = path.join(engineDir, 'sql', 'schema');
91 // eslint-disable-next-line security/detect-non-literal-fs-filename
92 const availableVersions = fs.readdirSync(schemaDir).filter((d) => isSchemaMigrationDirectory(schemaDir, d));
93 availableVersions.sort(schemaVersionStringCmp);
94 return availableVersions;
95 }
96
97
98 /**
99 * Return an array of schema migration directory names within engineDir,
100 * which are within supported range, and are greater than the current
101 * @param {String} engineDir
102 * @param {SchemaVersionObject} current
103 * @param {Object} supported
104 * @param {SchemaVersionObject} supported.min
105 * @param {SchemaVersionObject} supported.max
106 * @returns {String[]}
107 */
108 function unappliedSchemaVersions(engineDir, current, supported) {
109 const min = schemaVersionObjectToNumber(supported.min);
110 const max = schemaVersionObjectToNumber(supported.max);
111 const cur = schemaVersionObjectToNumber(current);
112 const available = allSchemaVersions(engineDir);
113 return available.filter((a) => {
114 a = schemaVersionStringToNumber(a);
115 return a >= min && a <= max && a > cur;
116 });
117 }
118
119
120 module.exports = {
121 schemaVersionStringToObject,
122 schemaVersionObjectToNumber,
123 schemaVersionStringToNumber,
124 schemaVersionStringCmp,
125 isSchemaMigrationDirectory,
126 allSchemaVersions,
127 unappliedSchemaVersions,
128 };