UNPKG

10.8 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', { value: true });
4
5const r = [8, 9, "a", "b"];
6
7function clone (arg) {
8 return JSON.parse(JSON.stringify(arg, null, 0));
9}
10
11function each (arr, fn) {
12 for (const item of arr.entries()) {
13 fn(item[1], item[0]);
14 }
15
16 return arr;
17}
18
19function indexKeys (arg = "", delimiter = "|", data = {}) {
20 return arg.split(delimiter).reduce((a, li, lidx) => {
21 const result = [];
22
23 (Array.isArray(data[li]) ? data[li] : [data[li]]).forEach(lli => lidx === 0 ? result.push(lli) : a.forEach(x => result.push(`${x}${delimiter}${lli}`)));
24
25 return result;
26 }, []);
27}
28
29function delIndex (index, indexes, delimiter, key, data) {
30 index.forEach(i => {
31 const idx = indexes.get(i);
32
33 each(i.includes(delimiter) ? indexKeys(i, delimiter, data) : Array.isArray(data[i]) ? data[i] : [data[i]], value => {
34 if (idx.has(value)) {
35 const o = idx.get(value);
36
37 o.delete(key);
38
39 if (o.size === 0) {
40 idx.delete(value);
41 }
42 }
43 });
44 });
45}
46
47function merge (a, b) {
48 if (a instanceof Object && b instanceof Object) {
49 each(Object.keys(b), i => {
50 if (a[i] instanceof Object && b[i] instanceof Object) {
51 a[i] = merge(a[i], b[i]);
52 } else if (Array.isArray(a[i]) && Array.isArray(b[i])) {
53 a[i] = a[i].concat(b[i]);
54 } else {
55 a[i] = b[i];
56 }
57 });
58 } else if (Array.isArray(a) && Array.isArray(b)) {
59 a = a.concat(b);
60 } else {
61 a = b;
62 }
63
64 return a;
65}
66
67function s () {
68 return ((Math.random() + 1) * 0x10000 | 0).toString(16).substring(1);
69}
70
71function setIndex (index, indexes, delimiter, key, data, indice) {
72 each(indice === null ? index : [indice], i => {
73 const lindex = indexes.get(i);
74
75 if (i.includes(delimiter)) {
76 each(indexKeys(i, delimiter, data), c => {
77 if (lindex.has(c) === false) {
78 lindex.set(c, new Set());
79 }
80
81 lindex.get(c).add(key);
82 });
83 } else {
84 each(Array.isArray(data[i]) ? data[i] : [data[i]], d => {
85 if (lindex.has(d) === false) {
86 lindex.set(d, new Set());
87 }
88
89 lindex.get(d).add(key);
90 });
91 }
92 });
93}
94
95function uuid () {
96 return s() + s() + "-" + s() + "-4" + s().substr(0, 3) + "-" + r[Math.floor(Math.random() * 4)] + s().substr(0, 3) + "-" + s() + s() + s();
97}
98
99class Haro {
100 constructor ({delimiter = "|", id = uuid(), index = [], key = "", versioning = false} = {}) {
101 this.data = new Map();
102 this.delimiter = delimiter;
103 this.id = id;
104 this.index = index;
105 this.indexes = new Map();
106 this.key = key;
107 this.size = 0;
108 this.versions = new Map();
109 this.versioning = versioning;
110
111 Object.defineProperty(this, "registry", {
112 enumerable: true,
113 get: () => Array.from(this.data.keys())
114 });
115
116 return this.reindex();
117 }
118
119 async batch (args, type = "set", lazyLoad = false) {
120 let result;
121
122 try {
123 const fn = type === "del" ? i => this.del(i, true, lazyLoad) : i => this.set(null, i, true, true, lazyLoad);
124
125 result = await Promise.all(this.beforeBatch(args, type).map(fn));
126 result = this.onbatch(result, type);
127 } catch (e) {
128 this.onerror("batch", e);
129 throw e;
130 }
131
132 return result;
133 }
134
135 beforeBatch (arg) {
136 return arg;
137 }
138
139 beforeClear () {
140 }
141
142 beforeDelete () {
143 }
144
145 beforeSet () {
146 }
147
148 clear () {
149 this.beforeClear();
150 this.size = 0;
151 this.data.clear();
152 this.indexes.clear();
153 this.versions.clear();
154 this.reindex().onclear();
155
156 return this;
157 }
158
159 del (key, batch = false, lazyLoad = false, retry = false) {
160 if (this.has(key) === false) {
161 throw new Error("Record not found");
162 }
163
164 const og = this.get(key, true);
165
166 this.beforeDelete(key, batch, lazyLoad, retry);
167 delIndex(this.index, this.indexes, this.delimiter, key, og);
168 this.data.delete(key);
169 --this.size;
170 this.ondelete(key, batch, retry, lazyLoad);
171
172 if (this.versioning) {
173 this.versions.delete(key);
174 }
175 }
176
177 dump (type = "records") {
178 let result;
179
180 if (type === "records") {
181 result = Array.from(this.entries());
182 } else {
183 result = Array.from(this.indexes).map(i => {
184 i[1] = Array.from(i[1]).map(ii => {
185 ii[1] = Array.from(ii[1]);
186
187 return ii;
188 });
189
190 return i;
191 });
192 }
193
194 return result;
195 }
196
197 entries () {
198 return this.data.entries();
199 }
200
201 find (where = {}, raw = false) {
202 const key = Object.keys(where).sort((a, b) => a.localeCompare(b)).join(this.delimiter),
203 index = this.indexes.get(key) || new Map();
204 let result = [];
205
206 if (index.size > 0) {
207 const keys = indexKeys(key, this.delimiter, where);
208
209 result = Array.from(keys.reduce((a, v) => {
210 if (index.has(v)) {
211 index.get(v).forEach(k => a.add(k));
212 }
213
214 return a;
215 }, new Set())).map(i => this.get(i, raw));
216 }
217
218 return raw ? result : this.list(...result);
219 }
220
221 filter (fn = () => void 0, raw = false) {
222 const x = raw ? (k, v) => v : (k, v) => Object.freeze([k, Object.freeze(v)]),
223 result = this.reduce((a, v, k, ctx) => {
224 if (fn.call(ctx, v)) {
225 a.push(x(k, v));
226 }
227
228 return a;
229 }, []);
230
231 return raw ? result : Object.freeze(result);
232 }
233
234 forEach (fn, ctx) {
235 this.data.forEach((value, key) => fn(clone(value), clone(key)), ctx || this.data);
236
237 return this;
238 }
239
240 get (key, raw = false) {
241 const result = clone(this.data.get(key) || null);
242
243 return raw ? result : this.list(key, result);
244 }
245
246 has (key) {
247 return this.data.has(key);
248 }
249
250 keys () {
251 return this.data.keys();
252 }
253
254 limit (offset = 0, max = 0, raw = false) {
255 const result = this.registry.slice(offset, offset + max).map(i => this.get(i, raw));
256
257 return raw ? result : this.list(...result);
258 }
259
260 list (...args) {
261 return Object.freeze(args.map(i => Object.freeze(i)));
262 }
263
264 map (fn, raw = false) {
265 const result = [];
266
267 this.forEach((value, key) => result.push(fn(value, key)));
268
269 return raw ? result : this.list(...result);
270 }
271
272 onbatch (arg) {
273 return arg;
274 }
275
276 onclear () {
277 }
278
279 ondelete () {
280 }
281
282 onerror () {
283 }
284
285 onset () {
286 }
287
288 async override (data, type = "records") {
289 const result = true;
290
291 if (type === "indexes") {
292 this.indexes = new Map(data.map(i => [i[0], new Map(i[1].map(ii => [ii[0], new Set(ii[1])]))]));
293 } else if (type === "records") {
294 this.indexes.clear();
295 this.data = new Map(data);
296 this.size = this.data.size;
297 } else {
298 throw new Error("Invalid type");
299 }
300
301 return result;
302 }
303
304 reduce (fn, accumulator, raw = false) {
305 let a = accumulator || this.data.keys().next().value;
306
307 this.forEach((v, k) => {
308 a = fn(a, v, k, this, raw);
309 }, this);
310
311 return a;
312 }
313
314 reindex (index) {
315 const indices = index ? [index] : this.index;
316
317 if (index && this.index.includes(index) === false) {
318 this.index.push(index);
319 }
320
321 each(indices, i => this.indexes.set(i, new Map()));
322 this.forEach((data, key) => each(indices, i => setIndex(this.index, this.indexes, this.delimiter, key, data, i)));
323
324 return this;
325 }
326
327 search (value, index, raw = false) {
328 const result = new Map(),
329 fn = typeof value === "function",
330 rgex = value && typeof value.test === "function";
331
332 if (value) {
333 each(index ? Array.isArray(index) ? index : [index] : this.index, i => {
334 let idx = this.indexes.get(i);
335
336 if (idx) {
337 idx.forEach((lset, lkey) => {
338 switch (true) {
339 case fn && value(lkey, i):
340 case rgex && value.test(Array.isArray(lkey) ? lkey.join(", ") : lkey):
341 case lkey === value:
342 lset.forEach(key => {
343 if (result.has(key) === false && this.has(key)) {
344 result.set(key, this.get(key, raw));
345 }
346 });
347 break;
348 }
349 });
350 }
351 });
352 }
353
354 return raw ? Array.from(result.values()) : this.list(...Array.from(result.values()));
355 }
356
357 async set (key, data, batch = false, override = false, lazyLoad = false, retry = false) {
358 let x = clone(data),
359 og, result;
360
361 if (key === void 0 || key === null) {
362 key = this.key && x[this.key] !== void 0 ? x[this.key] : uuid();
363 }
364
365 this.beforeSet(key, data, batch, override, lazyLoad, retry);
366
367 if (this.has(key) === false) {
368 ++this.size;
369
370 if (this.versioning) {
371 this.versions.set(key, new Set());
372 }
373 } else {
374 og = this.get(key, true);
375 delIndex(this.index, this.indexes, this.delimiter, key, og);
376
377 if (this.versioning) {
378 this.versions.get(key).add(Object.freeze(clone(og)));
379 }
380
381 if (override === false) {
382 x = merge(clone(og), x);
383 }
384 }
385
386 this.data.set(key, x);
387 setIndex(this.index, this.indexes, this.delimiter, key, x, null);
388 result = this.get(key);
389 this.onset(result, batch, retry, lazyLoad);
390
391 return result;
392 }
393
394 sort (fn, frozen = true) {
395 return frozen ? Object.freeze(this.limit(0, this.size, true).sort(fn).map(i => Object.freeze(i))) : this.limit(0, this.size, true).sort(fn);
396 }
397
398 sortBy (index, raw = false) {
399 const result = [],
400 keys = [];
401
402 let lindex;
403
404 if (this.indexes.has(index) === false) {
405 this.reindex(index);
406 }
407
408 lindex = this.indexes.get(index);
409 lindex.forEach((idx, key) => keys.push(key));
410 each(keys.sort(), i => lindex.get(i).forEach(key => result.push(this.get(key, raw))));
411
412 return raw ? result : this.list(...result);
413 }
414
415 toArray (frozen = true) {
416 const result = Array.from(this.data.values());
417
418 if (frozen) {
419 each(result, i => Object.freeze(i));
420 Object.freeze(result);
421 }
422
423 return result;
424 }
425
426 values () {
427 return this.data.values();
428 }
429
430 where (predicate, raw = false, op = "||") {
431 const keys = this.index.filter(i => i in predicate);
432
433 return keys.length > 0 ? this.filter(new Function("a", `return (${keys.map(i => {
434 let result;
435
436 if (Array.isArray(predicate[i])) {
437 result = `Array.isArray(a['${i}']) ? ${predicate[i].map(arg => `a['${i}'].includes(${typeof arg === "string" ? `'${arg}'` : arg})`).join(` ${op} `)} : (${predicate[i].map(arg => `a['${i}'] === ${typeof arg === "string" ? `'${arg}'` : arg}`).join(` ${op} `)})`;
438 } else if (predicate[i] instanceof RegExp) {
439 result = `Array.isArray(a['${i}']) ? a['${i}'].filter(i => ${predicate[i]}.test(a['${i}'])).length > 0 : ${predicate[i]}.test(a['${i}'])`;
440 } else {
441 const arg = typeof predicate[i] === "string" ? `'${predicate[i]}'` : predicate[i];
442
443 result = `Array.isArray(a['${i}']) ? a['${i}'].includes(${arg}) : a['${i}'] === ${arg}`;
444 }
445
446 return result;
447 }).join(") && (")});`), raw) : [];
448 }
449}
450
451function haro (data = null, config = {}) {
452 const obj = new Haro(config);
453
454 if (Array.isArray(data)) {
455 obj.batch(data, "set");
456 }
457
458 return obj;
459}
460
461exports.haro = haro;