feat(toolkit-lib): withListeners API for IoHost#1708
Conversation
withListeners API for IoHost
withListeners API for IoHostwithListeners API for IoHost
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1708 +/- ##
==========================================
- Coverage 89.59% 89.39% -0.20%
==========================================
Files 77 77
Lines 11758 11552 -206
Branches 1651 1624 -27
==========================================
- Hits 10534 10327 -207
- Misses 1195 1197 +2
+ Partials 29 28 -1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Total lines changed 1101 is greater than 1000. Please consider breaking this PR down. |
There was a problem hiding this comment.
Nope, these are private on purpose. Do not export them.
There was a problem hiding this comment.
Done. MessageInfo, CodeInfo, ActionLessMessage, ActionLessRequest are all un-exported again, and IO and the maker types are off the public barrel too. Nothing maker internal is public now
| */ | ||
| export type MessageSelector<T> = | ||
| | IoMessageMaker<T> | ||
| | IoRequestMaker<T, any> |
There was a problem hiding this comment.
These are not public types, nor should they be. We can accept string here (a single code) or a new MessageMatcher class (via IMessageMatcher). We can make the MessageMakers implement that interface so we can keep passing them in.
There was a problem hiding this comment.
Went with the string option. The public selector is now IoMessageCode (a single code) or a (msg) => boolean predicate, so it no longer references the maker types. I tried the IMessageMatcher route first but dropped it. To keep the typed maker path it wanted, IO would have to be public, and exporting IO pulls the maker internal types back onto the public surface through the same forgotten export cascade with api extractor. With IO private there's also no way for an external caller to obtain an IMessageMatcher. CliIoHost keeps passing makers internally through the private registry unchanged.
There was a problem hiding this comment.
yes there is, the CLI is a privileged caller that can use private exports from toolkit-lib.
In either case I don't understand why it's not possible. Can you share the IMessageMatcher interface you attempted?
| */ | ||
| export type MessageSelector<T> = | ||
| | IoMessageMaker<T> | ||
| | IoRequestMaker<T, any> |
There was a problem hiding this comment.
yes there is, the CLI is a privileged caller that can use private exports from toolkit-lib.
In either case I don't understand why it's not possible. Can you share the IMessageMatcher interface you attempted?
| * ``` | ||
| */ | ||
| rewrite( | ||
| code: IoMessageCode, |
| * const dispose = host.respond('CDK_TOOLKIT_I7010', true); | ||
| * ``` | ||
| */ | ||
| respond(code: IoMessageCode, value: unknown, suppressQuestion?: boolean): () => void; |
There was a problem hiding this comment.
I know is copied from what we currently have, but for a public API we need to think more careful about the design. If we ever want to add an other option this gets messy. We can preemptively put suppressQuestion in a property bag (options).
| export function withListeners(host: IIoHost): IoHostWithListeners { | ||
| return new ListeningIoHost(host); | ||
| } |
There was a problem hiding this comment.
Not sure I love this, but I guess it doesn't hurt either. 🤷🏻
| * codes are listed in the message registry: | ||
| * https://docs.aws.amazon.com/cdk/api/toolkit-lib/message-registry/ | ||
| */ | ||
| export interface IoHostWithListeners extends IIoHost { |
There was a problem hiding this comment.
do we need to extend the IIoHost interface? Or is IoEmitter a separate independent interface?
| * const toolkit = new Toolkit({ ioHost: host }); | ||
| * ``` | ||
| */ | ||
| export function withListeners(host: IIoHost): IoHostWithListeners { |
There was a problem hiding this comment.
We have this, we can at least make this a generic type so that the inner host keeps its higher fidelity.
| public on<T>( | ||
| selector: IoMessageMaker<T> | IoRequestMaker<T, any> | ((msg: IoMessage<any>) => msg is IoMessage<T>), | ||
| listener: (msg: IoMessage<T>) => MessageListenerResultOrPromise, | ||
| ): () => void; | ||
| public on( | ||
| predicate: (msg: IoMessage<any>) => boolean, | ||
| listener: (msg: IoMessage<unknown>) => MessageListenerResultOrPromise, | ||
| ): () => void; | ||
| public on(selector: MessageSelector<any>, listener: MessageListenerFn): () => void { |
There was a problem hiding this comment.
since this is a private class, it feels strange to have so many different signatures. But I guess thats because we couldn't make IIoMessageMatcher yet.
The CLI's
CliIoHosthas a listener mechanism that's private and welded to that one class. Programmatic toolkit-lib users have no way to observe or reshape what flows through their IoHost short of writing a whole custom host.This PR extracts that engine into a shared
ListenerRegistryand adds a publicwithListeners(host)wrapper, so listeners can be attached to anyIIoHost:A listener can observe a message (
on/once), rewrite its text (rewrite/rewriteOnce), suppress it, or answer a prompt (respond/respondOnce), and each registration returns a disposer.CliIoHostis refactored to delegate to the sameListenerRegistry, so there's one implementation of matching/ordering/rewriting/request-answering instead of two.The additions users interact with:
withListeners(host)and its return typeIoHostWithListeners.MessagePredicatefor matching on anything other than a single code.MessageListenerResult,MessageListenerResultOrPromiseListeners are keyed on a message
codestring (IoMessageCode, e.g.'CDK_TOOLKIT_I2901') or a predicate.msg.datais delivered asunknown; cast it to the payload interface documented for that code in the message registry.Checklist
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license