-
-
Notifications
You must be signed in to change notification settings - Fork 6
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
Create a new Evt after applying filter/operator #57
Comments
Hold on |
I've found subscribe: function () {
console.log(`handlers: ${pingEvent.getHandlers().length}`)
const event = pingEvent.state
? pingEvent
: pingEvent.toStateless();
return event.iter(event => event === 'some filter');
} the following shows increased handler count every new subscription. so the Edit: okay it is the. I was getting the following error
Which is why I implemented the |
Hi @barthuijgen, You are raising valid points. Evt needs something like a bidirectional pipes. I offer you two solutions: The naïve one (that work as long as Photos only subscribe at most one handler at the time): import { Evt } from "evt";
const evtPing = Evt.create<string | null>(null);
const evtStatelessPing = (()=>{
const ctx= Evt.newCtx();
const evtStatelessPing = evtPing
.toStateless(ctx)
.pipe(ping => ping === null ? null : [ping]);
evtStatelessPing.evtDetach.attachOnce(()=> ctx.done());
if( evtPing.state !== null ){
evtStatelessPing.postAsyncOnceHandled(evtPing.state);
}
return evtStatelessPing;
})() The solution without making any assumption whatsoever. import { Evt } from "evt";
const evtStatelessPing = (() => {
const evtStatelessPing = Evt.create<string>();
const ctx = Evt.newCtx();
evtStatelessPing.evtAttach.attach(handler => {
if (ctx.getHandlers().length === 0) {
evtPing.$attach(
ping => ping === null ? null : [ping],
ctx,
ping => evtStatelessPing.post(ping)
);
return;
}
if (evtPing.state === null) {
return;
}
const op = evtStatelessPing.getInvocableOp(handler.op);
const result = op(evtPing.state, () => { })
if (result === null) {
return;
}
handler.callback?.(result[0]);
});
evtStatelessPing.evtDetach.attach(
() => evtStatelessPing.getHandlers().length === 0,
() => ctx.done()
);
return evtStatelessPing;
})(); |
The first solution assumes Pothos will only suscribe at most one handler to the Evt. The second make no assumption. The code can be simplified a lot if you don't care about posting the initial ping state when the subscription is made by Pothos. |
This is the code if you don't need to post the initial state: const evtStatelessPing = (() => {
const evtStatelessPing = Evt.create<string>();
const ctx = Evt.newCtx();
evtStatelessPing.evtAttach.attach(
() => ctx.getHandlers().length === 0,
() => evtPing
.toStateless(ctx)
.$attach(
ping => ping === null ? null : [ping],
ping => evtStatelessPing.post(ping)
)
);
evtStatelessPing.evtDetach.attach(
() => evtStatelessPing.getHandlers().length === 0,
() => ctx.done()
);
return evtStatelessPing;
})(); |
Let me know if you need further explanation, I'm happy to answer. |
Wow thank you so much for such a thourough answer! I've been a big fan ever since I came across Evt but some of these concepts a still a bit beyond me, slowly learning more about them. Pothos can indeed call the It does seem overly complex for what seems like a simple use case, I hope performance is still fine. I could allow graphql to return Anyway this gives me options to experiment and find the best fit for me, thank you so much! |
Thanks for the kind words, they mean a lot!
I completely agree. It's unfortunate that EVT isn't more accommodating for this valid use case. I've added two items to my roadmap to address this:
I'll takle this alongside the planned integration with
I am quite confident that performance will be satisfactory. There will be a small overhead when attaching and detaching, but I believe this won't be an operation performed intensively. Additionally, the processes involved are not resource-intensive. One final suggestion: consider abstracting this logic into a utility function to streamline the implementation. import { Evt, type StatefulReadonlyEvt } from "evt";
/** Return a copy of the source Evt. While there isn't anything attached to the returned copy,
* the source evt won't be observed.
* When attaching an handler to the copied Evt it will imediately be invoked with the current value
* of the source evt unless this value is null.
*/
function weakPipe<T>(evt: StatefulReadonlyEvt<T | null> ): Evt<T>{
const evtOut = Evt.create<T>();
const ctx = Evt.newCtx();
const noSideEffect = () => { };
evtOut.evtAttach.attach(handler => {
if (ctx.getHandlers().length === 0) {
evt.$attach(
ping => ping === null ? null : [ping],
ctx,
ping => evtOut.post(ping)
);
return;
}
if (evt.state === null) {
return;
}
const op = evtOut.getInvocableOp(handler.op);
const result = op(evt.state, noSideEffect)
if (result === null) {
return;
}
handler.callback?.(result[0]);
});
evtOut.evtDetach.attach(
() => evtOut.getHandlers().length === 0,
() => ctx.done()
);
return evtOut;
}
const evtPing = Evt.create<string | null>(null);
const evtPingCopy= weakPipe(evtPing); |
That's great, good luck on working on those improvements, looking forward to any progress. That ts-pattern lib looks pretty neat as well, will give a try sometime. I think there is a slight issue with the It works fine just re-doing the |
@barthuijgen I'm verry sorry. Here is the updated code: import { Evt, type StatefulReadonlyEvt } from "evt";
/** Return a copy of the source Evt. While there isn't anything attached to the returned copy,
* the source evt won't be observed.
* When attaching an handler to the copied Evt it will imediately be invoked with the current value
* of the source evt unless this value is null.
*/
function weakPipe<T>(evt: StatefulReadonlyEvt<T | null> ): Evt<T>{
const evtOut = Evt.create<T>();
let ctx = Evt.newCtx();
const noSideEffect = () => { };
evtOut.evtAttach.attach(handler => {
if (ctx.getHandlers().length === 0) {
evt.$attach(
ping => ping === null ? null : [ping],
ctx,
ping => evtOut.post(ping)
);
return;
}
if (evt.state === null) {
return;
}
const op = evtOut.getInvocableOp(handler.op);
const result = op(evt.state, noSideEffect)
if (result === null) {
return;
}
handler.callback?.(result[0]);
});
evtOut.evtDetach.attach(
() => evtOut.getHandlers().length === 0,
() => {
ctx.done();
ctx = Evt.newCtx();
}
);
return evtOut;
}
const evtPing = Evt.create<string | null>(null);
const evtPingCopy= weakPipe(evtPing); |
Don't worry about it! I'm testing further and notice that it's not posting the initial state, not on first use or subsequent usage. I tried to re-create Pothos's use of async iterators in a stackblitz but it's not exactly the same, I was able to reproduce the issue. https://stackblitz.com/edit/evt-playground-syzowb?file=index.ts As you can see in the last one I also expected This problem seems to be unique to using async iterators, because if I swap those out with regular I've updated my project with a different workaround for now, so I dont need this method. just wanted to let you know my findings :) |
I'm sorry I wasn't able to produce a working solution. Here is a fix: -handler.callback?.(result[0])
+Promise.resolve().then(()=>handler.callback?.(result[0])); https://stackblitz.com/edit/evt-playground-7wpacg?file=index.ts |
Cheers! so for I replaced Thanks for all the effort helping me 👍 It's been fun learning more about this. |
The initial attach needed the same treatment if (ctx.getHandlers().length === 0) {
evt.$attach(
(value) => (value === null ? null : [value]),
ctx,
(value) => Promise.resolve().then(()=> evtOut.post(value))
);
return;
} |
I'm using Evt together with Pothos graphql subscriptions, where the
subscribe
method accepts an async iterator, which Evt instances are, great!Except now I'm trying to apply a filter or transform for the subscription, so I would have
and the schema
This works decently, as soon as you subscribe you get the latest value from the stateful event, which is great. But what if the first state was not published yet? I don't want to return
null
on the graphql resolver.I would like to be able to to something like
But I am not sure how to create a new instance of the Evt.
To create my own solution I attempted the helper method
and doing
And while this techincally works, it no longer unsubscribes, leading to eventually print
So, how am I able to apply operators to the event stream and return a new
Evt
that still properly unsubscribes?The text was updated successfully, but these errors were encountered: