Closed rubensworks closed 5 years ago
Good stuff, great as starting point for discussions.
Adding the first of multiple values:
await user.friends.add(user.friends.friends)
I don't find this intuitive; to me, the above syntax should be for adding all friends. For adding one friend (not the first BTW, no ordering), I'd say:
await user.friends.add(user.friends.friends.random)
So I'd be against for await
and addAll
.
Deleting the first of a list of values:
await user.friends.delete(user.friends.friends)
Deleting a list of values in bulk:
await user.friends.deleteAll(user.friends.friends)
I find the duplication confusing. I would propose
await user.friends.random().delete()
await user.friends.delete() // deletes all
Too bad that we cannot do
await delete user.friends
Because the delete trap is supposed to return a Boolean. That said, we might be able to do it if that Boolean also is a thenable! (untested)
Updating a single value:
await user.name.set('Ruben')
Can we also wire up the following alternative?
user.name = 'Ruben'
Also, there is no such cases as "single value", because anything might have multiple.
How about
user.name.set('Ruben') // removes all names and add one 'Ruben'
user.name.replace('ruben', 'Ruben') // remove all matching names and add one 'Ruben'
Adding the first of multiple values: await user.friends.add(user.friends.friends)
I don't find this intuitive; to me, the above syntax should be for adding all friends. For adding one >friend (not the first BTW, no ordering), I'd say: await user.friends.add(user.friends.friends.random)
Sure, that makes sense as well.
On the other hand, this may be confusing, as await user.friends.friends
returns only a single value. Perhaps await user.friends.friends.random
or await user.friends.friends.random()
should be a thing then as well?
So I'd be against for await and addAll.
Agreed. I was just writing out the possibilities :-)
What might be useful though, is to implement addAll like this:
await user.friends.addAll([ 'uri-a', 'uri-b', ... ])
The advantage of this is that a single bulk SPARQL query can be created, instead of having to send one for each addition separately.
await user.friends.random().delete() await user.friends.delete() // deletes all
👍
Too bad that we cannot do await delete user.friends Because the delete trap is supposed to return a Boolean. That said, we might be able to do it if that Boolean also is a thenable! (untested)
Hmm, maybe, I'll look into that.
Can we also wire up the following alternative? user.name = 'Ruben'
We have the same problem here as with delete
.
The return value of proxy's set
should be a boolean value.
Since a regular assignment returns its assigned value, we won't be able to promisify this.
We could definitely support this, but any rejections would be uncaught.
(A babel plugin might be possible here as well though)
How about user.name.set('Ruben') // removes all names and add one 'Ruben' user.name.replace('ruben', 'Ruben') // remove all matching names and add one 'Ruben'
👍
On the other hand, this may be confusing, as
await user.friends.friends
returns only a single value.
Yes, but there's no await
in front of it when adding, so I think we're good.
Perhaps
await user.friends.friends.random
orawait user.friends.friends.random()
should be a thing then as well?
Definitely; I suggest the former.
The difference being that a for await
over random
would also only give at most one element.
What might be useful though, is to implement addAll like this:
await user.friends.addAll([ 'uri-a', 'uri-b', ... ])
Possibly, or we could just splat it:
await user.friends.add(...array)
Can we also wire up the following alternative? user.name = 'Ruben'
We have the same problem here as with
delete
. The return value of proxy'sset
should be a boolean value. Since a regular assignment returns its assigned value, we won't be able to promisify this.We could definitely support this, but any rejections would be uncaught.
You just gave me a really interesting idea.
Recall how Node has this thing about uncaught Promise rejections.
What if we similarly allow such sync calls, which indeed have not completed yet, but give the option to manually sync? Example:
// Do data things
data.user.name = 'Ruben';
data.user.brother.name = 'Niels';
data.user.sister.name = 'Muriel';
delete data.user.daughter;
// Wait until all of the above is done
await data.pending;
Above, data.pending
is a Promise to an array of results for each of the currently pending operations.
You're free to await
or not await
it, knowing that if you don't, you might miss completion and/or rejection (and rejections would become global rejection errors).
Additionally, this gives us the possibility of bundling setters internally, and writing them all at once.
Note that, upon accessing data.pending
, all of the currently pending promises are returned and then forgotten. For example:
data.user.name = 'Ruben';
data.user.brother.name = 'Niels';
data.user.sister.name = 'Muriel';
const p1 = data.pending;
const p2 = data.pending;
console.log(await p2); // empty array
console.log(await p1); // three items
This allows explicit scoping of pending promises.
What if we similarly allow such sync calls, which indeed have not completed yet, but give the option to manually sync?
That's an interesting idea to enable batching. The await data.pending
does feel a bit like it exposes too much of the internals IMO. We can definitely use this internally, but I think it would be better if the user shouldn't know about this.
What if we enable 2 modes of mutation operations?
// Do data things non-batched
data.user.name = 'Ruben';
data.user.brother.name = 'Niels';
data.user.sister.name = 'Muriel';
delete data.user.daughter;
// Optionally wait until all of the above is done, but we should discourage this?
await data.pending;
// Do data things batched
data.batch((batch) -> {
batch.user.name = 'Ruben';
batch.user.brother.name = 'Niels';
batch.user.sister.name = 'Muriel';
delete batch.user.daughter;
});
or:
// Do data things batched
const batch = data.batch();
batch.user.name = 'Ruben';
batch.user.brother.name = 'Niels';
batch.user.sister.name = 'Muriel';
delete batch.user.daughter;
The second option might be a bit more complex to implement, but should be doable using weak references on batch
with WeakMap.
upon accessing data.pending, all of the currently pending promises are returned and then forgotten.
Are you sure we want this?
This would break use cases where the pending promise should be used to block multiple things.
I don't think there's a problem with data.pending
just always returning a Promise.all
over all pending promises at the moment is was called. If the operations would already be finished at the time the promise is await
-ed, the promise will already be resolved, so there's shouldn't be a problem there.
I've written everything down in a separate gist that I will update once we decide new things: https://gist.github.com/rubensworks/1123742b048bd2e82624c713a024dcab
I think we agree on everything already, except for perhaps the syntactical sugar and batching. But those build on top of the previous ones, so I can already go ahead an start implementing those.
But those build on top of the previous ones, so I can already go ahead an start implementing those.
Exactly!
I very much like your suggestion with the batch
function wrapper; it resembles the React state update pattern, and also gives the possibility to do transactions etc. Let's go that way once the core is there.
Too bad that we cannot do await delete user.friends Because the delete trap is supposed to return a Boolean. That said, we might be able to do it if that Boolean also is a thenable! (untested)
Just did some testing, and the set and delete traps unfortunately don't allow you to customize the return value of the operation. So we can probably only use them in the context of batching.
Closing this issue, since write support has been fully implemented in #6.
Created a separate issue for batched operations: #8.
Stumbled upon this by accident. Would be great to have it in the official documentation
Ok, I see there is https://github.com/RubenVerborgh/LDflex/issues/10 already :)
This is a proposal for adding write-support to LDflex.
It requires three new handles:
add
andaddAll
.delete
anddeleteAll
.update
andupdateAll
.Addition
This requires a straightforward handler that intercepts the
add
andaddAll
functions that take one mandatory argument.Adding a single value:
SPARQL:
Adding the first of multiple values:
SPARQL:
Adding multiple values:
SPARQL:
Adding multiple values in bulk:
SPARQL:
Note: We can not trap
=
via the proxy (set
), as this requires a synchronous operation, and you can not play around with return values.Deletion
This requires a handler that intercepts the
delete
anddeleteAll
functions that take one optional argument.Deleting a single value:
SPARQL:
Deleting the first of a list of values:
SPARQL:
(maybe this can be done with a
DELETE WHERE
?)Deleting a list of values:
SPARQL:
Deleting a list of values in bulk:
SPARQL:
Deleting first or all possible values:
Internally, this corresponds to
await user.friends.delete(user.friends)
orawait user.friends.deleteAll(user.friends)
, because SPARQL DELETE does not allow blank nodes or variablesNote: We can not trap the
delete
operator via the proxy (deleteProperty
), as this requires a synchronous operation, and you can not play around with return values.Update
This corresponds to a delete followed by an insert. This is merely convenience functionality that combines deletion and addition (optimizable with a combined SPARQL query: https://www.w3.org/TR/2013/REC-sparql11-update-20130321/#deleteInsert). This requires a handler for
set
andsetAll
that take one mandatory argument.Updating a single value:
Translation:
Updating the first of multiple value:
Translation:
Updating multiple values:
SPARQL:
Updating multiple values in bulk:
SPARQL: