Socket
Socket
Sign inDemoInstall

clever-frontend-utils

Package Overview
Dependencies
9
Maintainers
3
Versions
26
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.8.2 to 2.0.0

dist/reduxAsyncUtils/types.d.ts

19

dist/reduxAsyncUtils/actions.d.ts

@@ -1,2 +0,17 @@

export declare const asPending: (action: any) => any;
export declare const asError: (action: any) => any;
import type { ReduxAction } from "./types";
/**
* Transforms the action into the same type with `meta.isLoading` set to true.
*
* @template [P=any] The payload type for the given action
* @param {ReduxAction<P>} action The action to be transformed into the pending version of itself
* @returns {ReduxAction<P>} The pending action
*/
export declare const asPending: <P = any>(action: ReduxAction<P>) => ReduxAction<P>;
/**
* Transforms the action into the same type with `error` set to true.
*
* @template [P=any] The payload type for the given action
* @param {ReduxAction<P>} action The action to be transformed into the error version of itself
* @returns {ReduxAction<P>} The error action
*/
export declare const asError: <P = any>(action: ReduxAction<P>) => ReduxAction<P>;

@@ -15,5 +15,19 @@ "use strict";

exports.asError = exports.asPending = void 0;
/**
* Transforms the action into the same type with `meta.isLoading` set to true.
*
* @template [P=any] The payload type for the given action
* @param {ReduxAction<P>} action The action to be transformed into the pending version of itself
* @returns {ReduxAction<P>} The pending action
*/
var asPending = function (action) { return (__assign(__assign({}, action), { meta: __assign(__assign({}, action.meta), { isLoading: true }) })); };
exports.asPending = asPending;
/**
* Transforms the action into the same type with `error` set to true.
*
* @template [P=any] The payload type for the given action
* @param {ReduxAction<P>} action The action to be transformed into the error version of itself
* @returns {ReduxAction<P>} The error action
*/
var asError = function (action) { return (__assign(__assign({}, action), { error: true })); };
exports.asError = asError;

42

dist/reduxAsyncUtils/reducers.d.ts

@@ -22,26 +22,37 @@ /**

*/
import type { ReduxAction, SubstateItemData, SubstateListData } from "./types";
/**
* Returns a reducer that manages an async data item.
*
* @param {function(dataFromState, action):object} [reduceFn] - A function that produces a
* @template [I=any] The type for an item returned by these selectors
* @template [E=any] The error type for an item's failed load
* @template [P=I] The action payload type
* @param {function(I, ReduxAction<I>): I} [reduceFn] A function that produces a
* new data entry, given the data entry from the current state and the action object
* @return {function:Object} - The reducer
* @return {function(SubstateItemData<I,E>, ReduxAction<P|E>): SubstateItemData<I,E>} The reducer
*/
export declare const newAsyncDataReducer: (reduceFn?: (state: any, action: any) => any) => (state: any, action: any) => any;
export declare const newAsyncDataReducer: <I = any, E = any, P = I>(reduceFn?: (state: I, action: ReduxAction<I>) => I) => (state: SubstateItemData<I, E>, action: ReduxAction<E | P>) => SubstateItemData<I, E>;
/**
* Returns a reducer that stores/replaces a single item in an async data list.
*
* @param {function(payload):string} keyFn - A function that returns a unique key for the list item
* @param {function(dataFromState, action):object} [reduceFn] - A function that produces a
* @template [I=any] The type for an item returned by these selectors
* @template [E=any] The error type for an item's failed load
* @param {function(I | E): string} keyFn A function that returns a unique key for the list item
* @param {function(I, ReduxAction<I>): I} [itemReduceFn] A function that produces a
* new data entry, given the data entry from the current state and the action object
* @return {function:Object} - The reducer
* @return {function(SubstateListData<I,E>, ReduxAction<I|E>): SubstateListData<I,E>} The reducer
*/
export declare const newAsyncListItemReducer: (keyFn: any, reduceFn?: (state: any, action: any) => any) => (state: any, action: any) => any;
export declare const newAsyncListItemReducer: <I = any, E = any>(keyFn: (payload: I | E) => string, reduceFn?: (state: I, action: ReduxAction<I>) => I) => (state: SubstateListData<I, E, E>, action: ReduxAction<I | E>) => SubstateListData<I, E, E>;
/**
* Returns a reducer that deletes a single item in an async data list.
*
* @param {function(payload):string} keyFn - A function that returns a unique key for the list item
* @return {function:Object} - The reducer
* @template [I=any] The type for an item returned by these selectors
* @template [E=any] The error type for an item's failed load
* @template [P=any] The action payload type. May likely be *like* `I`, similar to the
* newAsyncDataReducer or newAsyncListItemReducer versions, but the only requirement is that `keyFn`
* be able to extract the correct key from the payload.
* @param {function(P): string} keyFn - A function that returns a unique key for the list item
* @return {function(SubstateListData<I,E>, action: ReduxAction<P>)} - The reducer
*/
export declare const newAsyncListItemDeleteReducer: (keyFn: any) => (state: any, action: any) => any;
export declare const newAsyncListItemDeleteReducer: <I = any, E = any, P = any>(keyFn: (payload: P) => string) => (state: SubstateListData<I, E, E>, action: ReduxAction<P>) => SubstateListData<I, E, E>;
/**

@@ -51,9 +62,12 @@ * Returns a reducer that takes a list payload and stores it as an object, where each value is of

*
* @param {function(payloadItem):string} keyFn - A function that returns a unique key for every
* @template [I=any] The type for an item returned by these selectors
* @template [E=any] The error type for an item's failed load
* @template [LE=E] The error type for the list's failed load. May or may not be the same as `E`.
* @param {function(I): string} keyFn - A function that returns a unique key for every
* list item
* @param {function(dataFromItemState, itemAction):object} [itemReduceFn] - A function that
* @param {function(SubstateItemData<I,E>, ReduxAction<I[]|LE>): I} [itemReduceFn] - A function that
* produces a new data entry for a single item in the list, given the data entry from the current
* item state and an action containing the item as its payload
* @return {function:Object} - The reducer
* @return {function(SubstateListData<I,E,LE>, ReduxAction<I[]|LE>): SubstateListData<I,E,LE>} - The reducer
*/
export declare const newAsyncListReducer: (keyFn: any, itemReduceFn?: (state: any, action: any) => any) => (state: any, action: any) => any;
export declare const newAsyncListReducer: <I = any, E = any, LE = E>(keyFn: (item: I) => string, itemReduceFn?: (state: SubstateItemData<I, E>, action: ReduxAction<I>) => I) => (state: SubstateListData<I, E, LE>, action: ReduxAction<LE | I[]>) => SubstateListData<I, E, LE>;

