Giter Club home page Giter Club logo

klona's People

Contributors

afzalsayed96 avatar aulisius avatar d-fischer avatar dependabot[bot] avatar jakebailey avatar lukeed avatar therealparmesh avatar tripodsgames avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

klona's Issues

support Blob.

thx @lukeed , i like this repo.

maybe we should support blob, but it's uneasy for test. if you accept it, i will make a pr.

if (str === '[object Blob]') return x.slice();

Can't clone objects created within iframes.

While testing a tool that uses klona/lite, I realized the library did not clone objects created within iframes.

There's a bigger discussion here about how prototypes are different for similar objects when created in different iframes (or within an iframe versus the top page).

When I create an object within an iframe:
image

this snippet https://github.com/lukeed/klona/blob/master/src/lite.js#L7-L13 is executed, given typeof x.constructor === 'function' and not object when the object is created within an iframe.

Line 10 though checks if the property already exists on the temporary object, which is empty, ignoring the property I'm trying to clone.

I got around the problem by switching from klona/lite to klona/full as the latter doesn't include that constructor check, but I'd love to switch back to shave those extra few bites off of my final bundle

Class instances cloned using `klona/full` do not run field initializers

When cloning instances of classes containing instance fields, their initializers are not run:

import { klona as klonaFull } from 'klona/full';

class Foo {
    foo = ( console.log( 'initializing' ), 0 );
}

const foo1 = new Foo();
const foo2 = klonaFull( foo1 );

The preceding example logs initializing only once, even though I'd expect the log to be printed twice.

This bug is not present in klona/lite and klona, most likely due to the fact that they have a branch detecting constructor functions:

import { klona as klonaLite } from 'klona/lite';
import { klona } from 'klona';

class Foo {
    foo = ( console.log( 'initializing' ), 0 );
}

const foo1 = new Foo();
const foo2 = klonaLite( foo1 );
const foo3 = klona( foo1 );

Here, initializing is correctly logged three times.

This behavior has some implications. For example, TypeScript 5.0 supports the newest decorators proposal, and it transpiles such decorators to similar code. The following example:

function log( target: unknown, ctx: ClassFieldDecoratorContext ): void {
    ctx.addInitializer( () => {
        console.log( 'initializing' );
    } );
}

class Foo {
    @log
    foo = 0;
}

is transpiled by the TypeScript compiler as follows:

function log(target, ctx) {
    ctx.addInitializer(() => {
        console.log("initializing");
    });
}
let Foo = (() => {
    let _instanceExtraInitializers = [];
    let _foo_decorators;
    let _foo_initializers = [];
    return class Foo {
        static {
            _foo_decorators = [log];
            __esDecorate(
                null,
                null,
                _foo_decorators,
                {
                    kind: "field",
                    name: "foo",
                    static: false,
                    private: false,
                    access: {
                        has: (obj) => "foo" in obj,
                        get: (obj) => obj.foo,
                        set: (obj, value) => {
                            obj.foo = value;
                        },
                    },
                },
                _foo_initializers,
                _instanceExtraInitializers
            );
        }
        foo =
            (__runInitializers(this, _instanceExtraInitializers),
            __runInitializers(this, _foo_initializers, 0));
    };
})();

Note that some helpers (such as __esDecorate) were omitted for brevity.

When cloning objects that use decorators using klona/full, initializers added via ctx.addInitializer() are not run.

cant copy jsx.element in object

for example:
const a = {b: 1, c: <span>dom</span>}
const clone = klona(a);
...
error info: var k, tmp, str=Object.prototype.toString.call(x); // Maximum call stack size exceeded

.d.ts fix

I use strict typescript.
When I use klona/full I get the following error:
Could not find a declaration file for module 'klona/full'. 'node_modules/klona/full/index.js' implicitly has an 'any' type. Try `npm install @types/klona` if it exists or add a new declaration (.d.ts) file containing `declare module 'klona/full';`

I fixed it by creating a custom definition file, but it would be nice if it was already defined.

declare module 'klona/full' {
    export function klona<T>(input: T): T;
}

klona is not a function

Hey Luke,

Myself and a coworker have been running into an issue with klona. When we attempt to use klona with CommonJS, such as the following:

const klona = require('klona');
....
const dup = klona(data);

We get the error: klona is not a function.

All other requires seem to be working and I've tried deconstructing the require as well. Any idea what the issue might be?

Suggestion: Don't copy frozen objects

