event-storage 0.8.0 → 1.0.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,79 @@
1
+ import crypto from 'crypto';
2
+
3
+ /**
4
+ * @param {string} secret The secret to use for calculating further HMACs
5
+ * @returns {function(string)} A function that calculates the HMAC for a given string
6
+ */
7
+ const createHmac = secret => string => {
8
+ const hmac = crypto.createHmac('sha256', secret);
9
+ hmac.update(string);
10
+ return hmac.digest('hex');
11
+ };
12
+
13
+ /**
14
+ * @typedef {object|function(object):boolean} Matcher
15
+ */
16
+
17
+ /**
18
+ * @param {object} document The document to check against the matcher.
19
+ * @param {Matcher} matcher An object of properties and their values that need to match in the object or a function that checks if the document matches.
20
+ * @returns {boolean} True if the document matches the matcher or false otherwise.
21
+ */
22
+ function matches(document, matcher) {
23
+ if (typeof document === 'undefined') return false;
24
+ if (typeof matcher === 'undefined') return true;
25
+
26
+ if (typeof matcher === 'function') return matcher(document);
27
+
28
+ for (let prop of Object.getOwnPropertyNames(matcher)) {
29
+ if (typeof matcher[prop] === 'object') {
30
+ if (!matches(document[prop], matcher[prop])) {
31
+ return false;
32
+ }
33
+ } else if (typeof matcher[prop] !== 'undefined' && document[prop] !== matcher[prop]) {
34
+ return false;
35
+ }
36
+ }
37
+ return true;
38
+ }
39
+
40
+ /**
41
+ * @param {Matcher} matcher The matcher object or function that should be serialized.
42
+ * @param {function(string)} hmac A function that calculates a HMAC of the given string.
43
+ * @returns {{matcher: string|object, hmac?: string}}
44
+ */
45
+ function buildMetadataForMatcher(matcher, hmac) {
46
+ if (!matcher) {
47
+ return undefined;
48
+ }
49
+ if (typeof matcher === 'object') {
50
+ return { matcher };
51
+ }
52
+ const matcherString = matcher.toString();
53
+ return { matcher: matcherString, hmac: hmac(matcherString) };
54
+ }
55
+
56
+ /**
57
+ * @param {{matcher: string|object, hmac: string}} matcherMetadata The serialized matcher and its HMAC
58
+ * @param {function(string)} hmac A function that calculates a HMAC of the given string.
59
+ * @returns {Matcher} The matcher object or function.
60
+ */
61
+ function buildMatcherFromMetadata(matcherMetadata, hmac) {
62
+ let matcher;
63
+ if (typeof matcherMetadata.matcher === 'object') {
64
+ matcher = matcherMetadata.matcher;
65
+ } else {
66
+ if (matcherMetadata.hmac !== hmac(matcherMetadata.matcher)) {
67
+ throw new Error('Invalid HMAC for matcher.');
68
+ }
69
+ matcher = eval('(' + matcherMetadata.matcher + ')').bind({}); // jshint ignore:line
70
+ }
71
+ return matcher;
72
+ }
73
+
74
+ export {
75
+ createHmac,
76
+ matches,
77
+ buildMetadataForMatcher,
78
+ buildMatcherFromMetadata
79
+ };
package/src/util.js CHANGED
@@ -1,6 +1,5 @@
1
- const crypto = require('crypto');
2
- const fs = require('fs');
3
- const mkdirpSync = require('mkdirp').sync;
1
+ import fs from 'fs';
2
+ import { mkdirpSync } from 'mkdirp';
4
3
 
5
4
  /**
6
5
  * Assert that actual and expected match or throw an Error with the given message appended by information about expected and actual value.
@@ -40,74 +39,27 @@ function alignTo(value, alignment) {
40
39
  }
41
40
 
42
41
  /**
43
- * @param {string} secret The secret to use for calculating further HMACs
44
- * @returns {function(string)} A function that calculates the HMAC for a given string
45
- */
46
- const createHmac = secret => string => {
47
- const hmac = crypto.createHmac('sha256', secret);
48
- hmac.update(string);
49
- return hmac.digest('hex');
50
- };
51
-
52
- /**
53
- * @typedef {object|function(object):boolean} Matcher
54
- */
55
-
56
- /**
57
- * @param {object} document The document to check against the matcher.
58
- * @param {Matcher} matcher An object of properties and their values that need to match in the object or a function that checks if the document matches.
59
- * @returns {boolean} True if the document matches the matcher or false otherwise.
42
+ * Method for hashing a string (e.g. a partition name) to a 32-bit unsigned integer.
43
+ *
44
+ * @param {string} str
45
+ * @returns {number}
60
46
  */
61
- function matches(document, matcher) {
62
- if (typeof document === 'undefined') return false;
63
- if (typeof matcher === 'undefined') return true;
64
-
65
- if (typeof matcher === 'function') return matcher(document);
66
-
67
- for (let prop of Object.getOwnPropertyNames(matcher)) {
68
- if (typeof matcher[prop] === 'object') {
69
- if (!matches(document[prop], matcher[prop])) {
70
- return false;
71
- }
72
- } else if (typeof matcher[prop] !== 'undefined' && document[prop] !== matcher[prop]) {
73
- return false;
74
- }
47
+ function hash(str) {
48
+ /* istanbul ignore if */
49
+ if (str.length === 0) {
50
+ return 0;
75
51
  }
76
- return true;
77
- }
52
+ let hash = 5381,
53
+ i = str.length;
78
54
 
79
- /**
80
- * @param {Matcher} matcher The matcher object or function that should be serialized.
81
- * @param {function(string)} hmac A function that calculates a HMAC of the given string.
82
- * @returns {{matcher: string|object, hmac?: string}}
83
- */
84
- function buildMetadataForMatcher(matcher, hmac) {
85
- if (!matcher) {
86
- return undefined;
87
- }
88
- if (typeof matcher === 'object') {
89
- return { matcher };
55
+ while(i) {
56
+ hash = ((hash << 5) + hash) ^ str.charCodeAt(--i); // jshint ignore:line
90
57
  }
91
- const matcherString = matcher.toString();
92
- return { matcher: matcherString, hmac: hmac(matcherString) };
93
- }
94
58
 
95
- /**
96
- * @param {{matcher: string|object, hmac: string}} matcherMetadata The serialized matcher and it's HMAC
97
- * @param {function(string)} hmac A function that calculates a HMAC of the given string.
98
- * @returns {Matcher} The matcher object or function.
99
- */
100
- function buildMatcherFromMetadata(matcherMetadata, hmac) {
101
- let matcher;
102
- if (typeof matcherMetadata.matcher === 'object') {
103
- matcher = matcherMetadata.matcher;
104
- } else {
105
- if (matcherMetadata.hmac !== hmac(matcherMetadata.matcher)) {
106
- throw new Error('Invalid HMAC for matcher.');
107
- }
108
- matcher = eval('(' + matcherMetadata.matcher + ')').bind({}); // jshint ignore:line
109
- }
110
- return matcher;
59
+ /* JavaScript does bitwise operations (like XOR, above) on 32-bit signed
60
+ * integers. Since we want the results to be always positive, convert the
61
+ * signed int to an unsigned by doing an unsigned bitshift. */
62
+ return hash >>> 0; // jshint ignore:line
111
63
  }
112
64
 
113
65
  /**
@@ -169,7 +121,7 @@ function binarySearch(number, length, get) {
169
121
  /**
170
122
  * @param {number} index The 1-based index position to wrap around if < 0 and check against the bounds.
171
123
  * @param {number} length The length of the index and upper bound.
172
- * @returns {number} The wrapped index or -1 if index out of bounds.
124
+ * @returns {number} The wrapped index position or -1 if index out of bounds.
173
125
  */
174
126
  function wrapAndCheck(index, length) {
175
127
  if (typeof index !== 'number') {
@@ -201,17 +153,66 @@ function ensureDirectory(dirName) {
201
153
  return true;
202
154
  }
203
155
 
156
+ /**
157
+ * Perform a k-way merge over multiple streams, invoking a callback for each item in ascending key order.
158
+ * Each stream object is mutated in place by the `advance` function.
159
+ *
160
+ * @param {object[]} streams Array of stream state objects; entries are removed when exhausted.
161
+ * @param {function(object): number} getKey Returns the current sort key for a stream state.
162
+ * @param {function(object): boolean} advance Advances the stream to its next item.
163
+ * Returns true if the stream has more items within range, false if exhausted.
164
+ * @param {function(object): void} visit Called for each stream state in merged order.
165
+ */
166
+ function kWayMerge(streams, getKey, advance, visit) {
167
+ while (streams.length > 0) {
168
+ let minIdx = 0;
169
+ for (let i = 1; i < streams.length; i++) {
170
+ if (getKey(streams[i]) < getKey(streams[minIdx])) {
171
+ minIdx = i;
172
+ }
173
+ }
174
+ visit(streams[minIdx]);
175
+ if (!advance(streams[minIdx])) {
176
+ streams.splice(minIdx, 1);
177
+ }
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Scan a directory for files whose names match a regex pattern, calling a callback for each match.
183
+ * The `onEach` callback receives the first capturing group of the match (`match[1]`), or the full
184
+ * match (`match[0]`) when no capturing group is defined in the pattern.
185
+ *
186
+ * @param {string} directory The directory to scan.
187
+ * @param {RegExp} regexPattern The pattern to match file names against.
188
+ * @param {function(string)} onEach Called with the first capturing group (or full match) for each matching file name.
189
+ * @param {function(Error?)} onDone Called when the scan is complete, or with an error if one occurred.
190
+ */
191
+ function scanForFiles(directory, regexPattern, onEach, onDone) {
192
+ fs.readdir(directory, (err, files) => {
193
+ if (err) {
194
+ return onDone(err);
195
+ }
196
+ let match;
197
+ for (let file of files) {
198
+ if ((match = file.match(regexPattern)) !== null) {
199
+ onEach(match[1] !== undefined ? match[1] : match[0]);
200
+ }
201
+ }
202
+ onDone(null);
203
+ });
204
+ }
205
+
204
206
 
205
- module.exports = {
207
+ export {
206
208
  assert,
207
209
  assertEqual,
210
+ hash,
208
211
  wrapAndCheck,
209
212
  binarySearch,
210
- createHmac,
211
- matches,
212
- buildMetadataForMatcher,
213
- buildMatcherFromMetadata,
214
213
  buildMetadataHeader,
215
214
  alignTo,
216
- ensureDirectory
215
+ ensureDirectory,
216
+ scanForFiles,
217
+ kWayMerge
217
218
  };