@@ -37,2 +37,15 @@ "use strict";

var _ = require("lodash");
/**
* An identity function that is the default reduce function for the generic reducers in this module.
* Given the state and an action, returns the action's payload as the new item state for this
* substate of the store.
*
* @template [S=any] The type representation of the Redux store state. Can be a full or partial
* typing; if the state type represents a partial slice of the store state, it is recommended to
* declare that type as an interface.
* @template [P=any] The payload type for the given action
* @param state {S} The Redux store state
* @param action {ReduxAction<P>} The action being reduced
* @returns {P} The action payload
*/
var identityFn = function (state, action) { return action.payload; };

@@ -42,5 +55,8 @@ /**

*
* @param {function(dataFromState, action):object} [reduceFn] - A function that produces a
* @template [I=any] The type for an item returned by these selectors
* @template [E=any] The error type for an item's failed load
* @template [P=I] The action payload type
* @param {function(I, ReduxAction<I>): I} [reduceFn] A function that produces a
* new data entry, given the data entry from the current state and the action object
* @return {function:Object} - The reducer
* @return {function(SubstateItemData<I,E>, ReduxAction<P|E>): SubstateItemData<I,E>} The reducer
*/

@@ -61,2 +77,8 @@ var newAsyncDataReducer = function (reduceFn) {

lastUpdated: new Date(),
// This typecast to ReduxAction<I> is a bit misleading, because it's not always accurate; see
// newAsyncListReducer below. If a caller wishes to solely update the error or loading state, as
// in the previous two cases, then the payload is either irrelevant (loading) or E (error). We
// assume that if neither of those things are true, then the payload is of the item type I.
// Alternatively, if we do not intend for callers to use it this way, then we can force the
// typecast in newAsyncListReducer instead.
data: reduceFn(state.data, action),

@@ -70,6 +92,8 @@ };

*
* @param {function(payload):string} keyFn - A function that returns a unique key for the list item
* @param {function(dataFromState, action):object} [reduceFn] - A function that produces a
* @template [I=any] The type for an item returned by these selectors
* @template [E=any] The error type for an item's failed load
* @param {function(I | E): string} keyFn A function that returns a unique key for the list item
* @param {function(I, ReduxAction<I>): I} [itemReduceFn] A function that produces a
* new data entry, given the data entry from the current state and the action object
* @return {function:Object} - The reducer
* @return {function(SubstateListData<I,E>, ReduxAction<I|E>): SubstateListData<I,E>} The reducer
*/

