1 | 'use strict';
|
2 |
|
3 | Object.defineProperty(exports, '__esModule', { value: true });
|
4 |
|
5 | const r = [8, 9, "a", "b"];
|
6 |
|
7 | function clone (arg) {
|
8 | return JSON.parse(JSON.stringify(arg, null, 0));
|
9 | }
|
10 |
|
11 | function each (arr, fn) {
|
12 | for (const item of arr.entries()) {
|
13 | fn(item[1], item[0]);
|
14 | }
|
15 |
|
16 | return arr;
|
17 | }
|
18 |
|
19 | function 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 |
|
29 | function 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 |
|
47 | function 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 |
|
67 | function s () {
|
68 | return ((Math.random() + 1) * 0x10000 | 0).toString(16).substring(1);
|
69 | }
|
70 |
|
71 | function 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 |
|
95 | function 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 |
|
99 | class 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 |
|
451 | function 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 |
|
461 | exports.haro = haro;
|