-
Notifications
You must be signed in to change notification settings - Fork 145
Description
Link to thread with @bergundy
Is your feature request related to a problem? Please describe.
There's a common need to keep track of what queries and signals are legal to send to a workflow at this moment. My team is creating a layer for this internally, but it would be ideal to solve it on a SDK level.
Describe the solution you'd like
I'd like to see a built-in query which returns the queries and signals being currently listened to. This could take two forms:
Separate
- A
QueryDefinition<Array<string>, []>namedqueries - A
QueryDefinition<Array<string>, []>namedsignals
Together
- A
QueryDefinition<Array<{ name: string, type: 'query' | 'signal' }>, []>namedhandlers
In either case, we can't preserve the type information about these queries or signals, but we can at least maintain the knowledge of whether there is an active setHandler(name, () => {...}) for a given name. This allows us to drive UX experiences that only allow currently legal operations, which is a powerful technique for code-as-source-of-truth on our workflows.
Additional context
Here's a spike implementation for a trackHandler shim over setHandler that we're working on internally. Workflows need to be wrapped with trackHandlers in the form export const myWorkflow = (args: Args) => trackHandlers(myWorkflowImpl, args);. If this was built-in, such wrapping would not be needed.
const trackedHandlerStorage = new AsyncLocalStorage<Set<string>>();
const getTrackedHandlers = () => {
const handlers = trackedHandlerStorage.getStore();
if (!handlers) {
throw new IllegalStateError('Not in a tracked context!');
}
return handlers;
};
export const trackHandler: typeof setHandler = (def, handler) => {
const handlers = getTrackedHandlers();
handlers.add(def.name);
setHandler(def, ((...args) => {
trackedHandlerStorage.enterWith(handlers);
if (handler) {
return handler(...(args as Parameters<typeof handler>));
}
return undefined;
}) as typeof handler);
};
export const clearHandler = (def: Parameters<typeof setHandler>[0]) => {
const handlers = getTrackedHandlers();
handlers.delete(def.name);
setHandler(def, undefined);
};
export const clearHandlers = (defs: Array<Parameters<typeof setHandler>[0]>) => {
for (const def of defs) {
clearHandler(def);
}
};
export const handlersQuery = defineQuery<string[]>('handlers');
export const trackHandlers = <R, TArgs extends any[]>(
callback: (...args: TArgs) => R,
...args: TArgs
): R =>
trackedHandlerStorage.run(new Set(), () => {
const handlersRef = getTrackedHandlers();
trackHandler(handlersQuery, () => [...handlersRef]);
return callback(...args);
});