@@ -90,4 +114,9 @@ var newAsyncListItemReducer = function (keyFn, reduceFn) {

*
* @param {function(payload):string} keyFn - A function that returns a unique key for the list item
* @return {function:Object} - The reducer
* @template [I=any] The type for an item returned by these selectors
* @template [E=any] The error type for an item's failed load
* @template [P=any] The action payload type. May likely be *like* `I`, similar to the
* newAsyncDataReducer or newAsyncListItemReducer versions, but the only requirement is that `keyFn`
* be able to extract the correct key from the payload.
* @param {function(P): string} keyFn - A function that returns a unique key for the list item
* @return {function(SubstateListData<I,E>, action: ReduxAction<P>)} - The reducer
*/

@@ -109,8 +138,11 @@ var newAsyncListItemDeleteReducer = function (keyFn) { return function (state, action) {

*
* @param {function(payloadItem):string} keyFn - A function that returns a unique key for every
* @template [I=any] The type for an item returned by these selectors
* @template [E=any] The error type for an item's failed load
* @template [LE=E] The error type for the list's failed load. May or may not be the same as `E`.
* @param {function(I): string} keyFn - A function that returns a unique key for every
* list item
* @param {function(dataFromItemState, itemAction):object} [itemReduceFn] - A function that
* @param {function(SubstateItemData<I,E>, ReduxAction<I[]|LE>): I} [itemReduceFn] - A function that
* produces a new data entry for a single item in the list, given the data entry from the current
* item state and an action containing the item as its payload
* @return {function:Object} - The reducer
* @return {function(SubstateListData<I,E,LE>, ReduxAction<I[]|LE>): SubstateListData<I,E,LE>} - The reducer
*/

@@ -117,0 +149,0 @@ var newAsyncListReducer = function (keyFn, itemReduceFn) {

@@ -1,29 +0,48 @@

export declare const newAsyncDataSelectors: (fieldPath: any, options?: {
itemDefault: any;
}) => {
itemLoading: (state: any) => boolean;
itemLoadError: (state: any) => any;
itemLoaded: (state: any) => boolean;
itemLastUpdated: (state: any) => any;
item: (state: any) => any;
import type { ItemMap } from "./types";
/**
* The options type for newAsyncDataSelectors and newAsyncListSelectors. Currently the only option
* is for specifying the default item value.
*
* @template I The item type for this group of selectors
*/
export type SelectorOptions<I> = {
itemDefault: I | null;
};
export declare const newAsyncListSelectors: (subKeyPath: any, options?: {
itemDefault: any;
}) => {
listLoading: (state: any) => boolean;
listLoadError: (state: any) => any;
listLoaded: (state: any) => boolean;
listLastUpdated: (state: any) => any;
list: import("reselect").OutputSelector<any, any[], (res: any) => any[]>;
itemsByID: import("reselect").OutputSelector<any, {
[x: string]: any;
}, (res: any) => {
[x: string]: any;
/**
* @template [S=any] The state that will be passed to this selector. This can be the type for the whole store or
* an interface that defines only the slice of the store state needed by these selectors.
* @template [I=any] The type for an item returned by these selectors.
* @template [E=any] The error type for an item's failed load.
*/
export declare const newAsyncDataSelectors: <S = any, I = any, E = any>(fieldPath: string, options?: SelectorOptions<I>) => {
itemLoading: (state: S) => boolean;
itemLoadError: (state: S) => E;
itemLoaded: (state: S) => boolean;
itemLastUpdated: (state: S) => Date | undefined;
item: (state: S) => I;
};
/**
* @template [S=any] The state that will be passed to this selector. This can be the type for the whole store or
* an interface that defines only the slice of the store state needed by these selectors.
* @template [I=any] The type for an item returned by these selectors.
* @template [E=any] The error type for an item's failed load.
* @template [LE=E] The error for failing to load a collection of items.
*/
export declare const newAsyncListSelectors: <S = any, I = any, E = any, LE = E>(subKeyPath: string, options?: SelectorOptions<I>) => {
listLoading: (state: S) => boolean;
listLoadError: (state: S) => LE;
listLoaded: (state: S) => boolean;
listLastUpdated: (state: S) => Date | undefined;
list: import("reselect").OutputSelector<S, I[], (res: ItemMap<I, E>) => I[]>;
itemsByID: import("reselect").OutputSelector<S, {
[key: string]: I;
}, (res: ItemMap<I, E>) => {
[key: string]: I;
}>;
itemLoading: (state: any, id: any) => boolean;
itemLoadError: (state: any, id: any) => any;
itemLoaded: (state: any, id: any) => boolean;
itemLastUpdated: (state: any, id: any) => any;
itemsLoaded: (state: any, ids: any) => boolean;
item: (state: any, id: any) => any;
itemLoading: (state: S, id: string) => boolean;
itemLoadError: (state: S, id: string) => E;
itemLoaded: (state: S, id: string) => boolean;
itemLastUpdated: (state: S, id: string) => Date | undefined;
itemsLoaded: (state: S, ids: string[]) => boolean;
item: (state: S, id: string) => I;
};

@@ -6,5 +6,13 @@ "use strict";

var _ = require("lodash");
/**
* @template [S=any] The state that will be passed to this selector. This can be the type for the whole store or
* an interface that defines only the slice of the store state needed by these selectors.
* @template [I=any] The type for an item returned by these selectors.
* @template [E=any] The error type for an item's failed load.
*/
var newAsyncDataSelectors = function (fieldPath, options) {
if (options === void 0) { options = { itemDefault: null }; }
var itemState = function (state) { return _.get(state, fieldPath, {}); };
var itemState = function (state) {
return _.get(state, fieldPath, {});
};
return {

@@ -19,5 +27,14 @@ itemLoading: function (state) { return !!itemState(state).isLoading; },

exports.newAsyncDataSelectors = newAsyncDataSelectors;
/**
* @template [S=any] The state that will be passed to this selector. This can be the type for the whole store or
* an interface that defines only the slice of the store state needed by these selectors.
* @template [I=any] The type for an item returned by these selectors.
* @template [E=any] The error type for an item's failed load.
* @template [LE=E] The error for failing to load a collection of items.
*/
var newAsyncListSelectors = function (subKeyPath, options) {
if (options === void 0) { options = { itemDefault: null }; }
var listState = function (state) { return _.get(state, subKeyPath, {}); };
var listState = function (state) {
return _.get(state, subKeyPath, {});
};
var listData = function (state) { return listState(state).data || {}; };

@@ -24,0 +41,0 @@ var itemState = function (state, id) { return listData(state)[id] || {}; };

@@ -309,143 +309,130 @@ "use strict";

});
describe("events", function () {
describe("events include request and response information", function () {
ALL_METHODS.forEach(function (method) {
it("".concat(method, " request"), function (done) { return __awaiter(void 0, void 0, void 0, function () {
var testBody, testHeaders, testResponse;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
testBody = { x: 5 };
testHeaders = { "X-test-header": "value", "X-test-2": "value2" };
testResponse = new Response("ok");
Fetch_1.Fetch.on(Fetch_1.Fetch.Event.SUCCESS, function (request, response) {
try {
// This is a bit annoying, but Fetch.Get automatically removes any trailing slashes.
if (method === Fetch_1.Fetch.Method.GET) {
expect(request.url).toEqual("http://example.com");
}
else {
expect(request.url).toEqual(TEST_URL);
}
expect(request.method).toEqual(method);
// Fetch.Get and Fetch.Delete don't support custom bodies or headers.
if (method !== Fetch_1.Fetch.Method.GET && method !== Fetch_1.Fetch.Method.DELETE) {
expect(request.body).toEqual(JSON.stringify(testBody));
// We can't directly compare the headers objects because Fetch adds some itself, like Content-Type.
expect(request.headers.get("X-test-header")).toEqual("value");
expect(request.headers.get("X-test-2")).toEqual("value2");
}
expect(response).toEqual(testResponse);
done();
}
catch (err) {
done.fail(err);
}
});
fetchMock.once("*", testResponse);
if (!(method === Fetch_1.Fetch.Method.GET || method === Fetch_1.Fetch.Method.DELETE)) return [3 /*break*/, 2];
return [4 /*yield*/, FETCH_FUNCTIONS[method](TEST_URL)];
case 1:
_a.sent();
return [3 /*break*/, 4];
case 2: return [4 /*yield*/, FETCH_FUNCTIONS[method](TEST_URL, testBody, testHeaders)];
case 3:
_a.sent();
_a.label = 4;
case 4: return [2 /*return*/];
}
});
}); });
describe.each(ALL_METHODS)("events for FetchMethod %s", function (method) {
jest.useFakeTimers();
test("events include request and response information", function () { return __awaiter(void 0, void 0, void 0, function () {
var testBody, testHeaders, testResponse, mock;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
testBody = { x: 5 };
testHeaders = { "X-test-header": "value", "X-test-2": "value2" };
testResponse = new Response("ok");
mock = jest.fn(function (request, response) {
// This is a bit annoying, but Fetch.Get automatically removes any trailing slashes.
if (method === Fetch_1.Fetch.Method.GET) {
expect(request.url).toEqual("http://example.com");
}
else {
expect(request.url).toEqual(TEST_URL);
}
expect(request.method).toEqual(method);
// Fetch.Get and Fetch.Delete don't support custom bodies or headers.
if (method !== Fetch_1.Fetch.Method.GET && method !== Fetch_1.Fetch.Method.DELETE) {
expect(request.body).toEqual(JSON.stringify(testBody));
// We can't directly compare the headers objects because Fetch adds some itself, like Content-Type.
expect(request.headers.get("X-test-header")).toEqual("value");
expect(request.headers.get("X-test-2")).toEqual("value2");
}
expect(response).toEqual(testResponse);
});
Fetch_1.Fetch.on(Fetch_1.Fetch.Event.SUCCESS, mock);
fetchMock.once("*", testResponse);
if (!(method === Fetch_1.Fetch.Method.GET || method === Fetch_1.Fetch.Method.DELETE)) return [3 /*break*/, 2];
return [4 /*yield*/, FETCH_FUNCTIONS[method](TEST_URL)];
case 1:
_a.sent();
return [3 /*break*/, 4];
case 2: return [4 /*yield*/, FETCH_FUNCTIONS[method](TEST_URL, testBody, testHeaders)];
case 3:
_a.sent();
_a.label = 4;
case 4:
jest.runAllTimers();
expect(mock).toBeCalled();
return [2 /*return*/];
}
});
});
describe("fires a SUCCESS event for a successful request", function () {
ALL_METHODS.forEach(function (method) {
it("".concat(method, " request"), function (done) { return __awaiter(void 0, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
Fetch_1.Fetch.on(Fetch_1.Fetch.Event.SUCCESS, function (request, response) {
done();
});
fetchMock.once("*", "ok");
return [4 /*yield*/, FETCH_FUNCTIONS[method](TEST_URL)];
case 1:
_a.sent();
return [2 /*return*/];
}
});
}); });
}); });
test("fires a SUCCESS event for a successful request", function () { return __awaiter(void 0, void 0, void 0, function () {
var mock;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
mock = jest.fn();
Fetch_1.Fetch.on(Fetch_1.Fetch.Event.SUCCESS, mock);
fetchMock.once("*", "ok");
return [4 /*yield*/, FETCH_FUNCTIONS[method](TEST_URL)];
case 1:
_a.sent();
jest.runAllTimers();
expect(mock).toBeCalled();
return [2 /*return*/];
}
});
});
describe("fires an ERROR event for an unsuccessful request", function () {
ALL_METHODS.forEach(function (method) {
it("".concat(method, " request"), function (done) { return __awaiter(void 0, void 0, void 0, function () {
var err_2;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
Fetch_1.Fetch.on(Fetch_1.Fetch.Event.ERROR, function (request, response) {
done();
});
fetchMock.once("*", new Response("error", { status: 401 }));
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
return [4 /*yield*/, FETCH_FUNCTIONS[method](TEST_URL)];
case 2:
_a.sent();
return [3 /*break*/, 4];
case 3:
err_2 = _a.sent();
return [3 /*break*/, 4];
case 4: return [2 /*return*/];
}
});
}); });
}); });
it("fires an ERROR event for an unsuccessful request", function () { return __awaiter(void 0, void 0, void 0, function () {
var cb, err_2;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
expect.assertions(1);
cb = jest.fn();
Fetch_1.Fetch.on(Fetch_1.Fetch.Event.ERROR, cb);
fetchMock.once("*", new Response("error", { status: 401 }));
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
return [4 /*yield*/, FETCH_FUNCTIONS[method](TEST_URL)];
case 2:
_a.sent();
return [3 /*break*/, 4];
case 3:
err_2 = _a.sent();
return [3 /*break*/, 4];
case 4:
jest.runAllTimers();
expect(cb).toBeCalled();
return [2 /*return*/];
}
});
});
describe("allows registering multiple handlers for the same event", function () {
ALL_METHODS.forEach(function (method) {
it("".concat(method, " request"), function (done) { return __awaiter(void 0, void 0, void 0, function () {
var eventsFired, markEventFired, err_3;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
eventsFired = {
a: false,
b: false,
c: false,
d: false,
};
markEventFired = function (id) { return function (request, response) {
eventsFired[id] = true;
if (eventsFired.a && eventsFired.b && eventsFired.c && eventsFired.d) {
done();
}
}; };
Fetch_1.Fetch.on(Fetch_1.Fetch.Event.SUCCESS, markEventFired("a"));
Fetch_1.Fetch.on(Fetch_1.Fetch.Event.SUCCESS, markEventFired("b"));
Fetch_1.Fetch.on(Fetch_1.Fetch.Event.ERROR, markEventFired("c"));
Fetch_1.Fetch.on(Fetch_1.Fetch.Event.ERROR, markEventFired("d"));
fetchMock.once("".concat(TEST_URL, "success"), "ok");
fetchMock.once("".concat(TEST_URL, "error"), new Response("error", { status: 404 }));
return [4 /*yield*/, FETCH_FUNCTIONS[method]("".concat(TEST_URL, "success"))];
case 1:
_a.sent();
_a.label = 2;
case 2:
_a.trys.push([2, 4, , 5]);
return [4 /*yield*/, FETCH_FUNCTIONS[method]("".concat(TEST_URL, "error"))];
case 3:
_a.sent();
return [3 /*break*/, 5];
case 4:
err_3 = _a.sent();
return [3 /*break*/, 5];
case 5: return [2 /*return*/];
}
});
}); });
}); });
test("allows registering multiple handlers for the same event", function () { return __awaiter(void 0, void 0, void 0, function () {
var spy, mockEventHandler, err_3;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
jest.useFakeTimers();
expect.assertions(5);
spy = jest.fn(function (id) { return id; });
mockEventHandler = function (id) { return function () { return spy(id); }; };
Fetch_1.Fetch.on(Fetch_1.Fetch.Event.SUCCESS, mockEventHandler("a"));
Fetch_1.Fetch.on(Fetch_1.Fetch.Event.SUCCESS, mockEventHandler("b"));
Fetch_1.Fetch.on(Fetch_1.Fetch.Event.ERROR, mockEventHandler("c"));
Fetch_1.Fetch.on(Fetch_1.Fetch.Event.ERROR, mockEventHandler("d"));
fetchMock.once("".concat(TEST_URL, "success"), "ok");
fetchMock.once("".concat(TEST_URL, "error"), new Response("error", { status: 404 }));
return [4 /*yield*/, FETCH_FUNCTIONS[method]("".concat(TEST_URL, "success"))];
case 1:
_a.sent();
_a.label = 2;
case 2:
_a.trys.push([2, 4, , 5]);
return [4 /*yield*/, FETCH_FUNCTIONS[method]("".concat(TEST_URL, "error"))];
case 3:
_a.sent();
return [3 /*break*/, 5];
case 4:
err_3 = _a.sent();
return [3 /*break*/, 5];
case 5:
jest.runAllTimers();
expect(spy).toBeCalledTimes(4);
expect(spy).toBeCalledWith("a");
expect(spy).toBeCalledWith("b");
expect(spy).toBeCalledWith("c");
expect(spy).toBeCalledWith("d");
return [2 /*return*/];
}
});
});
}); });
});

