New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

patched-undo-peasy

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

patched-undo-peasy - npm Package Compare versions

Comparing version 0.1.5 to 0.1.6

2

package.json
{
"name": "patched-undo-peasy",
"version": "0.1.5",
"version": "0.1.6",
"main": "src/UndoRedoApi.ts",

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

import "chai/register-should";
import _ from "lodash";
import produce from "immer";
import { clonePlain, filterCopy, findGetters, removeDeep } from "../Utils";
import { assert } from "chai";
import { findGetters, removeDeep } from "../Utils";
test("clonePlain", () => {
const src = {
foo: "bar",
n: 2,
a: [1, 2, 3, () => 1],
zoo: {
get bar() {
return 3;
},
zap: "zo",
},
zin: {
get fum() {
return 4;
},
},
};
const expected = {
foo: "bar",
n: 2,
a: [1, 2, 3],
zoo: {
zap: "zo",
},
zin: {},
};
const copy = clonePlain(src);
copy.should.deep.equal(expected);
("bar" in copy.zoo).should.equal(false);
});
function filterCopyData() {
const src = {
foo: "ignored",
pickMeToo: "zip",
bar: {
baz: undefined,
yo: {
bah: undefined,
pickMe: "zap",
},
},
};
const dest = {
bar: {
},
};
return {src, dest};
}
test("filterCopy", () => {
const {src, dest} = filterCopyData();
filterCopy(src, dest, (key:string) => key.startsWith("pick"));
(dest as any).pickMeToo.should.equal("zip");
(dest as any).bar.yo.pickMe.should.equal("zap");
});
test("immer filterCopy", () => {
const {src, dest} = filterCopyData();
const result = produce(dest, draft => {
filterCopy(src, draft, (key:string) => key.startsWith("pick"));
});
(result as any).pickMeToo.should.equal("zip");
(result as any).bar.yo.pickMe.should.equal("zap");
assert((dest as any).pickMeToo === undefined);
});
test("findGetters", () => {

@@ -79,0 +7,0 @@ const src = {

@@ -6,4 +6,2 @@ import { AnyAction, Dispatch, Middleware, MiddlewareAPI } from "redux";

TODO
* separate undoredo into its own project
* clean up for review
* add option for max number undo elements

@@ -13,3 +11,6 @@ */

export interface UndoRedoConfig {
/** function called to identify actions that should not be saved in undo history */
noSaveActions?: ActionFilter;
/** function called to identify keys that should not be saved in undo history */
noSaveKeys?: KeyPathFilter;

@@ -26,2 +27,16 @@ }

/** @returns redux middleware to support undo/redo actions.
*
* The middleware does two things:
*
* 1) for undo/redo actions, the middlware attaches some information to the action.
* It attaches the 'raw' state object. easy peasy normally sends only an immer
* proxy of the raw state, and the proxy obscures the difference between computed
* and regular properties.
* At it attaches any user provided noSaveKeys filter.
*
* 2) for normal actions, the middeware dispatches an additional undoSave action to
* follow the original action. The reducer for the undoSave action will save the state
* in undo history.
*/
export function undoRedo(config: UndoRedoConfig = {}): Middleware {

@@ -28,0 +43,0 @@ const { noSaveActions, noSaveKeys } = replaceUndefined(config, undoDefaults);

@@ -28,11 +28,2 @@ import _ from "lodash";

interface UndoParams {
noSaveKeys: KeyPathFilter;
state: WithUndo;
}
interface WithUndoHistory {
undoHistory: UndoHistory;
}
export type ModelWithUndo<T> = {

@@ -50,2 +41,17 @@ [P in keyof T]: T[P];

const undoModel: UndoHistory = { undo: [], redo: [] };
/** Used internally, to pass params and raw state from middleware config to action reducers.
* Users of the actions do _not_ need to pass these parameters, they are attached by the middleware.
*/
interface UndoParams {
noSaveKeys: KeyPathFilter;
state: WithUndo;
}
interface WithUndoHistory {
undoHistory: UndoHistory;
}
const undoSave = action<WithUndo, UndoParams>((draftState, params) => {

@@ -63,2 +69,4 @@ const history = draftState.undoHistory;

if (!history.computeds) {
// consider this initialization only happens once, is there an init hook we could use instead?
// LATER consider, what if the model is hot-reloaded?
history.computeds = findGetters(params.state);

@@ -68,2 +76,3 @@ }

// remove keys that shouldn't be saved in undo history (computeds, user filtered, and history state)
const filteredCopy: AnyObject = removeDeep(draftState, (_value, key, path) => {

@@ -76,4 +85,4 @@ const fullPath = path.concat([key]);

});
delete filteredCopy["undoHistory"];
draftState.undoHistory.current = filteredCopy;

@@ -115,2 +124,1 @@ }

export const undoModel: UndoHistory = { undo: [], redo: [] };

@@ -49,26 +49,2 @@ import _ from "lodash";

/**
* @return a deep copy of an object or value, preserving only 'plain' serializable
* properties and values.
*
* Properties whose values are numbers, strings, booleans, Dates, undefined, null,
* arrays or objects are copied.
*
* Getter properties and properties with function values are dropped. */
export function clonePlain(src: any): any {
if (isPrimitive(src)) {
return src;
} else if (_.isArray(src)) {
return src.filter(isPlain).map(clonePlain);
} else if (_.isObject(src) && !_.isFunction(src)) {
const plain = Object.entries(src).filter(
([key, value]) => !isGetter(src, key) && isPlain(value)
);
const copy = plain.map(([key, value]) => [key, clonePlain(value)]);
return Object.fromEntries(copy);
} else {
return undefined;
}
}
export interface AnyObject {

@@ -93,66 +69,3 @@ [key: string]: any;

/** Copy target properties from a src to a dest object, mutating the destination object.
* Target properties in child objects of the src object.
* Parent objects of target properties are created in the dest object as necessary.
*
* Property getters are not copied.
*
* @param keyFilter function to identify property keys to copy.
*/
export function filterCopy(
src: AnyObject,
dest: AnyObject,
keyFilter: (key: string, path: string[]) => boolean
): void {
copyRecurse(src, []);
function copyRecurse(from: AnyObject, path: string[]): void {
const srcKeys = Object.keys(from);
const copyKeys = srcKeys.filter(
(key) =>
keyFilter(key, path) &&
Object.getOwnPropertyDescriptor(from, key)?.get === undefined
);
copyKeys.forEach((key) => pathSet(path.concat([key]), dest, from[key]));
const recurseKeys = Object.keys(from).filter(
(key) => !keyFilter(key, path) && _.isPlainObject(from[key])
);
recurseKeys.forEach((key) => copyRecurse(from[key], path.concat([key])));
}
}
function pathSet(fullPath: string[], destRoot: AnyObject, value: any): void {
const path = fullPath.slice(0, fullPath.length - 1);
const key = _.last(fullPath)!;
let destChild = destRoot;
for (const pathElem of path) {
if (destChild[pathElem] === undefined) {
destChild[pathElem] = {};
}
destChild = destChild[pathElem];
}
destChild[key] = value;
}
function isPrimitive(value: any): boolean {
return (
value === undefined ||
value === null ||
_.isNumber(value) ||
_.isString(value) ||
_.isBoolean(value) ||
_.isDate(value)
);
}
function isPlain(value: any): boolean {
return (
isPrimitive(value) || _.isArray(value) || (_.isObject(value) && !_.isFunction(value))
);
}
function isGetter(src: {}, key: string): boolean {

@@ -159,0 +72,0 @@ const desc = Object.getOwnPropertyDescriptor(src, key);

SocketSocket SOC 2 Logo

Product

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

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc