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

feat(signal): separate request command with response #750

Merged
merged 1 commit into from
Jan 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions core/signal/src/command-trigger.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {requestCommand} from './core.js';
import {requestCommand, requestCommandWithResponse} from './core.js';

import type {OmitFirstParam, Stringifyable} from '@alwatr/type';

Expand All @@ -7,16 +7,29 @@ import type {OmitFirstParam, Stringifyable} from '@alwatr/type';
*/
export const commandTrigger = {
/**
* Dispatch request command signal with commandArgument as detail and return untilNext of callback signal.
* Dispatch request command signal with commandArgument as detail.
*
* Example:
*
* ```ts
* const returnObject = await commandTrigger.request<ArgumentType, ReturnType>('show-dialog', {foo: 'bar'});
* commandTrigger.request<ArgumentType>('show-dialog', {foo: 'bar'});
* ```
*/
request: requestCommand,

/**
* Dispatch request command signal with commandArgument as detail and return untilNext of callback signal.
*
* Request command and wait for answer.
*
* Example:
*
* ```ts
* const response = await commandTrigger.requestWithResponse<ArgumentType, ReturnType>('show-dialog', {foo: 'bar'});
* ```
*/
requestWithResponse: requestCommandWithResponse,

/**
* Bind define command to special command.
*
Expand All @@ -38,10 +51,22 @@ export const commandTrigger = {
* Example:
*
* ```ts
* const returnObject = await showDialog.request({foo: 'bar'});
* showDialog.request({foo: 'bar'});
* ```
*/
request: requestCommand.bind(null, commandId) as unknown as
OmitFirstParam<typeof requestCommand<TArgument, TReturn>>,
OmitFirstParam<typeof requestCommand<TArgument>>,

/**
* Dispatch request command signal with commandArgument as detail and return untilNext of callback signal.
*
* Example:
*
* ```ts
* const response = await showDialog.requestWithResponse({foo: 'bar'});
* ```
*/
requestWithResponse: requestCommandWithResponse.bind(null, commandId) as unknown as
OmitFirstParam<typeof requestCommandWithResponse<TArgument, TReturn>>,
}),
} as const;
93 changes: 70 additions & 23 deletions core/signal/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,24 +340,27 @@ export const setContextProvider = <TContext extends Stringifyable, TRquest exten
*/
export const defineCommand = <TArgument extends Record<string, Stringifyable>, TReturn extends Stringifyable>(
signalId: string,
signalProvider: ProviderFunction<TArgument & {_callbackSignalId: string}, TReturn>,
signalProvider: ProviderFunction<TArgument & {_callbackSignalId?: string}, TReturn>,
options: Partial<Pick<ProviderOptions, 'debounce'>> = {},
): void => {
options.debounce ??= 'AnimationFrame';
logger.logMethodArgs('defineCommand', {commandId: signalId, options});
const requestSignalId = 'request-' + signalId;
removeAllListeners(requestSignalId);
subscribe<TArgument & {_callbackSignalId: string}>(
subscribe<TArgument & {_callbackSignalId?: string}>(
requestSignalId,
async (argumentObject) => {
const callbackSignalId = argumentObject._callbackSignalId;
// TODO: validate callbackSignalId
const commandReturn = await signalProvider(argumentObject);
dispatch<TReturn>(callbackSignalId, commandReturn, {debounce: options.debounce});
},
{
receivePrevious: 'No', // Prevent to merge multiple previous requests
clearDetail(requestSignalId); // clean argumentObject from memory
if (argumentObject._callbackSignalId == null) {
signalProvider(argumentObject);
}
else {
dispatch<TReturn>(argumentObject._callbackSignalId, await signalProvider(argumentObject), {
debounce: options.debounce,
});
}
},
{receivePrevious: 'NextCycle'},
);
};

Expand All @@ -380,34 +383,59 @@ export const requestContext = <TRequest extends Stringifyable>(
return dispatch<TRequest>(contextId, requestParam, options);
};

/**
* Dispatch request command signal with commandArgument as detail.
*
* Example:
*
* ```ts
* requestCommand<ArgumentType>('show-dialog', {foo: 'bar'});
* ```
*/
export const requestCommand = <TArgument extends Record<string, Stringifyable>>(
commandId: string,
commandArgument: TArgument,
): void => {
logger.logMethodArgs('requestCommand', {commandId, commandArgument});
dispatch<TArgument>(`request-${commandId}`, commandArgument, {debounce: 'No'});
};

/**
* Dispatch request command signal with commandArgument as detail and return untilNext of callback signal.
*
* Request command and wait for answer.
*
* Example:
*
* ```ts
* const returnObject = await requestCommand<ArgumentType, ReturnType>('show-dialog', {foo: 'bar'});
* const response = await requestCommandWithResponse<ArgumentType, ReturnType>('show-dialog', {foo: 'bar'});
* ```
*/
export const requestCommand = <TArgument extends Record<string, Stringifyable>, TReturn extends Stringifyable>(
export const requestCommandWithResponse = async <
TArgument extends Record<string, Stringifyable>,
TReturn extends Stringifyable
>(
commandId: string,
commandArgument: TArgument,
): Promise<TReturn> => {
return new Promise((resolve) => {
logger.logMethodArgs('requestCommand', {commandId, commandArgument});
logger.logMethodArgs('requestCommand', {commandId, commandArgument});

const requestSignalId = `request-${commandId}`;
const callbackSignalId = `callback-${commandId}-${++_lastListenerAutoId}`;
const _requestSignalId = `request-${commandId}`;
const _callbackSignalId = `callback-${commandId}-${++_lastListenerAutoId}`;
const untilCallback = untilNext<TReturn>(_callbackSignalId);

subscribe<TReturn>(callbackSignalId, resolve, {once: true, priority: true, receivePrevious: 'No'});
// TODO: refactor _untilNextSignal with option and use it
dispatch<TArgument & {_callbackSignalId: string}>(
_requestSignalId,
{
...commandArgument,
_callbackSignalId,
},
{debounce: 'No'},
);

dispatch<TArgument & {_callbackSignalId: string}>(
requestSignalId,
{...commandArgument, _callbackSignalId: callbackSignalId},
{debounce: 'No'},
);
});
const response = await untilCallback;
destroySignal(_callbackSignalId);
njfamirm marked this conversation as resolved.
Show resolved Hide resolved
return response;
};

/**
Expand All @@ -426,3 +454,22 @@ export const clearDetail = (signalId: string): void => {
const signal = getSignalObject(signalId);
delete signal.detail;
};

/**
* Delete signal object with detail and listeners and options.
*
* new subscriber options.receivePrevious not work until new signal
*
* Example:
*
* ```ts
* destroySignal('product-list');
* ```
*/
export const destroySignal = (signalId: string): void => {
logger.logMethodArgs('destroySignal', signalId);
const signal = _signalStorage[signalId];
if (signal == null) return;
signal.listenerList.length = 0;
delete _signalStorage[signalId];
};