@@ -523,16 +510,13 @@ describe("global headers", function () {

}); });
it("includes global headers in event handlers input", function (done) { return __awaiter(void 0, void 0, void 0, function () {
it("includes global headers in event handlers input", function () { return __awaiter(void 0, void 0, void 0, function () {
var mockEventHandler;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
jest.useFakeTimers();
mockEventHandler = jest.fn(function (request) {
expect(request.headers.get("x-test")).toEqual("value");
});
Fetch_1.Fetch.setGlobalHeader("x-test", "value");
Fetch_1.Fetch.on(Fetch_1.Fetch.Event.SUCCESS, function (request, response) {
try {
expect(request.headers.get("x-test")).toEqual("value");
done();
}
catch (err) {
done.fail(err);
}
});
Fetch_1.Fetch.on(Fetch_1.Fetch.Event.SUCCESS, mockEventHandler);
fetchMock.once("*", "ok");

@@ -542,2 +526,4 @@ return [4 /*yield*/, Fetch_1.Fetch.post(TEST_URL)];

_a.sent();
jest.runAllTimers();
expect(mockEventHandler).toBeCalled();
return [2 /*return*/];

@@ -544,0 +530,0 @@ }

@@ -85,2 +85,3 @@ "use strict";

};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Endpoint.prototype.handler = function (_req, _res, _next) {

@@ -87,0 +88,0 @@ return __awaiter(this, void 0, void 0, function () {

@@ -1,2 +0,11 @@

export const asPending = (action) => ({
import type { ReduxAction } from "./types";
/**
* Transforms the action into the same type with `meta.isLoading` set to true.
*
* @template [P=any] The payload type for the given action
* @param {ReduxAction<P>} action The action to be transformed into the pending version of itself
* @returns {ReduxAction<P>} The pending action
*/
export const asPending = <P = any>(action: ReduxAction<P>): ReduxAction<P> => ({
...action,

@@ -9,5 +18,12 @@ meta: {

export const asError = (action) => ({
/**
* Transforms the action into the same type with `error` set to true.
*
* @template [P=any] The payload type for the given action
* @param {ReduxAction<P>} action The action to be transformed into the error version of itself
* @returns {ReduxAction<P>} The error action
*/
export const asError = <P = any>(action: ReduxAction<P>): ReduxAction<P> => ({
...action,
error: true,
});

@@ -24,4 +24,18 @@ /**

import * as _ from "lodash";
import type { ItemMap, ReduxAction, SubstateItemData, SubstateListData } from "./types";
const identityFn = (state, action) => action.payload;
/**
* An identity function that is the default reduce function for the generic reducers in this module.
* Given the state and an action, returns the action's payload as the new item state for this
* substate of the store.
*
* @template [S=any] The type representation of the Redux store state. Can be a full or partial
* typing; if the state type represents a partial slice of the store state, it is recommended to
* declare that type as an interface.
* @template [P=any] The payload type for the given action
* @param state {S} The Redux store state
* @param action {ReduxAction<P>} The action being reduced
* @returns {P} The action payload
*/
const identityFn = <S = any, P = any>(state: S, action: ReduxAction<P>): P => action.payload;

@@ -31,7 +45,12 @@ /**

*
* @param {function(dataFromState, action):object} [reduceFn] - A function that produces a
* @template [I=any] The type for an item returned by these selectors
* @template [E=any] The error type for an item's failed load
* @template [P=I] The action payload type
* @param {function(I, ReduxAction<I>): I} [reduceFn] A function that produces a
* new data entry, given the data entry from the current state and the action object
* @return {function:Object} - The reducer
* @return {function(SubstateItemData<I,E>, ReduxAction<P|E>): SubstateItemData<I,E>} The reducer
*/
export const newAsyncDataReducer = (reduceFn = identityFn) => (state, action) => {
export const newAsyncDataReducer = <I = any, E = any, P = I>(
reduceFn: (state: I, action: ReduxAction<I>) => I = identityFn,
) => (state: SubstateItemData<I, E>, action: ReduxAction<P | E>): SubstateItemData<I, E> => {
// Leave existing data intact while loading

@@ -50,3 +69,3 @@ if (action.meta && action.meta.isLoading) {

isLoading: false,
error: action.payload,
error: action.payload as E,
lastUpdated: new Date(),

@@ -59,3 +78,9 @@ };

lastUpdated: new Date(),
data: reduceFn(state.data, action),
// This typecast to ReduxAction<I> is a bit misleading, because it's not always accurate; see
// newAsyncListReducer below. If a caller wishes to solely update the error or loading state, as
// in the previous two cases, then the payload is either irrelevant (loading) or E (error). We
// assume that if neither of those things are true, then the payload is of the item type I.
// Alternatively, if we do not intend for callers to use it this way, then we can force the
// typecast in newAsyncListReducer instead.
data: reduceFn(state.data, action as ReduxAction<I>),
};

@@ -67,11 +92,16 @@ };

*
* @param {function(payload):string} keyFn - A function that returns a unique key for the list item
* @param {function(dataFromState, action):object} [reduceFn] - A function that produces a
* @template [I=any] The type for an item returned by these selectors
* @template [E=any] The error type for an item's failed load
* @param {function(I | E): string} keyFn A function that returns a unique key for the list item
* @param {function(I, ReduxAction<I>): I} [itemReduceFn] A function that produces a
* new data entry, given the data entry from the current state and the action object
* @return {function:Object} - The reducer
* @return {function(SubstateListData<I,E>, ReduxAction<I|E>): SubstateListData<I,E>} The reducer
*/
export const newAsyncListItemReducer = (keyFn, reduceFn = identityFn) => (state, action) => {
export const newAsyncListItemReducer = <I = any, E = any>(
keyFn: (payload: I | E) => string,
reduceFn: (state: I, action: ReduxAction<I>) => I = identityFn,
) => (state: SubstateListData<I, E>, action: ReduxAction<I | E>): SubstateListData<I, E> => {
const itemKey = keyFn(action.payload);
const currentItemState = _.get(state, `data.${itemKey}`) || {};
const newItemState = newAsyncDataReducer(reduceFn)(currentItemState, action);
const newItemState = newAsyncDataReducer<I, E>(reduceFn)(currentItemState, action);
return { ...state, data: { ...state.data, [itemKey]: newItemState } };

@@ -83,6 +113,13 @@ };

*
* @param {function(payload):string} keyFn - A function that returns a unique key for the list item
* @return {function:Object} - The reducer
* @template [I=any] The type for an item returned by these selectors
* @template [E=any] The error type for an item's failed load
* @template [P=any] The action payload type. May likely be *like* `I`, similar to the
* newAsyncDataReducer or newAsyncListItemReducer versions, but the only requirement is that `keyFn`
* be able to extract the correct key from the payload.
* @param {function(P): string} keyFn - A function that returns a unique key for the list item
* @return {function(SubstateListData<I,E>, action: ReduxAction<P>)} - The reducer
*/
export const newAsyncListItemDeleteReducer = (keyFn) => (state, action) => {
export const newAsyncListItemDeleteReducer = <I = any, E = any, P = any>(
keyFn: (payload: P) => string,
) => (state: SubstateListData<I, E>, action: ReduxAction<P>): SubstateListData<I, E> => {
const itemKey = keyFn(action.payload);

@@ -92,3 +129,3 @@

// new reference
const newState = { ...state, data: { ...state.data } };
const newState = { ...state, data: { ...state.data } } as SubstateListData<I, E>;
if (newState.data && newState.data[itemKey]) {

@@ -105,12 +142,23 @@ delete newState.data[itemKey];

*
* @param {function(payloadItem):string} keyFn - A function that returns a unique key for every
* @template [I=any] The type for an item returned by these selectors
* @template [E=any] The error type for an item's failed load
* @template [LE=E] The error type for the list's failed load. May or may not be the same as `E`.
* @param {function(I): string} keyFn - A function that returns a unique key for every
* list item
* @param {function(dataFromItemState, itemAction):object} [itemReduceFn] - A function that
* @param {function(SubstateItemData<I,E>, ReduxAction<I[]|LE>): I} [itemReduceFn] - A function that
* produces a new data entry for a single item in the list, given the data entry from the current
* item state and an action containing the item as its payload
* @return {function:Object} - The reducer
* @return {function(SubstateListData<I,E,LE>, ReduxAction<I[]|LE>): SubstateListData<I,E,LE>} - The reducer
*/
export const newAsyncListReducer = (keyFn, itemReduceFn = identityFn) => (state, action) => {
export const newAsyncListReducer = <I = any, E = any, LE = E>(
keyFn: (item: I) => string,
itemReduceFn: (state: SubstateItemData<I, E>, action: ReduxAction<I>) => I = identityFn,
) => (
state: SubstateListData<I, E, LE>,
action: ReduxAction<I[] | LE>,
): SubstateListData<I, E, LE> => {
// Leave existing data intact but update metadata if necessary
const newState = newAsyncDataReducer((currentState) => currentState || {})(state, action);
const newState: SubstateListData<I, E, LE> = newAsyncDataReducer<ItemMap<I, E>, LE, I[]>(
(currentState: ItemMap<I, E>): ItemMap<I, E> => currentState || {},
)(state, action);

@@ -123,3 +171,3 @@ if (newState.isLoading || newState.error) {

newState.data = {};
const items = action.payload;
const items = action.payload as I[];

@@ -140,3 +188,3 @@ // If items is null or undefined just give them empty data. We need to allow

const currentItemState = _.get(state, `data.${itemKey}`) || {};
const newItemState = newAsyncDataReducer(itemReduceFn)(currentItemState, {
const newItemState = newAsyncDataReducer<I, E>(itemReduceFn)(currentItemState, {
...action,

@@ -143,0 +191,0 @@ payload: item,

import { createSelector as createMemoizedSelector } from "reselect";
import * as _ from "lodash";
import type { ItemMap, SubstateItemData, SubstateListData } from "./types";
export const newAsyncDataSelectors = (fieldPath, options = { itemDefault: null }) => {
const itemState = (state) => _.get(state, fieldPath, {});
/**
* The options type for newAsyncDataSelectors and newAsyncListSelectors. Currently the only option
* is for specifying the default item value.
*
* @template I The item type for this group of selectors
*/
export type SelectorOptions<I> = {
itemDefault: I | null;
};
/**
* @template [S=any] The state that will be passed to this selector. This can be the type for the whole store or
* an interface that defines only the slice of the store state needed by these selectors.
* @template [I=any] The type for an item returned by these selectors.
* @template [E=any] The error type for an item's failed load.
*/
export const newAsyncDataSelectors = <S = any, I = any, E = any>(
fieldPath: string,
options: SelectorOptions<I> = { itemDefault: null },
) => {
const itemState = (state: S): SubstateItemData<I, E> =>
_.get(state, fieldPath, {} as SubstateItemData<I, E>);
return {
itemLoading: (state) => !!itemState(state).isLoading,
itemLoadError: (state) => itemState(state).error,
itemLoaded: (state) => !!itemState(state).lastUpdated,
itemLastUpdated: (state) => itemState(state).lastUpdated,
item: (state) => itemState(state).data || options.itemDefault,
itemLoading: (state: S): boolean => !!itemState(state).isLoading,
itemLoadError: (state: S): E | undefined => itemState(state).error,
itemLoaded: (state: S): boolean => !!itemState(state).lastUpdated,
itemLastUpdated: (state: S): Date | undefined => itemState(state).lastUpdated,
item: (state: S): I => itemState(state).data || options.itemDefault,
};
};
export const newAsyncListSelectors = (subKeyPath, options = { itemDefault: null }) => {
const listState = (state) => _.get(state, subKeyPath, {});
const listData = (state) => listState(state).data || {};
const itemState = (state, id) => listData(state)[id] || {};
const itemLoaded = (state, id) => !!itemState(state, id).lastUpdated;
/**
* @template [S=any] The state that will be passed to this selector. This can be the type for the whole store or
* an interface that defines only the slice of the store state needed by these selectors.
* @template [I=any] The type for an item returned by these selectors.
* @template [E=any] The error type for an item's failed load.
* @template [LE=E] The error for failing to load a collection of items.
*/
export const newAsyncListSelectors = <S = any, I = any, E = any, LE = E>(
subKeyPath: string,
options: SelectorOptions<I> = { itemDefault: null },
) => {
const listState = (state: S): SubstateListData<I, E, LE> =>
_.get(state, subKeyPath, {}) as SubstateListData<I, E, LE>;
const listData = (state: S): ItemMap<I, E> => listState(state).data || {};
const itemState = (state: S, id: string): SubstateItemData<I, E> => listData(state)[id] || {};
const itemLoaded = (state: S, id: string): boolean => !!itemState(state, id).lastUpdated;
return {
listLoading: (state) => !!listState(state).isLoading,
listLoadError: (state) => listState(state).error,
listLoaded: (state) => !!listState(state).lastUpdated,
listLastUpdated: (state) => listState(state).lastUpdated,
list: createMemoizedSelector(listData, (data) =>
listLoading: (state: S): boolean => !!listState(state).isLoading,
listLoadError: (state: S): LE => listState(state).error,
listLoaded: (state: S): boolean => !!listState(state).lastUpdated,
listLastUpdated: (state: S): Date | undefined => listState(state).lastUpdated,
list: createMemoizedSelector(listData, (data: ItemMap<I, E>): I[] =>
_.map(data, (value) => value.data || options.itemDefault),
),
itemsByID: createMemoizedSelector(listData, (data) =>
itemsByID: createMemoizedSelector(listData, (data: ItemMap<I, E>): { [key: string]: I } =>
_.mapValues(data, (value) => value.data || options.itemDefault),
),
itemLoading: (state, id) => !!itemState(state, id).isLoading,
itemLoadError: (state, id) => itemState(state, id).error,
itemLoaded: (state, id) => itemLoaded(state, id),
itemLastUpdated: (state, id) => itemState(state, id).lastUpdated,
itemsLoaded: (state, ids) =>
itemLoading: (state: S, id: string): boolean => !!itemState(state, id).isLoading,
itemLoadError: (state: S, id: string): E | undefined => itemState(state, id).error,
itemLoaded: (state: S, id: string): boolean => itemLoaded(state, id),
itemLastUpdated: (state: S, id: string): Date | undefined => itemState(state, id).lastUpdated,
itemsLoaded: (state: S, ids: string[]): boolean =>
_.reduce(ids, (loaded, id) => loaded && itemLoaded(state, id), true),
item: (state, id) => itemState(state, id).data || options.itemDefault,
item: (state: S, id: string): I => itemState(state, id).data || options.itemDefault,
};
};

@@ -180,96 +180,80 @@ import { Fetch } from "./Fetch";

});
describe("events", () => {
describe("events include request and response information", () => {
ALL_METHODS.forEach((method) => {
it(`${method} request`, async (done) => {
const testBody = { x: 5 };
const testHeaders = { "X-test-header": "value", "X-test-2": "value2" };
const testResponse = new Response("ok");
Fetch.on(Fetch.Event.SUCCESS, (request, response) => {
try {
// This is a bit annoying, but Fetch.Get automatically removes any trailing slashes.
if (method === Fetch.Method.GET) {
expect(request.url).toEqual("http://example.com");
} else {
expect(request.url).toEqual(TEST_URL);
}
expect(request.method).toEqual(method);
// Fetch.Get and Fetch.Delete don't support custom bodies or headers.
if (method !== Fetch.Method.GET && method !== Fetch.Method.DELETE) {
expect(request.body).toEqual(JSON.stringify(testBody));
// We can't directly compare the headers objects because Fetch adds some itself, like Content-Type.
expect(request.headers.get("X-test-header")).toEqual("value");
expect(request.headers.get("X-test-2")).toEqual("value2");
}
expect(response).toEqual(testResponse);
done();
} catch (err) {
done.fail(err);
}
});
fetchMock.once("*", testResponse);
// Fetch.Get and Fetch.Delete don't support custom bodies or headers.
if (method === Fetch.Method.GET || method === Fetch.Method.DELETE) {
await FETCH_FUNCTIONS[method](TEST_URL);
} else {
await FETCH_FUNCTIONS[method](TEST_URL, testBody, testHeaders);
}
});
describe.each(ALL_METHODS)("events for FetchMethod %s", (method) => {
jest.useFakeTimers();
test("events include request and response information", async () => {
const testBody = { x: 5 };
const testHeaders = { "X-test-header": "value", "X-test-2": "value2" };
const testResponse = new Response("ok");
const mock = jest.fn((request, response) => {
// This is a bit annoying, but Fetch.Get automatically removes any trailing slashes.
if (method === Fetch.Method.GET) {
expect(request.url).toEqual("http://example.com");
} else {
expect(request.url).toEqual(TEST_URL);
}
expect(request.method).toEqual(method);
// Fetch.Get and Fetch.Delete don't support custom bodies or headers.
if (method !== Fetch.Method.GET && method !== Fetch.Method.DELETE) {
expect(request.body).toEqual(JSON.stringify(testBody));
// We can't directly compare the headers objects because Fetch adds some itself, like Content-Type.
expect(request.headers.get("X-test-header")).toEqual("value");
expect(request.headers.get("X-test-2")).toEqual("value2");
}
expect(response).toEqual(testResponse);
});
Fetch.on(Fetch.Event.SUCCESS, mock);
fetchMock.once("*", testResponse);
// Fetch.Get and Fetch.Delete don't support custom bodies or headers.
if (method === Fetch.Method.GET || method === Fetch.Method.DELETE) {
await FETCH_FUNCTIONS[method](TEST_URL);
} else {
await FETCH_FUNCTIONS[method](TEST_URL, testBody, testHeaders);
}
jest.runAllTimers();
expect(mock).toBeCalled();
});
describe("fires a SUCCESS event for a successful request", () => {
ALL_METHODS.forEach((method) => {
it(`${method} request`, async (done) => {
Fetch.on(Fetch.Event.SUCCESS, (request, response) => {
done();
});
fetchMock.once("*", "ok");
await FETCH_FUNCTIONS[method](TEST_URL);
});
});
test("fires a SUCCESS event for a successful request", async () => {
const mock = jest.fn();
Fetch.on(Fetch.Event.SUCCESS, mock);
fetchMock.once("*", "ok");
await FETCH_FUNCTIONS[method](TEST_URL);
jest.runAllTimers();
expect(mock).toBeCalled();
});
describe("fires an ERROR event for an unsuccessful request", () => {
ALL_METHODS.forEach((method) => {
it(`${method} request`, async (done) => {
Fetch.on(Fetch.Event.ERROR, (request, response) => {
done();
});
fetchMock.once("*", new Response("error", { status: 401 }));
try {
await FETCH_FUNCTIONS[method](TEST_URL);
} catch (err) {
// do nothing - an error is expected
}
});
});
it("fires an ERROR event for an unsuccessful request", async () => {
expect.assertions(1);
const cb = jest.fn();
Fetch.on(Fetch.Event.ERROR, cb);
fetchMock.once("*", new Response("error", { status: 401 }));
try {
await FETCH_FUNCTIONS[method](TEST_URL);
} catch (err) {
// do nothing - an error is expected
}
jest.runAllTimers();
expect(cb).toBeCalled();
});
describe("allows registering multiple handlers for the same event", () => {
ALL_METHODS.forEach((method) => {
it(`${method} request`, async (done) => {
const eventsFired = {
a: false,
b: false,
c: false,
d: false,
};
const markEventFired = (id) => (request, response) => {
eventsFired[id] = true;
if (eventsFired.a && eventsFired.b && eventsFired.c && eventsFired.d) {
done();
}
};
Fetch.on(Fetch.Event.SUCCESS, markEventFired("a"));
Fetch.on(Fetch.Event.SUCCESS, markEventFired("b"));
Fetch.on(Fetch.Event.ERROR, markEventFired("c"));
Fetch.on(Fetch.Event.ERROR, markEventFired("d"));
fetchMock.once(`${TEST_URL}success`, "ok");
fetchMock.once(`${TEST_URL}error`, new Response("error", { status: 404 }));
await FETCH_FUNCTIONS[method](`${TEST_URL}success`);
try {
await FETCH_FUNCTIONS[method](`${TEST_URL}error`);
} catch (err) {
// do nothing - an error is expected
}
});
});
test("allows registering multiple handlers for the same event", async () => {
jest.useFakeTimers();
expect.assertions(5);
const spy = jest.fn((id: string) => id);
const mockEventHandler = (id: string) => () => spy(id);
Fetch.on(Fetch.Event.SUCCESS, mockEventHandler("a"));
Fetch.on(Fetch.Event.SUCCESS, mockEventHandler("b"));
Fetch.on(Fetch.Event.ERROR, mockEventHandler("c"));
Fetch.on(Fetch.Event.ERROR, mockEventHandler("d"));
fetchMock.once(`${TEST_URL}success`, "ok");
fetchMock.once(`${TEST_URL}error`, new Response("error", { status: 404 }));
await FETCH_FUNCTIONS[method](`${TEST_URL}success`);
try {
await FETCH_FUNCTIONS[method](`${TEST_URL}error`);
} catch (err) {
// do nothing - an error is expected
}
jest.runAllTimers();
expect(spy).toBeCalledTimes(4);
expect(spy).toBeCalledWith("a");
expect(spy).toBeCalledWith("b");
expect(spy).toBeCalledWith("c");
expect(spy).toBeCalledWith("d");
});

@@ -322,14 +306,13 @@ });

});
it("includes global headers in event handlers input", async (done) => {
it("includes global headers in event handlers input", async () => {
jest.useFakeTimers();
const mockEventHandler = jest.fn((request) => {
expect(request.headers.get("x-test")).toEqual("value");
});
Fetch.setGlobalHeader("x-test", "value");
Fetch.on(Fetch.Event.SUCCESS, (request, response) => {
try {
expect(request.headers.get("x-test")).toEqual("value");
done();
} catch (err) {
done.fail(err);
}
});
Fetch.on(Fetch.Event.SUCCESS, mockEventHandler);
fetchMock.once("*", "ok");
await Fetch.post(TEST_URL);
jest.runAllTimers();
expect(mockEventHandler).toBeCalled();
});

@@ -336,0 +319,0 @@ it("only uses global headers for the specified methods", async () => {

@@ -244,3 +244,3 @@ import "isomorphic-fetch";

// eslint-disable-next-line no-redeclare
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace Fetch {

@@ -247,0 +247,0 @@ // The static fields on the Fetch class allow using the enums, but

@@ -43,2 +43,3 @@ import * as express from "express";

// eslint-disable-next-line @typescript-eslint/no-unused-vars
async handler(_req: express.Request, _res: express.Response, _next: express.NextFunction) {

@@ -45,0 +46,0 @@ throw new Error(`handler() not implemented for ${this.constructor.name}`);

{
"name": "clever-frontend-utils",
"version": "1.8.2",
"version": "2.0.0",
"description": "A set of utils for frontend projects at Clever (and potentially elsewhere!)",

@@ -17,3 +17,3 @@ "main": "dist/index.js",

"transform": {
"^.+\\.ts$": "<rootDir>/node_modules/ts-jest/preprocessor.js"
"^.+\\.ts$": "ts-jest"
}

@@ -24,21 +24,21 @@ },

"devDependencies": {
"@eslint/js": "^9.0.0",
"@types/express": "^4.16.1",
"@types/fetch-mock": "^7.3.0",
"@types/jest": "^18.1.1",
"@types/jest": "^27.5.2",
"@types/lodash": "^4.14.134",
"@types/node": "^12.12.24",
"@types/node": "^18.19.31",
"@types/requestidlecallback": "^0.3.1",
"@types/sinon": "^9.0.10",
"@typescript-eslint/eslint-plugin": "^1.9.0",
"@typescript-eslint/parser": "^1.9.0",
"babel-eslint": "^7.1.1",
"eslint": "5.x",
"eslint-config-airbnb": "^6.2.0",
"eslint-plugin-react": "^4.2.3",
"eslint": "^8.57.0",
"eslint-plugin-react": "^7.34.1",
"fetch-mock": "^7.3.3",
"jest": "^18.1.0",
"globals": "^15.0.0",
"jest": "^27.5.1",
"prettier": "2.1.2",
"redux": "^5.0.1",
"sinon": "^8.1.1",
"ts-jest": "^18.0.3",
"typescript": "4.x"
"ts-jest": "^27.1.5",
"typescript": "^4.4.3",
"typescript-eslint": "^7.7.0"
},

@@ -45,0 +45,0 @@ "dependencies": {

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc