@vida-global/core 2.4.3 → 2.4.4
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.
|
@@ -401,9 +401,9 @@ class BaseRecord extends Model {
|
|
|
401
401
|
}
|
|
402
402
|
|
|
403
403
|
|
|
404
|
-
static async cachedIds(key, where, { clear=false }={}) {
|
|
404
|
+
static async cachedIds(key, where, { clear=false, expireSeconds=null }={}) {
|
|
405
405
|
const pk = this.primaryKeyAttribute;
|
|
406
|
-
const result = await this.cachedFetch([key], { clear }, async () => {
|
|
406
|
+
const result = await this.cachedFetch([key], { clear, expireSeconds }, async () => {
|
|
407
407
|
const rows = await this.findAll({ where, attributes: [pk], raw: true});
|
|
408
408
|
return {[key]: rows.map(row => row[pk])};
|
|
409
409
|
});
|
|
@@ -412,7 +412,7 @@ class BaseRecord extends Model {
|
|
|
412
412
|
}
|
|
413
413
|
|
|
414
414
|
|
|
415
|
-
static async cachedFetch(keys, { clear=false }, fetcher) {
|
|
415
|
+
static async cachedFetch(keys, { clear=false, expireSeconds=null }, fetcher) {
|
|
416
416
|
const client = await this._getRedisClient();
|
|
417
417
|
const cachedValues = clear ? [] : await client.mGet(keys);
|
|
418
418
|
const kvPairs = keys.map((key,idx) => {
|
|
@@ -429,14 +429,14 @@ class BaseRecord extends Model {
|
|
|
429
429
|
if (missedKeys.length) {
|
|
430
430
|
this.debugLog('Cache Miss', missedKeys.join(', '));
|
|
431
431
|
fetchedData = await fetcher(missedKeys);
|
|
432
|
-
await this._cacheFetchedData(fetchedData)
|
|
432
|
+
await this._cacheFetchedData(fetchedData, { expireSeconds })
|
|
433
433
|
}
|
|
434
434
|
|
|
435
435
|
return {...cachedData, ...fetchedData};
|
|
436
436
|
}
|
|
437
437
|
|
|
438
438
|
|
|
439
|
-
static async _cacheFetchedData(fetchedData) {
|
|
439
|
+
static async _cacheFetchedData(fetchedData, { expireSeconds=null }={}) {
|
|
440
440
|
const toCache = {};
|
|
441
441
|
|
|
442
442
|
for (const [key, value] of Object.entries(fetchedData)) {
|
|
@@ -445,7 +445,14 @@ class BaseRecord extends Model {
|
|
|
445
445
|
|
|
446
446
|
const client = await this._getRedisClient();
|
|
447
447
|
if (Object.keys(toCache).length) {
|
|
448
|
-
|
|
448
|
+
if (expireSeconds) {
|
|
449
|
+
// mSet cannot carry an expiry, so expiring entries are set one by one.
|
|
450
|
+
await Promise.all(Object.entries(toCache).map(([key, value]) => {
|
|
451
|
+
return client.set(key, value, { EX: expireSeconds });
|
|
452
|
+
}));
|
|
453
|
+
} else {
|
|
454
|
+
await client.mSet(toCache);
|
|
455
|
+
}
|
|
449
456
|
}
|
|
450
457
|
this.debugLog('Cache Set', Object.keys(fetchedData).join(', '));
|
|
451
458
|
}
|
package/lib/logger/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
const
|
|
1
|
+
const util = require('node:util');
|
|
2
|
+
const winston = require('winston');
|
|
2
3
|
const { colorize, combine, printf, timestamp } = winston.format;
|
|
3
4
|
|
|
4
5
|
|
|
@@ -122,12 +123,25 @@ class Logger {
|
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
|
|
125
|
-
debug(
|
|
126
|
-
error(
|
|
127
|
-
info(
|
|
128
|
-
silly(
|
|
129
|
-
verbose(
|
|
130
|
-
warn(
|
|
126
|
+
debug(...args) { this.#logger.debug(this.formatArguments(args)) }
|
|
127
|
+
error(...args) { this.#logger.error(this.formatArguments(args)) }
|
|
128
|
+
info(...args) { this.#logger.info(this.formatArguments(args)) }
|
|
129
|
+
silly(...args) { this.#logger.silly(this.formatArguments(args)) }
|
|
130
|
+
verbose(...args) { this.#logger.verbose(this.formatArguments(args)) }
|
|
131
|
+
warn(...args) { this.#logger.warn(this.formatArguments(args)) }
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
// The final winston formatter emits `message` as-is, so an object argument would land as
|
|
135
|
+
// "[object Object]". `util.formatWithOptions` is what console.log itself uses, so callers get
|
|
136
|
+
// the same handling of objects, format specifiers, and Errors.
|
|
137
|
+
formatArguments(args) {
|
|
138
|
+
return util.formatWithOptions(this.inspectOptions, ...args);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
get inspectOptions() {
|
|
143
|
+
return { depth: 4, colors: false };
|
|
144
|
+
}
|
|
131
145
|
|
|
132
146
|
get _winstonLogger() {
|
|
133
147
|
this.warn('Directly accessing the underlying winston logger should be avoided unless necessary');
|
package/package.json
CHANGED
|
@@ -730,6 +730,14 @@ describe('BaseRecord', () => {
|
|
|
730
730
|
expect(Person.findAll).toHaveBeenCalledTimes(1);
|
|
731
731
|
expect(Person.findAll).toHaveBeenCalledWith({ where, attributes: [pk], raw: true });
|
|
732
732
|
});
|
|
733
|
+
|
|
734
|
+
it ('sets the cache entry with an expiry when expireSeconds is passed', async () => {
|
|
735
|
+
Person.findAll.mockImplementation(() => rows);
|
|
736
|
+
await Person.cachedIds(key, where, {expireSeconds: 300})
|
|
737
|
+
expect(redisClient.mSet).not.toHaveBeenCalled();
|
|
738
|
+
expect(redisClient.set).toHaveBeenCalledTimes(1);
|
|
739
|
+
expect(redisClient.set).toHaveBeenCalledWith(key, JSON.stringify(ids), {EX: 300});
|
|
740
|
+
});
|
|
733
741
|
});
|
|
734
742
|
|
|
735
743
|
|
|
@@ -779,7 +787,7 @@ describe('BaseRecord', () => {
|
|
|
779
787
|
await Person.cachedFetch([key1, key2], {}, fetcher);
|
|
780
788
|
|
|
781
789
|
expect(cacheSpy).toHaveBeenCalledTimes(1);
|
|
782
|
-
expect(cacheSpy).toHaveBeenCalledWith(data);
|
|
790
|
+
expect(cacheSpy).toHaveBeenCalledWith(data, {expireSeconds: null});
|
|
783
791
|
});
|
|
784
792
|
|
|
785
793
|
it ('returns data from cache hits and misses', async () => {
|
|
@@ -841,6 +849,19 @@ describe('BaseRecord', () => {
|
|
|
841
849
|
expect(redisClient.mSet).toHaveBeenCalledTimes(1);
|
|
842
850
|
expect(redisClient.mSet).toHaveBeenCalledWith(cachedData);
|
|
843
851
|
});
|
|
852
|
+
|
|
853
|
+
it ('sets each entry with an expiry instead of a multi set when expireSeconds is passed', async () => {
|
|
854
|
+
const objKey = TestHelpers.Faker.Text.randomString();
|
|
855
|
+
const objVal = TestHelpers.Faker.Text.randomString();
|
|
856
|
+
const obj = {[objKey]: objVal};
|
|
857
|
+
|
|
858
|
+
await Person._cacheFetchedData({ [key1]: obj, [key2]: obj }, { expireSeconds: 60 });
|
|
859
|
+
|
|
860
|
+
expect(redisClient.mSet).not.toHaveBeenCalled();
|
|
861
|
+
expect(redisClient.set).toHaveBeenCalledTimes(2);
|
|
862
|
+
expect(redisClient.set).toHaveBeenCalledWith(key1, JSON.stringify(obj), {EX: 60});
|
|
863
|
+
expect(redisClient.set).toHaveBeenCalledWith(key2, JSON.stringify(obj), {EX: 60});
|
|
864
|
+
});
|
|
844
865
|
});
|
|
845
866
|
|
|
846
867
|
|
|
@@ -197,4 +197,81 @@ describe('Logger', () => {
|
|
|
197
197
|
expect(result[marker]).toEqual('info');
|
|
198
198
|
});
|
|
199
199
|
});
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
describe('#formatArguments', () => {
|
|
203
|
+
it ('returns a lone string unchanged', () => {
|
|
204
|
+
const logger = new Logger(randomScope());
|
|
205
|
+
const message = TestHelpers.Faker.Text.randomString();
|
|
206
|
+
|
|
207
|
+
expect(logger.formatArguments([message])).toEqual(message);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it ('inspects an object instead of stringifying it', () => {
|
|
211
|
+
const logger = new Logger(randomScope());
|
|
212
|
+
const value = TestHelpers.Faker.Math.randomNumber();
|
|
213
|
+
|
|
214
|
+
const result = logger.formatArguments([{ count: value }]);
|
|
215
|
+
|
|
216
|
+
expect(result).toEqual(`{ count: ${value} }`);
|
|
217
|
+
expect(result).not.toContain('[object Object]');
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it ('joins a message and an object the way console.log does', () => {
|
|
221
|
+
const logger = new Logger(randomScope());
|
|
222
|
+
const value = TestHelpers.Faker.Math.randomNumber();
|
|
223
|
+
|
|
224
|
+
const result = logger.formatArguments(['totals', { count: value }]);
|
|
225
|
+
|
|
226
|
+
expect(result).toEqual(`totals { count: ${value} }`);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it ('applies printf style format specifiers', () => {
|
|
230
|
+
const logger = new Logger(randomScope());
|
|
231
|
+
const value = TestHelpers.Faker.Math.randomNumber();
|
|
232
|
+
|
|
233
|
+
expect(logger.formatArguments(['count %d', value])).toEqual(`count ${value}`);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it ('renders an error with its stack', () => {
|
|
237
|
+
const logger = new Logger(randomScope());
|
|
238
|
+
const message = TestHelpers.Faker.Text.randomString();
|
|
239
|
+
|
|
240
|
+
const result = logger.formatArguments([new Error(message)]);
|
|
241
|
+
|
|
242
|
+
expect(result).toContain(message);
|
|
243
|
+
expect(result).toContain('at ');
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it ('nests objects to the configured depth', () => {
|
|
247
|
+
const logger = new Logger(randomScope());
|
|
248
|
+
|
|
249
|
+
const result = logger.formatArguments([{ a: { b: { c: { d: { e: { f: 1 } } } } } }]);
|
|
250
|
+
|
|
251
|
+
expect(result).toContain('e:');
|
|
252
|
+
expect(result).toContain('[Object]');
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
describe.each([
|
|
258
|
+
['debug'],
|
|
259
|
+
['error'],
|
|
260
|
+
['info'],
|
|
261
|
+
['silly'],
|
|
262
|
+
['verbose'],
|
|
263
|
+
['warn'],
|
|
264
|
+
])('#%s', (level) => {
|
|
265
|
+
it ('formats its arguments before handing them to the transport', () => {
|
|
266
|
+
const logger = new Logger(randomScope());
|
|
267
|
+
const spy = jest.spyOn(logger, 'formatArguments');
|
|
268
|
+
const value = TestHelpers.Faker.Math.randomNumber();
|
|
269
|
+
|
|
270
|
+
logger[level]('totals', { count: value });
|
|
271
|
+
|
|
272
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
273
|
+
expect(spy).toHaveBeenCalledWith(['totals', { count: value }]);
|
|
274
|
+
spy.mockRestore();
|
|
275
|
+
});
|
|
276
|
+
});
|
|
200
277
|
});
|