If part of the data being copied is a reference to an object that has been frozen with Object.freeze, it's not really necessary to copy it, as it can't be mutated on either side anyway.

My suggestion is to check Object.isFrozen, and only copy the reference if it returns true,

The point of this, is that it provides a way for the user to opt out of copying parts of the data. Not being able to do this is currently a problem for me, because there are types are not possible to copy using this function, and I don't have a way to disable it, since it's used inside another package (vee-validate)

More Help to use this

HI, im new in the node.js field and dont know how to use this one.
can you upload step by step guide for newbies.
no problem if you feel hesitate by my answer. i know that not an issue request.

Symbol properties are ignored

Symbols can be used as property keys, but they are ignored.

const klona = require('klona');

const mySymbol = Symbol('mySymbol');

const x = { value: 42, [mySymbol]: 'hello' };

console.log(x);
// { value: 42, [Symbol(mySymbol)]: 'hello' }

const y = klona(x);
console.log(y);
// { value: 42 }
// expected: { value: 42, [Symbol(mySymbol)]: 'hello' }

JSDoc + named export

Love the simplicity of the code @lukeed! This ticket is just 2 suggestions, not issues at all:

  1. It would be great to have a named export besides the default, meaning:
export const klona = target => { // ...
export default klona

So the devs can do both of these:

import klona from "klona";
import { klona } from "klona";
  1. If you add JSDocs to Klona and remove the d.ts file, you can use typescriptas a devDependency to generate the d.ts automatically from the docs. So if you do something like this:
/**
 * Tiny & fast deep-clone.
 *
 * @template TargetType
 * @param {TargetType} target Target to be cloned.
 * @returns {TargetType}
 */
const klona = target => { // ...

And then you run tsc (having a tsconfig.json which has allowJs set to true, and declaration set to true), you'll get a d.ts file like this:

export function klona<TargetType>(target: TargetType): TargetType;
export default klona;

So if you add tsc to your build as a last step, you can work in the JS only and forget about the d.ts.

If you want me to lend you a hand with any of this suggestions, I can create an PR for it πŸ˜„

[TypeScript] Incorrectly declared types

The current type definition results in an error when using library with TypeScript:
TypeError: klona.default is not a function

To fix this error, type definitions should be changed to the following:

export = klona;
declare function klona<T>(val: T): T

Just like dayjs does it.
More info about the error.

Ignore circular references

I am trying to tidy our codebase and replace all of our _.deepClone stuff with Klona or with simpler solutions where it fits. Unfortunately, in one case where a deep clone is required, we have a circular ref that we cannot remove at this point. Would it be possible for Klona to ignore a circular ref or add some option how to handle these, so we could ignore it?

I expect something like

const circRef = {...}
const cloned = klona(circRef, {
  onCircularReference(ref) {
    return undefined
  }
})

I am also happy with any package that removes circular references, but I would love to have both in one place.

doesn't work with `Object.create`

Reproducible repo:

// Works with `clone-deep` too
const clone = require('clone');
const { klona } = require('klona/full');

const obj = Object.create({
  method() {
    return 'foo';
  },
});

console.log(clone(obj).method());
console.log(klona(obj).method());

Classes named "Object"

I noticed the usage of Object.prototype.toString and it seemed really interesting, but custom classes can be mistaken as other objects inadvertently through this method.

> Object.prototype.toString.call(new (class Object {}))
'[object Object]'

Klona (JSON) preserves undefined

I am not sure if this is wanted behavior, but if there is an array that has undefined as an element it stays as undefined.
JSON parse/stringify turns it into a null.

The same happens if a property is undefined, but with JSON it is completely stripped away.

In the README it says that undefined is only from supported from the lite, and JSON does not support undefined anyway.

Reproduction

I am assuming the way klona/json is intended to be used is we know in advance that our objects are JSON compatible in the first place, so this might be working as intended (but Map/Set are handled the same way as the JSON methods).

Circular reference stacktrace very painful for development

When running across an accidental circular reference (aka uncovering a bug) buried deep in a stack, calling klona throws a Maximum call stack size exceeded but with the stacktrace only containing the last n lines from within klona, meaning that there's no way to see what the call stack is that called klona. This is a frustrating developer experience.

Note: this is different than #37 which wants a way to do something. I only want a useful stacktrace so I can track down and handle the error.

An example

Imagine that you have a bug deep in your API stack that creates a circular reference:

// circular.js
const dog = { type: 'dog' }
const person = { type: 'person' }
dog.owner = person
person.pet = dog

Making a naive copy of person e.g. JSON.parse(JSON.stringify(person)) will of course throw a circular reference exception, notably with the stacktrace intact so you can see where the clone was created and the error was thrown:

console.log(JSON.parse(JSON.stringify(person)))
                                     ^

TypeError: Converting circular structure to JSON
[...snip...]
    at file:///circular.js:5:38
[...snip...]

However, when cloning person with any of the klona importables, e.g.:

import { klona } from 'klona/json'
// ...
console.log(klona(person))

The stacktrace throws a RangeError with the stacktrace entirely filled with these:

RangeError: Maximum call stack size exceeded
    at Object.toString (<anonymous>)
    at klona (/my-app/node_modules/klona/json/index.mjs:10:32)
    at klona (/my-app/node_modules/klona/json/index.mjs:21:56)
    at klona (/my-app/node_modules/klona/json/index.mjs:21:56)
    at klona (/my-app/node_modules/klona/json/index.mjs:21:56)
    at klona (/my-app/node_modules/klona/json/index.mjs:21:56)
    at klona (/my-app/node_modules/klona/json/index.mjs:6:66)
    at klona (/my-app/node_modules/klona/json/index.mjs:21:56)
    at klona (/my-app/node_modules/klona/json/index.mjs:21:56)
    at klona (/my-app/node_modules/klona/json/index.mjs:21:56)

Workaround

My current workaround in any of my apps is to basically wrap klona with a try/catch and re-throw a new error:

// clone.js
import { klona } from 'klona/json'
export const clone = obj => {
  try {
    return klona(obj)
  } catch(error) {
    throw new Error('Got an error in klona: ' + error.message)
  }
}

// circular.js
import { clone } from './clone.js'
const dog = { type: 'dog' }
const person = { type: 'person' }
dog.owner = person
person.pet = dog
console.log(clone(person))

which then throws this, which has the useful bits of the stacktrace in the error:

file:///clone.js:6
		throw new Error('Got an error in klona: ' + error.message)
		      ^

Error: Got an error in klona: Maximum call stack size exceeded
    at clone (file:///clone.js:6:9)
    at file:///circular.js:8:13

Issue

Of course, this is a really annoying developer experience, since I have to choose between either try/catch-ing for every klona call (with the wrapper) or completely losing the ability to find where the klona call is happening that has a circular reference.

For the most part I've been able to track down bugs without the wrapper, knowing that I'm working in a particular flow so the problem should be around ~here, but as the application has grown in complexity it's more frustrating to run into this issue, which is why I made the wrapper and came here.

Solution?

Probably the best solution would be to split the exported function and the recursed function, so the try catch happens only on the one exported function instead of every recursed call:

function _klona(val) {
	// the existing code, except recursion calls _klona
}
export function klona(val) {
	try {
		return _klona(val)
	} catch (error) {
		if (error.name === 'RangeError') throw new Error('Circular reference detected')
		else throw error
	}
}

I'm okay using that little wrapper in my apps, but it would be a nicer developer experience to have it built in.

Question

If you like that solution, I can make the switch and add tests etc. if you are open to that.

If not just say so here and feel free to close this issue.

(In part I'm filing an issue so that when this comes up again I'll see this and not keep bugging you about it. πŸ˜…)

Clona fails to copy an object, when it contains a `DateTime` from Luxon

Reproduction code (tested with both Luxon v1.25 and v2.0.2):

import { klona } from 'klona/lite';
import { DateTime } from 'luxon';

const data = {
  date: DateTime.fromISO('2021-10-01'),
};

const copy = klona(data);

Expected result:
copy is a new object, caontaining the same date as the original. Since DateTime is immutable, it doesn't need to copy it.

Actual result:
Exception:

luxon.js?1315:6204 Uncaught TypeError: Cannot read properties of undefined (reading 'zone')
    at new DateTime (luxon.js?1315:6204)
    at klona (index.mjs?ba1d:8)
    at klona (index.mjs?ba1d:25)
    at eval (main.ts?cd49:30)

Problem scope:
This is a problem for us, when using vee-validate, with is using klona internally, and we are using dates in our custom form components. There's no way to opt out of this behavior, which is just crashing our page after upgrading vee-validate.

Suggestion:
Obviously, it's not in the scope of this package to be compatible with everything that's out there. Although, it is questionable to call constructors of unknown classes, and then try to copy field by field. This will probably fail for a large number of classes. There should be some way to opt-out of the copying, and make klona copy by reference, which would be OK in this case. Maybe it would be possible to mark objects with a certain property, to make then "non-copyable" by klona. Even better, mark the constructor of the class in question, so that all objects of that class are copied by reference.

klona/full seems discarding prototype properties from class

const clone = require('clone');
const {klona} = require('klona');
const {klona: klonaFull} = require('klona/full');

function MyPlugin() {
}

MyPlugin.prototype = {
    install: function() {
        console.log('install');
    }
}

const options = {
    plugin: new MyPlugin(),
};

console.log('oringinal', options.plugin.install);
console.log('clone', clone(options).plugin.install);
console.log('klona', klona(options).plugin.install);
console.log('klona/full', klonaFull(options).plugin.install);

Output:

oringinal [Function: install]
clone [Function: install]
klona [Function: install]
klona/full undefined

Wondering its a bug or is by design, this issue makes less-loader lost less plugin methods.

Additional types

Great job again, @lukeed!

By the way, do you plan to extend with additional common types, like Date, Buffer, Map, Set?

Multiple Modes

As with others of my modules, I think a "choose your path" option for this module would be great.

"default"

By default – aka, the regular import – a middle of the road approach should be offered that covers the majority of cases. As of today, this would be the current klona module, supporting Objects, Arrays, Dates, RegExps, Maps, Sets, all TypedArray variants (includes Buffer), and primitives, of course.

Using this mode will continue to look like this:

import klona from 'klona';

"full"

There will definitely be a "full" mode (name TBD) that does everything "default" does, but with these added features. I originally was going to publish klona with these features to start, but removed them since they don't fit the 90% use case IMO.

Of course, this mode will be a bit larger (~400 bytes) and significantly slower than the current klona – however it'll still be faster than most contenders of the current benchmark & none of them offer these extra features.

Again, this is opt-in behavior & I'm a fan of being explicit about what you need and where you need it. Using this mode will look like this:

import klona from 'klona/full'; // name TBD

"lite"

There will also be "lite" or "json" mode (name TBD) for handling simpler cases. I actually think the 90% use case doesn't bother with cloning RegExps, TypedArrays, or even Maps & Sets, but "default" included them to be safe.

The lite/json mode, as the name suggests, will handle far fewer cases than the "default" and "full" counterparts. Because of this, this mode will be ~200 bytes and the fastest of the three.

I'm still debating if this mode should handle RegExp and Date. If so, then the name would have to be "lite" – otherwise only valid JSON datatypes will remain, thus making "json" a clear & obvious choice for dealing with JSON data objects.

As with "full", using this mode is an opt-in behavior and should be obvious when you've made that choice. Using this mode will look like:

import klona from 'klona/lite';
// ~ OR ~
import klona from 'klona/json';

Please leave any comments or concerns or naming suggestions that you may have.
This is planned for a minor/feature release since nothing changes to the default mode.

Thanks!

#8 (comment)

thx @lukeed , i like this repo.

maybe we should support blob, but it's uneasy for test. if you accept it, i will make a pr.

if (str === '[object Blob]') return x.slice();

#8

Originally posted by @tooss367 in #8 (comment)

add deepmerge to benchmark

I added deepmerge and run benchmark and i got this results

Validation: 
  ✘ JSON.stringify (FAILED @ "initial copy")
  ✘ fast-clone (FAILED @ "initial copy")
  βœ” lodash
  βœ” clone-deep
  ✘ deep-copy (FAILED @ "initial copy")
  βœ” depcopy
  βœ” klona
  ✘ deepmerge (FAILED @ "initial copy")

Benchmark:
  JSON.stringify   x 20,873 ops/sec Β±3.54% (80 runs sampled)
  fast-clone       x 8,542 ops/sec Β±15.12% (61 runs sampled)
  lodash           x 21,840 ops/sec Β±7.48% (80 runs sampled)
  clone-deep       x 43,664 ops/sec Β±6.99% (75 runs sampled)
  deep-copy        x 65,898 ops/sec Β±5.09% (81 runs sampled)
  depcopy          x 15,026 ops/sec Β±2.96% (83 runs sampled)
  klona            x 149,326 ops/sec Β±3.57% (84 runs sampled)
  deepmerge        x 18,183,850 ops/sec Β±3.00% (80 runs sampled)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.