node-red-contrib-db-storage 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,161 @@
1
+ const DatabaseAdapter = require('./DatabaseAdapter');
2
+
3
+ /**
4
+ * SQLite adapter for Node-RED storage
5
+ *
6
+ * Configuration:
7
+ * @param {Object} config - Standardized adapter configuration
8
+ * @param {string} config.url - SQLite database file path (e.g., '/path/to/database.db' or ':memory:')
9
+ * @param {string} config.database - Database name (used for identification, not for SQLite file)
10
+ */
11
+ class SQLiteAdapter extends DatabaseAdapter {
12
+ constructor(config) {
13
+ super(config);
14
+ // url and databaseName are set by parent class
15
+ this.db = null;
16
+ this._sqlite = null;
17
+ }
18
+
19
+ async connect() {
20
+ // Dynamically require better-sqlite3 to make it an optional dependency
21
+ try {
22
+ this._sqlite = require('better-sqlite3');
23
+ } catch (err) {
24
+ throw new Error('SQLite adapter requires the "better-sqlite3" package. Install it with: npm install better-sqlite3');
25
+ }
26
+
27
+ // Parse the URL to get the file path
28
+ let dbPath = this.url;
29
+ if (dbPath.startsWith('sqlite://')) {
30
+ dbPath = dbPath.replace('sqlite://', '');
31
+ } else if (dbPath.startsWith('file://')) {
32
+ dbPath = dbPath.replace('file://', '');
33
+ }
34
+
35
+ this.db = new this._sqlite(dbPath);
36
+
37
+ // Enable WAL mode for better concurrent performance
38
+ this.db.pragma('journal_mode = WAL');
39
+
40
+ // Create tables if they don't exist
41
+ await this._initializeTables();
42
+ }
43
+
44
+ async close() {
45
+ if (this.db) {
46
+ this.db.close();
47
+ this.db = null;
48
+ }
49
+ }
50
+
51
+ async _initializeTables() {
52
+ // We'll create tables dynamically as needed
53
+ // This is a helper to ensure the schema exists
54
+ }
55
+
56
+ async _ensureTable(tableName) {
57
+ const createTableQuery = `
58
+ CREATE TABLE IF NOT EXISTS "${tableName}" (
59
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
60
+ path TEXT UNIQUE,
61
+ data TEXT NOT NULL,
62
+ meta TEXT,
63
+ body TEXT,
64
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
65
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
66
+ )
67
+ `;
68
+ this.db.exec(createTableQuery);
69
+
70
+ // Create index on path for faster lookups
71
+ const createIndexQuery = `
72
+ CREATE INDEX IF NOT EXISTS "${tableName}_path_idx" ON "${tableName}" (path)
73
+ `;
74
+ this.db.exec(createIndexQuery);
75
+ }
76
+
77
+ async findAll(collectionName) {
78
+ this.validateCollectionName(collectionName);
79
+ await this._ensureTable(collectionName);
80
+
81
+ const query = `SELECT data FROM "${collectionName}"`;
82
+ const rows = this.db.prepare(query).all();
83
+
84
+ if (rows.length === 0) {
85
+ return [];
86
+ }
87
+
88
+ return rows.map(row => JSON.parse(row.data));
89
+ }
90
+
91
+ async saveAll(collectionName, objects) {
92
+ this.validateCollectionName(collectionName);
93
+ this.validateObjectArray(objects);
94
+ await this._ensureTable(collectionName);
95
+
96
+ // Use a transaction for atomicity
97
+ const transaction = this.db.transaction((objs) => {
98
+ // Delete all existing data
99
+ this.db.prepare(`DELETE FROM "${collectionName}"`).run();
100
+
101
+ // Insert new data
102
+ if (objs.length > 0) {
103
+ const insert = this.db.prepare(
104
+ `INSERT INTO "${collectionName}" (data) VALUES (?)`
105
+ );
106
+ for (const obj of objs) {
107
+ insert.run(JSON.stringify(obj));
108
+ }
109
+ }
110
+ });
111
+
112
+ transaction(objects);
113
+ }
114
+
115
+ async findOneByPath(collectionName, path) {
116
+ this.validateCollectionName(collectionName);
117
+ this.validatePath(path);
118
+ await this._ensureTable(collectionName);
119
+
120
+ const query = `SELECT body FROM "${collectionName}" WHERE path = ?`;
121
+ const row = this.db.prepare(query).get(path);
122
+
123
+ if (!row) {
124
+ return {};
125
+ }
126
+
127
+ const body = row.body;
128
+ if (body == null) {
129
+ return {};
130
+ }
131
+
132
+ return JSON.parse(body);
133
+ }
134
+
135
+ async saveOrUpdateByPath(collectionName, path, meta, body) {
136
+ this.validateCollectionName(collectionName);
137
+ this.validatePath(path);
138
+ await this._ensureTable(collectionName);
139
+
140
+ const data = { path, meta, body };
141
+
142
+ const query = `
143
+ INSERT INTO "${collectionName}" (path, meta, body, data, updated_at)
144
+ VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)
145
+ ON CONFLICT(path) DO UPDATE SET
146
+ meta = excluded.meta,
147
+ body = excluded.body,
148
+ data = excluded.data,
149
+ updated_at = CURRENT_TIMESTAMP
150
+ `;
151
+
152
+ this.db.prepare(query).run(
153
+ path,
154
+ JSON.stringify(meta),
155
+ JSON.stringify(body),
156
+ JSON.stringify(data)
157
+ );
158
+ }
159
+ }
160
+
161
+ module.exports = SQLiteAdapter;
@@ -0,0 +1,15 @@
1
+ const DatabaseAdapter = require('./DatabaseAdapter');
2
+ const MongoAdapter = require('./MongoAdapter');
3
+ const PostgresAdapter = require('./PostgresAdapter');
4
+ const MySQLAdapter = require('./MySQLAdapter');
5
+ const SQLiteAdapter = require('./SQLiteAdapter');
6
+ const AdapterFactory = require('./AdapterFactory');
7
+
8
+ module.exports = {
9
+ DatabaseAdapter,
10
+ MongoAdapter,
11
+ PostgresAdapter,
12
+ MySQLAdapter,
13
+ SQLiteAdapter,
14
+ AdapterFactory
15
+ };
package/constants.js ADDED
@@ -0,0 +1,8 @@
1
+ module.exports = {
2
+ DefaultCollectionNames: {
3
+ flows: "nodered-flows",
4
+ credentials: "nodered-credentials",
5
+ settings: "nodered-settings",
6
+ sessions: "nodered-sessions"
7
+ }
8
+ }
@@ -0,0 +1,115 @@
1
+ //npm install http;
2
+ var http = require('http');
3
+ //npm install express
4
+ var express = require("express");
5
+ //npm install node-red
6
+ var RED = require("node-red");
7
+ //npm install util
8
+ var util = require("util");
9
+
10
+ // Create an Express app
11
+ var app = express();
12
+
13
+ // Add a simple route for static content served from 'public'
14
+ app.use("/",express.static("public"));
15
+
16
+ // Create a server
17
+ var server = http.createServer(app);
18
+
19
+ // Create the settings object - see default settings.js file for other options
20
+ var settings = {
21
+ httpAdminRoot:"/red",
22
+ httpNodeRoot: "/api",
23
+ //on windows
24
+ userDir:"c:\\node-red-files",
25
+ //on linux
26
+ // userDir:"/home/user",
27
+ uiPort : 1880,
28
+ uiHost: "localhost",
29
+ storageModule : require("node-red-mongo-storage-plugin"),
30
+ storageModuleOptions: {
31
+ mongoUrl: 'mongodb://localhost:27017',
32
+ database: 'local',
33
+ collectionNames:{
34
+ flows: "nodered-flows",
35
+ credentials: "nodered-credentials",
36
+ settings: "nodered-settings",
37
+ sessions: "nodered-sessions"
38
+ }
39
+ },
40
+ functionGlobalContext: { } // enables global context
41
+ };
42
+
43
+ // Initialise the runtime with a server and settings
44
+ RED.init(server,settings);
45
+
46
+ // Serve the editor UI from /red
47
+ app.use(settings.httpAdminRoot,RED.httpAdmin);
48
+
49
+ // Serve the http nodes UI from /api
50
+ app.use(settings.httpNodeRoot,RED.httpNode);
51
+
52
+
53
+
54
+ function getListenPath() {
55
+ var port = settings.serverPort;
56
+ if (port === undefined){
57
+ port = settings.uiPort;
58
+ }
59
+
60
+ var listenPath = 'http'+(settings.https?'s':'')+'://'+
61
+ (settings.uiHost == '::'?'localhost':(settings.uiHost == '0.0.0.0'?'127.0.0.1':settings.uiHost))+
62
+ ':'+port;
63
+ if (settings.httpAdminRoot !== false) {
64
+ listenPath += settings.httpAdminRoot;
65
+ } else if (settings.httpStatic) {
66
+ listenPath += "/";
67
+ }
68
+ return listenPath;
69
+ }
70
+
71
+ RED.start().then(function() {
72
+ if (settings.httpAdminRoot !== false || settings.httpNodeRoot !== false || settings.httpStatic) {
73
+ server.on('error', function(err) {
74
+ if (err.errno === "EADDRINUSE") {
75
+ RED.log.error(RED.log._("server.unable-to-listen", {listenpath:getListenPath()}));
76
+ RED.log.error(RED.log._("server.port-in-use"));
77
+ } else {
78
+ RED.log.error(RED.log._("server.uncaught-exception"));
79
+ if (err.stack) {
80
+ RED.log.error(err.stack);
81
+ } else {
82
+ RED.log.error(err);
83
+ }
84
+ }
85
+ process.exit(1);
86
+ });
87
+ server.listen(settings.uiPort,settings.uiHost,function() {
88
+ if (settings.httpAdminRoot === false) {
89
+ RED.log.info(RED.log._("server.admin-ui-disabled"));
90
+ }
91
+ settings.serverPort = server.address().port;
92
+ process.title = 'node-red';
93
+ RED.log.info(RED.log._("server.now-running", {listenpath:getListenPath()}));
94
+ });
95
+ } else {
96
+ RED.log.info(RED.log._("server.headless-mode"));
97
+ }
98
+ }).otherwise(function(err) {
99
+ RED.log.error(RED.log._("server.failed-to-start"));
100
+ if (err.stack) {
101
+ RED.log.error(err.stack);
102
+ } else {
103
+ RED.log.error(err);
104
+ }
105
+ });
106
+
107
+ process.on('uncaughtException',function(err) {
108
+ util.log('[red] Uncaught Exception:');
109
+ if (err.stack) {
110
+ util.log(err.stack);
111
+ } else {
112
+ util.log(err);
113
+ }
114
+ process.exit(1);
115
+ });
package/index.js ADDED
@@ -0,0 +1,185 @@
1
+ const constants = require("./constants");
2
+ const { AdapterFactory } = require("./adapters");
3
+ const { UrlParser } = require("./utils");
4
+
5
+ /**
6
+ * Storage module for Node-RED with pluggable database backends.
7
+ * Encapsulates adapter and settings state within the module closure.
8
+ */
9
+ var storageModule = (function () {
10
+ // Private state encapsulated in closure
11
+ let _settings = null;
12
+ let _adapter = null;
13
+ let _collectionNames = null;
14
+
15
+ /**
16
+ * Parse configuration options and extract database connection details
17
+ * @param {Object} options - Storage module options
18
+ * @returns {Object} Parsed configuration with dbType, dbUrl, dbName
19
+ */
20
+ function parseConfiguration(options) {
21
+ let dbType, dbUrl, dbName;
22
+
23
+ if (options.type) {
24
+ // New configuration format with explicit type
25
+ dbType = options.type;
26
+ dbUrl = options.url;
27
+ dbName = options.database;
28
+ } else if (options.mongoUrl) {
29
+ // Legacy MongoDB configuration for backward compatibility
30
+ dbType = "mongodb";
31
+ dbUrl = options.mongoUrl;
32
+ dbName = options.database;
33
+ } else if (options.postgresUrl) {
34
+ // PostgreSQL configuration
35
+ dbType = "postgres";
36
+ dbUrl = options.postgresUrl;
37
+ dbName = options.database;
38
+ } else {
39
+ throw new Error(
40
+ "Database URL is required. Provide 'url' with 'type', or 'mongoUrl'/'postgresUrl'",
41
+ );
42
+ }
43
+
44
+ if (!dbUrl) {
45
+ throw new Error("Database URL is required");
46
+ }
47
+
48
+ // Extract database name from URL if not provided
49
+ if (!dbName) {
50
+ dbName = UrlParser.extractDatabaseName(dbType, dbUrl);
51
+ }
52
+
53
+ // Standardized interface requires database name for all adapters
54
+ if (!dbName) {
55
+ throw new Error(
56
+ "Database name is required. Provide 'database' in configuration or include it in the connection URL",
57
+ );
58
+ }
59
+
60
+ return { dbType, dbUrl, dbName };
61
+ }
62
+
63
+ /**
64
+ * Initialize collection names from configuration
65
+ * @param {Object} options - Storage module options
66
+ * @returns {Object} Collection names mapping
67
+ */
68
+ function initializeCollectionNames(options) {
69
+ const collectionNames = Object.assign({}, constants.DefaultCollectionNames);
70
+ if (options.collectionNames != null) {
71
+ for (let name of Object.keys(options.collectionNames)) {
72
+ collectionNames[name] = options.collectionNames[name];
73
+ }
74
+ }
75
+ return collectionNames;
76
+ }
77
+
78
+ return {
79
+ init: function (settings) {
80
+ _settings = settings;
81
+
82
+ // Validate required options
83
+ if (_settings.storageModuleOptions == null) {
84
+ throw new Error("storageModuleOptions is required");
85
+ }
86
+
87
+ const options = _settings.storageModuleOptions;
88
+
89
+ // Parse configuration
90
+ const { dbType, dbUrl, dbName } = parseConfiguration(options);
91
+
92
+ // Set up collection names
93
+ _collectionNames = initializeCollectionNames(options);
94
+
95
+ // Create adapter
96
+ _adapter = AdapterFactory.create(dbType, {
97
+ url: dbUrl,
98
+ database: dbName,
99
+ });
100
+
101
+ return _adapter.connect();
102
+ },
103
+
104
+ // Expose collection names for external access
105
+ get collectionNames() {
106
+ return _collectionNames;
107
+ },
108
+
109
+ getFlows: function () {
110
+ return _adapter.findAll(_collectionNames.flows);
111
+ },
112
+
113
+ saveFlows: function (flows) {
114
+ return _adapter.saveAll(_collectionNames.flows, flows);
115
+ },
116
+
117
+ getCredentials: function () {
118
+ return _adapter.findAll(_collectionNames.credentials).then((result) => {
119
+ // Convert array back to object format that Node-RED expects
120
+ if (Array.isArray(result) && result.length > 0) {
121
+ return result[0];
122
+ }
123
+ return {};
124
+ });
125
+ },
126
+
127
+ saveCredentials: function (credentials) {
128
+ // Node-RED passes credentials as an object, convert to array for storage
129
+ const credentialsArray =
130
+ credentials && Object.keys(credentials).length > 0 ? [credentials] : [];
131
+ return _adapter.saveAll(_collectionNames.credentials, credentialsArray);
132
+ },
133
+
134
+ getSettings: function () {
135
+ return _adapter.findAll(_collectionNames.settings).then((result) => {
136
+ // Convert array back to object format that Node-RED expects
137
+ if (Array.isArray(result) && result.length > 0) {
138
+ return result[0];
139
+ }
140
+ return {};
141
+ });
142
+ },
143
+
144
+ saveSettings: function (settings) {
145
+ // Node-RED passes settings as an object, convert to array for storage
146
+ const settingsArray =
147
+ settings && Object.keys(settings).length > 0 ? [settings] : [];
148
+ return _adapter.saveAll(_collectionNames.settings, settingsArray);
149
+ },
150
+
151
+ getSessions: function () {
152
+ return _adapter.findAll(_collectionNames.sessions).then((result) => {
153
+ // Convert array back to object format that Node-RED expects
154
+ if (Array.isArray(result) && result.length > 0) {
155
+ return result[0];
156
+ }
157
+ return {};
158
+ });
159
+ },
160
+
161
+ saveSessions: function (sessions) {
162
+ // Node-RED passes sessions as an object, convert to array for storage
163
+ const sessionsArray =
164
+ sessions && Object.keys(sessions).length > 0 ? [sessions] : [];
165
+ return _adapter.saveAll(_collectionNames.sessions, sessionsArray);
166
+ },
167
+
168
+ getLibraryEntry: function (type, path) {
169
+ return _adapter.findOneByPath(type, path);
170
+ },
171
+
172
+ saveLibraryEntry: function (type, path, meta, body) {
173
+ return _adapter.saveOrUpdateByPath(type, path, meta, body);
174
+ },
175
+
176
+ close: function () {
177
+ if (_adapter) {
178
+ return _adapter.close();
179
+ }
180
+ return Promise.resolve();
181
+ },
182
+ };
183
+ })();
184
+
185
+ module.exports = storageModule;
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "node-red-contrib-db-storage",
3
+ "version": "0.2.0",
4
+ "description": "Node-RED storage plugin with support for MongoDB, PostgreSQL, and other databases",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "jest --coverage --testPathIgnorePatterns=integration",
8
+ "test:unit": "jest --coverage --testPathIgnorePatterns=integration",
9
+ "test:integration": "jest --testMatch='**/*.integration.test.js' --runInBand",
10
+ "test:integration:mongo": "TEST_DATABASES=mongodb jest --testMatch='**/*.integration.test.js' --runInBand",
11
+ "test:integration:postgres": "TEST_DATABASES=postgres jest --testMatch='**/*.integration.test.js' --runInBand",
12
+ "test:all": "npm run test:unit && npm run test:integration",
13
+ "docker:up": "docker-compose up -d",
14
+ "docker:down": "docker-compose down",
15
+ "docker:logs": "docker-compose logs -f"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/srmurali002/node-red-contrib-db-storage.git"
20
+ },
21
+ "keywords": [
22
+ "node-red",
23
+ "storage",
24
+ "mongodb",
25
+ "postgres",
26
+ "postgresql",
27
+ "database"
28
+ ],
29
+ "author": "srmurali002",
30
+ "license": "MIT",
31
+ "bugs": {
32
+ "url": "https://github.com/srmurali002/node-red-contrib-db-storage/issues"
33
+ },
34
+ "homepage": "https://github.com/srmurali002/node-red-contrib-db-storage#readme",
35
+ "engines": {
36
+ "node": ">=10.0.0"
37
+ },
38
+ "dependencies": {
39
+ "better-sqlite3": "^12.4.1",
40
+ "mongodb": "^3.4.1",
41
+ "mysql2": "^3.15.3"
42
+ },
43
+ "optionalDependencies": {
44
+ "pg": "^8.11.0"
45
+ },
46
+ "devDependencies": {
47
+ "jest": "^29.7.0"
48
+ },
49
+ "peerDependencies": {
50
+ "pg": "^8.0.0"
51
+ },
52
+ "peerDependenciesMeta": {
53
+ "pg": {
54
+ "optional": true
55
+ }
56
+ },
57
+ "jest": {
58
+ "testEnvironment": "node",
59
+ "coverageDirectory": "coverage",
60
+ "collectCoverageFrom": [
61
+ "*.js",
62
+ "adapters/**/*.js",
63
+ "!jest.config.js"
64
+ ],
65
+ "testMatch": [
66
+ "**/*.test.js"
67
+ ]
68
+ }
69
+ }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Utility class for parsing database connection URLs
3
+ * Extracts database names from various connection URL formats
4
+ */
5
+ class UrlParser {
6
+ /**
7
+ * Extract database name from a connection URL based on database type
8
+ * @param {string} dbType - The database type (postgres, mysql, sqlite, etc.)
9
+ * @param {string} url - The connection URL
10
+ * @returns {string|null} The extracted database name or null if not found
11
+ */
12
+ static extractDatabaseName(dbType, url) {
13
+ if (!dbType || !url) {
14
+ return null;
15
+ }
16
+
17
+ const dbTypeLower = dbType.toLowerCase();
18
+
19
+ // PostgreSQL and MySQL: extract from connection URL
20
+ // Format: postgresql://user:password@host:port/database or mysql://user:password@host:port/database
21
+ if (this.isRelationalDatabase(dbTypeLower)) {
22
+ return this.extractFromConnectionUrl(url);
23
+ }
24
+
25
+ // SQLite: use the filename as database name
26
+ if (this.isSQLite(dbTypeLower)) {
27
+ return this.extractFromSQLitePath(url);
28
+ }
29
+
30
+ // MongoDB: extract from connection URL
31
+ if (this.isMongoDB(dbTypeLower)) {
32
+ return this.extractFromConnectionUrl(url);
33
+ }
34
+
35
+ return null;
36
+ }
37
+
38
+ /**
39
+ * Check if the database type is a relational database (PostgreSQL or MySQL)
40
+ * @param {string} dbType - The database type in lowercase
41
+ * @returns {boolean}
42
+ */
43
+ static isRelationalDatabase(dbType) {
44
+ return ['postgres', 'postgresql', 'pg', 'mysql', 'mariadb'].includes(dbType);
45
+ }
46
+
47
+ /**
48
+ * Check if the database type is SQLite
49
+ * @param {string} dbType - The database type in lowercase
50
+ * @returns {boolean}
51
+ */
52
+ static isSQLite(dbType) {
53
+ return ['sqlite', 'sqlite3'].includes(dbType);
54
+ }
55
+
56
+ /**
57
+ * Check if the database type is MongoDB
58
+ * @param {string} dbType - The database type in lowercase
59
+ * @returns {boolean}
60
+ */
61
+ static isMongoDB(dbType) {
62
+ return ['mongodb', 'mongo'].includes(dbType);
63
+ }
64
+
65
+ /**
66
+ * Extract database name from a standard connection URL
67
+ * @param {string} url - The connection URL
68
+ * @returns {string|null} The database name or null if not found
69
+ */
70
+ static extractFromConnectionUrl(url) {
71
+ // Parse URL to extract path after host:port
72
+ // Format: protocol://[user:pass@]host[:port]/database[?params]
73
+ // We need to find the database name which is in the path after the authority
74
+
75
+ // Remove protocol
76
+ let remaining = url.replace(/^[a-zA-Z]+:\/\//, '');
77
+
78
+ // Find the start of the path (first slash after host:port)
79
+ const pathStart = remaining.indexOf('/');
80
+ if (pathStart === -1) {
81
+ return null; // No path, no database
82
+ }
83
+
84
+ // Extract path
85
+ let path = remaining.substring(pathStart + 1);
86
+
87
+ // Remove query parameters
88
+ const queryStart = path.indexOf('?');
89
+ if (queryStart !== -1) {
90
+ path = path.substring(0, queryStart);
91
+ }
92
+
93
+ // The database name is what's left
94
+ return path || null;
95
+ }
96
+
97
+ /**
98
+ * Extract database name from SQLite file path
99
+ * @param {string} url - The SQLite file path or URL
100
+ * @returns {string} The database name (filename without extension)
101
+ */
102
+ static extractFromSQLitePath(url) {
103
+ let filePath = url;
104
+
105
+ // Remove URL prefixes
106
+ if (filePath.startsWith('sqlite://')) {
107
+ filePath = filePath.replace('sqlite://', '');
108
+ } else if (filePath.startsWith('file://')) {
109
+ filePath = filePath.replace('file://', '');
110
+ }
111
+
112
+ // Handle memory databases
113
+ if (filePath === ':memory:') {
114
+ return 'memory';
115
+ }
116
+
117
+ // Extract filename without extension as database name
118
+ const fileName = filePath.split('/').pop();
119
+ return fileName.replace(/\.[^/.]+$/, '') || 'sqlite';
120
+ }
121
+
122
+ /**
123
+ * Normalize SQLite file path by removing URL prefixes
124
+ * @param {string} url - The SQLite file path or URL
125
+ * @returns {string} The normalized file path
126
+ */
127
+ static normalizeSQLitePath(url) {
128
+ let filePath = url;
129
+
130
+ if (filePath.startsWith('sqlite://')) {
131
+ filePath = filePath.replace('sqlite://', '');
132
+ } else if (filePath.startsWith('file://')) {
133
+ filePath = filePath.replace('file://', '');
134
+ }
135
+
136
+ return filePath;
137
+ }
138
+ }
139
+
140
+ module.exports = UrlParser;
package/utils/index.js ADDED
@@ -0,0 +1,5 @@
1
+ const UrlParser = require('./UrlParser');
2
+
3
+ module.exports = {
4
+ UrlParser
5
+ };