UNPKG

8.26 kBJavaScriptView Raw
1
2/*
3 Loki (node) fs structured Adapter (need to require this script to instance and use it).
4
5 This adapter will save database container and each collection to separate files and
6 save collection only if it is dirty. It is also designed to use a destructured serialization
7 method intended to lower the memory overhead of json serialization.
8
9 This adapter utilizes ES6 generator/iterator functionality to stream output and
10 uses node linereader module to stream input. This should lower memory pressure
11 in addition to individual object serializations rather than loki's default deep object
12 serialization.
13*/
14
15(function (root, factory) {
16 if (typeof define === 'function' && define.amd) {
17 // AMD
18 define([], factory);
19 } else if (typeof exports === 'object') {
20 // Node, CommonJS-like
21 module.exports = factory();
22 } else {
23 // Browser globals (root is window)
24 root.LokiFsStructuredAdapter = factory();
25 }
26}(this, function () {
27 return (function() {
28
29 const fs = require('fs');
30 const readline = require('readline');
31 const stream = require('stream');
32
33 /**
34 * Loki structured (node) filesystem adapter class.
35 * This class fulfills the loki 'reference' abstract adapter interface which can be applied to other storage methods.
36 *
37 * @constructor LokiFsStructuredAdapter
38 *
39 */
40 function LokiFsStructuredAdapter()
41 {
42 this.mode = "reference";
43 this.dbref = null;
44 this.dirtyPartitions = [];
45 }
46
47 /**
48 * Generator for constructing lines for file streaming output of db container or collection.
49 *
50 * @param {object=} options - output format options for use externally to loki
51 * @param {int=} options.partition - can be used to only output an individual collection or db (-1)
52 *
53 * @returns {string|array} A custom, restructured aggregation of independent serializations.
54 * @memberof LokiFsStructuredAdapter
55 */
56 LokiFsStructuredAdapter.prototype.generateDestructured = function*(options) {
57 var idx, sidx;
58 var dbcopy;
59
60 options = options || {};
61
62 if (!options.hasOwnProperty("partition")) {
63 options.partition = -1;
64 }
65
66 // if partition is -1 we will return database container with no data
67 if (options.partition === -1) {
68 // instantiate lightweight clone and remove its collection data
69 dbcopy = this.dbref.copy();
70
71 for(idx=0; idx < dbcopy.collections.length; idx++) {
72 dbcopy.collections[idx].data = [];
73 }
74
75 yield dbcopy.serialize({
76 serializationMethod: "normal"
77 });
78
79 return;
80 }
81
82 // 'partitioned' along with 'partition' of 0 or greater is a request for single collection serialization
83 if (options.partition >= 0) {
84 var doccount,
85 docidx;
86
87 // dbref collections have all data so work against that
88 doccount = this.dbref.collections[options.partition].data.length;
89
90 for(docidx=0; docidx<doccount; docidx++) {
91 yield JSON.stringify(this.dbref.collections[options.partition].data[docidx]);
92 }
93 }
94 };
95
96 /**
97 * Loki persistence adapter interface function which outputs un-prototype db object reference to load from.
98 *
99 * @param {string} dbname - the name of the database to retrieve.
100 * @param {function} callback - callback should accept string param containing db object reference.
101 * @memberof LokiFsStructuredAdapter
102 */
103 LokiFsStructuredAdapter.prototype.loadDatabase = function(dbname, callback)
104 {
105 var instream = fs.createReadStream(dbname);
106 var outstream = new stream();
107 var rl = readline.createInterface(instream, outstream);
108 var self=this;
109
110 this.dbref = null;
111
112 // first, load db container component
113 rl.on('line', function(line) {
114 // it should single JSON object (a one line file)
115 if (self.dbref === null && line !== "") {
116 self.dbref = JSON.parse(line);
117 }
118 });
119
120 // when that is done, examine its collection array to sequence loading each
121 rl.on('close', function() {
122 if (self.dbref.collections.length > 0) {
123 self.loadNextCollection(dbname, 0, function() {
124 callback(self.dbref);
125 });
126 }
127 });
128 };
129
130 /**
131 * Recursive function to chain loading of each collection one at a time.
132 * If at some point i can determine how to make async driven generator, this may be converted to generator.
133 *
134 * @param {string} dbname - the name to give the serialized database within the catalog.
135 * @param {int} collectionIndex - the ordinal position of the collection to load.
136 * @param {function} callback - callback to pass to next invocation or to call when done
137 * @memberof LokiFsStructuredAdapter
138 */
139 LokiFsStructuredAdapter.prototype.loadNextCollection = function(dbname, collectionIndex, callback) {
140 var instream = fs.createReadStream(dbname + "." + collectionIndex);
141 var outstream = new stream();
142 var rl = readline.createInterface(instream, outstream);
143 var self=this,
144 obj;
145
146 rl.on('line', function (line) {
147 if (line !== "") {
148 obj = JSON.parse(line);
149 self.dbref.collections[collectionIndex].data.push(obj);
150 }
151 });
152
153 rl.on('close', function (line) {
154 instream = null;
155 outstream = null;
156 rl = null;
157 obj = null;
158
159 // if there are more collections, load the next one
160 if (++collectionIndex < self.dbref.collections.length) {
161 self.loadNextCollection(dbname, collectionIndex, callback);
162 }
163 // otherwise we are done, callback to loadDatabase so it can return the new db object representation.
164 else {
165 callback();
166 }
167 });
168 };
169
170 /**
171 * Generator for yielding sequence of dirty partition indices to iterate.
172 *
173 * @memberof LokiFsStructuredAdapter
174 */
175 LokiFsStructuredAdapter.prototype.getPartition = function*() {
176 var idx,
177 clen = this.dbref.collections.length;
178
179 // since database container (partition -1) doesn't have dirty flag at db level, always save
180 yield -1;
181
182 // yield list of dirty partitions for iterateration
183 for(idx=0; idx<clen; idx++) {
184 if (this.dbref.collections[idx].dirty) {
185 yield idx;
186 }
187 }
188 };
189
190 /**
191 * Loki reference adapter interface function. Saves structured json via loki database object reference.
192 *
193 * @param {string} dbname - the name to give the serialized database within the catalog.
194 * @param {object} dbref - the loki database object reference to save.
195 * @param {function} callback - callback passed obj.success with true or false
196 * @memberof LokiFsStructuredAdapter
197 */
198 LokiFsStructuredAdapter.prototype.exportDatabase = function(dbname, dbref, callback)
199 {
200 var idx;
201
202 this.dbref = dbref;
203
204 // create (dirty) partition generator/iterator
205 var pi = this.getPartition();
206
207 this.saveNextPartition(dbname, pi, function() {
208 callback(null);
209 });
210
211 };
212
213 /**
214 * Utility method for queueing one save at a time
215 */
216 LokiFsStructuredAdapter.prototype.saveNextPartition = function(dbname, pi, callback) {
217 var li;
218 var filename;
219 var self = this;
220 var pinext = pi.next();
221
222 if (pinext.done) {
223 callback();
224 return;
225 }
226
227 // db container (partition -1) uses just dbname for filename,
228 // otherwise append collection array index to filename
229 filename = dbname + ((pinext.value === -1)?"":("." + pinext.value));
230
231 var wstream = fs.createWriteStream(filename);
232 //wstream.on('finish', function() {
233 wstream.on('close', function() {
234 self.saveNextPartition(dbname, pi, callback);
235 });
236
237 li = this.generateDestructured({ partition: pinext.value });
238
239 // iterate each of the lines generated by generateDestructured()
240 for(var outline of li) {
241 wstream.write(outline + "\n");
242 }
243
244 wstream.end();
245 };
246
247 return LokiFsStructuredAdapter;
248
249 }());
250}));