Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement: get informed about insertion/removal of properties #25

Closed
rozek opened this issue Jun 22, 2023 · 18 comments · Fixed by #26 or #28
Closed

Enhancement: get informed about insertion/removal of properties #25

rozek opened this issue Jun 22, 2023 · 18 comments · Fixed by #26 or #28

Comments

@rozek
Copy link

rozek commented Jun 22, 2023

First of all: thank you very much for your marvellous work!

It looks brilliant - but lacks a feature I currently need: I want to get informed whenever a new property is added to an observed object.

After looking into your code, I quickly hacked the required changes in a way I can live with - but these changes are neither properly tested nor completely free of side effects (I'm currently using a "helper property" with a literal key rather than a JavaScript symbol - I did that in order not to touch too many parts of your code)

If you are interested, just have a look into my fork - perhaps, you'll come up with a better (or more professional) solution?

@luisherranz
Copy link
Owner

Can you show me an example of the syntax that you are trying to use? Is it something like this?

const store = deepSignal({ a: 1 });

effect(() => {
  for (const property in store) {
    console.log(`${property}: ${store[property]}`);
  }
});

store.b = 2;

@rozek
Copy link
Author

rozek commented Jun 22, 2023

Almost, I would say, but you will have to create a binding to the KeyListSymbol like so

import { deepSignal,KeyListSymbol } from 'deepsignal'

const store = deepSignal({ a: 1 });

effect(() => {
  store[KeyListSymbol]
  for (const property in store) {
    console.log(`${property}: ${store[property]}`);
  }
});

store.b = 2;

Just accessing store[KeyListSymbol] is sufficient, but if you prefer, you may also define a dummy variable or constant

PS: I just tried that example and it turns out that - while the new property is very well recognized - the loop does not mention the new key b although it can be accessed and delivers the expected value...

PS 2: the proxy, however, contains the new property and Object.keys(store) lists b as well (which is what I currently need). Nevertheless, it's very unpleasant that the iterator does not work properly

PS 3: a real non-enumerable JavaScript symbol would be much better, but this is what I was able to hack together this afternoon...

@rozek
Copy link
Author

rozek commented Jun 22, 2023

I've just added some code for the ownKeys Proxy method which eliminates my internal KeyListSymbol . Now Object.keys(store) produces the expected output...

...but the iterator still does not mention b

@rozek
Copy link
Author

rozek commented Jun 22, 2023

according to StackOverflow, trapping for...in seems to be difficult if not impossible...

@rozek
Copy link
Author

rozek commented Jun 22, 2023

well, I've made some more changes which now also deal with key in object and Object.getOwnPropertyDescriptor - they all work fine, but the for ... in loop doesn't.

  console.log(Object.keys(store))

  console.log('a in store','a' in store)
  console.log('b in store','b' in store)
  console.log('c in store','c' in store)
  console.log('* in store',KeyListSymbol in store)

  console.log('a',Object.getOwnPropertyDescriptor(store,'a'))
  console.log('b',Object.getOwnPropertyDescriptor(store,'b'))
  console.log('c',Object.getOwnPropertyDescriptor(store,'c'))
  console.log('*',Object.getOwnPropertyDescriptor(store,KeyListSymbol))

@rozek
Copy link
Author

rozek commented Jun 22, 2023

ok, I got it: I simply had to change the KeyListSymbol property after setting or defining the new property...

I.e., the for...in loop works (in principle), but the effect argument is called too often (twice upon setting store.b

PS: setting a new property internally invokes defineProperty - by updating the KeyListSymbol property in that method only I got rid of the extra effect invocation

@luisherranz luisherranz linked a pull request Jun 23, 2023 that will close this issue
@luisherranz
Copy link
Owner

I made this PR which should add support for things like for...in or Object.keys():

@luisherranz
Copy link
Owner

Released as 1.3.2, it would be great if you could test that it works 🙂

@rozek
Copy link
Author

rozek commented Jun 24, 2023

Good morning!

I found some minutes to test the new version - but, surprisingly, it does NOT work (don't yet know why)

Here is my code

  import { deepSignal } from 'deepsignal'

  const store = deepSignal({ a:1 })

  effect(() => {
console.log('------')
    for (const property in store) {
      console.log(`${property}: ${store[property]}`)
    }
  })

console.log('----')
  store.b = 2;
console.log('----')

  console.log('store',store)

which produces the following output:

------
a: 1
----
----
store Proxy(Object) {a: 1, b: 2}

Thus,

  • the effect is invoked when defined,
  • but not when store.b is set,
  • however, the store finally contains the new property b

@rozek
Copy link
Author

rozek commented Jun 24, 2023

what I found out so far:

  • the code itself works as intended (objToIterable.get(target).value++ is invoked)
  • but that does not recalculate the effect body

However, family is calling and I'll have to postpone further investigations until tomorrow...

@rozek
Copy link
Author

rozek commented Jun 24, 2023

Ok, I got some more minutes and found a solution:

just change the ownKeys trap to

	ownKeys(target: object): (string | symbol)[] {
		if (!objToIterable.has(target)) objToIterable.set(target, signal(0));
		objToIterable.get(target).value = objToIterable.get(target).value
		return Reflect.ownKeys(target);
	},

a simple objToIterable.get(target).value is not sufficient (perhaps the compiler optimizes it away?)

But now everything works as intended

@luisherranz
Copy link
Owner

perhaps the compiler optimizes it away?

It removes the .value for some reason.

ownKeys: function (e) {
  return s.has(e) || s.set(e, t.signal(0)), s.get(e), Reflect.ownKeys(e);
},

What I don't understand is why the tests passed fine. They are supposed to run against the production build as well.

Anyway, thanks for checking it! I'll fix it on Monday.

@luisherranz luisherranz reopened this Jun 24, 2023
@rozek
Copy link
Author

rozek commented Jun 24, 2023

My own fork is now based on your version 1.3.2 and includes both the bug fix mentioned above and the modifications I need for myself (for defineProperty and a property named $)

@luisherranz
Copy link
Owner

Cool, thanks.

@luisherranz luisherranz linked a pull request Jun 26, 2023 that will close this issue
@luisherranz
Copy link
Owner

Hey @rozek, can you test the latest release?

for defineProperty and a property named $

Feel free to open new issues for those to explain the use case of each.

@rozek
Copy link
Author

rozek commented Jun 26, 2023

Thank you vey much for your effort!

I've merged your changes and built again - now I can confirm, that insertion and removal of properties are reported once the "ownKeys" trap has been activated.

@luisherranz
Copy link
Owner

Great, thanks!

@rozek
Copy link
Author

rozek commented Jun 26, 2023

As suggested, I've opened two new issues for the enhancements I need

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants