jazz icon indicating copy to clipboard operation
jazz copied to clipboard

feat: added asPlainData(depth?) method for CoMap and CoList

Open pax-k opened this issue 1 year ago • 4 comments

pax-k avatar Aug 12 '24 16:08 pax-k

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
jazz-chat ❌ Failed (Inspect) Aug 14, 2024 3:36pm
jazz-homepage ❌ Failed (Inspect) Aug 14, 2024 3:36pm
jazz-pets ❌ Failed (Inspect) Aug 14, 2024 3:36pm
jazz-todo ❌ Failed (Inspect) Aug 14, 2024 3:36pm
1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
jazz-inspector ⬜️ Skipped (Inspect) Aug 14, 2024 3:36pm

vercel[bot] avatar Aug 12 '24 16:08 vercel[bot]

Looks great so far!

Please could you create a sync version asPlainData() based on whatever is available and async asPlainDataLoaded(depth)?

aeplay avatar Aug 12 '24 16:08 aeplay

How should I handle circular references?

For example, here is an example without circular references (taken from the tests):

        const innerMap = SimpleMap.create(
            {
                name: "Bob",
                age: 25,
                isActive: false,
            },
            { owner: me }
        );
        const nestedMap = NestedMap.create(
            {
                info: innerMap,
                data: "Some data",
            },
            { owner: me }
        );

        const plainData = nestedMap.asPlainData();
        expect(plainData).toEqual({
            info: {
                name: "Bob",
                age: 25,
                isActive: false,
            },
            data: "Some data",
        });

But consider this schema:

export class PasswordItem extends CoMap {
  name = co.string;
  username = co.optional.string;
  username_input_selector = co.optional.string;
  password = co.string;
  password_input_selector = co.optional.string;
  uri = co.optional.string;
  folder = co.ref(Folder);
  deleted = co.boolean;
}

export class PasswordList extends CoList.Of(co.ref(PasswordItem)) {}

export class Folder extends CoMap {
  name = co.string;
  items = co.ref(PasswordList);
}

export class FolderList extends CoList.Of(co.ref(Folder)) {}

export class PasswordManagerAccountRoot extends CoMap {
  folders = co.ref(FolderList);
}

export class PasswordManagerAccount extends Account {
  profile = co.ref(Profile);
  root = co.ref(PasswordManagerAccountRoot);

We're using co.ref(Folder) for both FolderList and PasswordItem.folder.

Now, if I want to create a plain object:

const { me, logOut } = useAccount();
console.log(me.root?.asPlainData())

We get the error:

RangeError: Maximum call stack size exceeded
    at Object.ownKeys (coMap.ts:651:12)
    at Function.keys (<anonymous>)
    at Proxy.asPlainDataSync (coMap.ts:543:34)
    at Proxy.asPlainData (coMap.ts:501:25)
    at Proxy.asPlainDataSync (coList.ts:480:39)
    at Proxy.asPlainData (coList.ts:462:25)
    at Proxy.asPlainDataSync (coMap.ts:551:51)
    at Proxy.asPlainData (coMap.ts:501:25)
    at Proxy.asPlainDataSync (coMap.ts:548:51)
    at Proxy.asPlainData (coMap.ts:501:25)

One way to fix this is to use a WeakMap to keep track of objects that have already been processed:

if (seen.has(this)) {
   return { _circular: this.id } // or return seen.get(this) to get an infinitely nested object
}

And then console.log(me.root?.asPlainData()) looks like this:

{
    "folders": [
        {
            "name": "Default",
            "items": [
                {
                    "name": "123",
                    "username": "[email protected]",
                    "password": "password123",
                    "uri": "https://gmail.com",
                    "folder": {
                        "_circular": "co_zWA4FKAjAUbNc5HT4YiH4d1EE5s"
                    },
                    "deleted": false
                },
                {
                    "name": "Facebook",
                    "username": "[email protected]",
                    "password": "facebookpass",
                    "uri": "https://facebook.com",
                    "folder": {
                        "_circular": "co_zWA4FKAjAUbNc5HT4YiH4d1EE5s"
                    },
                    "deleted": false
                }
            ]
        }
    ]
}

I'm not sure how it's best to handle this.

pax-k avatar Aug 13 '24 15:08 pax-k

Please may you adapt the implementation as suggested by you - so for circular references asPlainData returns plain data that is itself circularly references itself?

aeplay avatar Aug 13 '24 15:08 aeplay

Closing this for now - we'll likely use it as a basis for a bigger API change in the future

aeplay avatar Sep 18 '24 16:09 aeplay