Skip to content

Commit

Permalink
feat(signal2): add signal2 (#1207)
Browse files Browse the repository at this point in the history
  • Loading branch information
alimd authored Jun 5, 2023
2 parents 3fd91fc + 0a69993 commit 544e16a
Show file tree
Hide file tree
Showing 23 changed files with 555 additions and 15 deletions.
31 changes: 17 additions & 14 deletions cloud/alwatr/soffit-order/env/soffit-order.env
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
U2FsdGVkX19utyKMkhE4ZmeXOKYyMLrRwLLssEpByjjWw3vNO3CLHulQZm/y9qYN
3Nymgm9K/8OcJAbKvOdKH8CANly0E4nXTsnnmNdcX4Lp7SJ0uPLT7Bun64/34JZW
CzeBNAb5WxGCFva6KOyMqja0NjXSZEokIXVyt2DzaHOM6DqrBCN8TPNpJtKw0FQb
l8wboKuf6FRaW6MRLgnl394rbQr3NJ0AmhbM58UaotYYGPS+TV4vvLjDTNNfJvkV
ACL9h6xYEWdP0ub6Mk/pq6Mwa3dOks73BUCXMnitDhR2V11Ce9T88M2DkQ9YJAUJ
YH+GOxw5m8f0VBHRZwe/XWAdaZJRJxfCx39mJpSwUV4r6hX7aFkjxJ4R+xyATACL
L6jkcIkZFwtf+Cc4ITVjC7iQ8Xhh95PY5R0YgHrh/EeKCxPq4MqcTZX9ttlAy4Pq
f5J+lTCOY8ZAH9Dfm97Ll6BwyB/rHOvhyAhydVYD9hQiBNBP/LNyPZ5fYacNbL6x
Hd9JKIP2w/0UURptZoisVMngXDF5X2TNypFl4TRbYUsps7CRcNlhMymJq+veGfk9
SJCllxCVe3wJL+07nfRbQyHYZgsxPCbWER92yW4UPRfmRtWi5pClD7xtkUvq5kN0
esYHo+TIhEjn+DmQtF1HmfEKyQtY4rv598YyjNJ+LDCFGC9ZKNiOmXx6JDzTfoz4
Y59a+WjN5IJ3VO5nDTyr18EFDgDqJ6wxg+J7bQzmGH/HFvD/S5aeTCxbGVRjHmaK
yYn3Rux6wf9cr2xpZjEZaZrNbjeJ+QRJX0dalzyO6hsuNWCnIvFKrKNp8AxjGqHN
W7L7IdkOSS6eqNp2x5dEjA==
U2FsdGVkX1+xuzPPE1ep+hVLPWUU56j5bvx8MiVNF9jKHjIXIB/9lIdENKGqceKm
shaVMz3kNApDcQfe4A+PpRvmB+I4wk3PtjlCBWap7s4aJaI049hS6BiimCY+B5V0
KV9aqTApj7rQMueRopMIn6+hrrCjX09dUj3+0ftiOzN6Z+AABvTIdBpyBydQbJRD
auRtgTOkoScLAYStfx+r3dHxabzjb17cbPaSTmpRnCo4zV863JDVFw3I4ate/3Fj
Wfx2qhh++DGWuiNf/J6Yqq0wh8kkSPh0EV4areD9DtoJHtcClHnZeyZeGGI5FRVp
71AR2aT0IiWGxh5pykAjx/7b74/nu5g7DaPHD01hu3OjdjZHKwLbXsYMJT7pLyI7
NYN5uCWXKU/IC+WyyKc42hqydi62g4FW+BeKLMU0N5bznOCwnl280of2wBGtADGV
bfCvt2b+ZBoYAwCVerUXIm5dj4ARX/y1ZtX9+B/A2s9McyZktZwLPsFakXs83hrn
BNDcdhVfEsE7cyqwoJHVAJIBXnB7S/SrdGQeFFG+LdRbufrNEvwwAheJKvxIn37h
p8P6LsXUusM6eaEQk8jUlMy5zatw0b/V/lNi0AtiMDqe4ru2T15GasENVHldfQDE
NN0xWAwAAZ1ovvKxL1PndhatWN5dqs+WfnB8gBj7//mjVusq8e+A1yDUiJeIgp1I
7AbXEc1F7gHFGbOZIESxSnjzDZXBP7bEbRR6ytcA4/oJ3lj6p93pNwxPaTjucTep
37fnyB9QUcM9w71cBtNJx8C1fRsvxjA7mHJ7uEKRSda0N5VzAKPGjNsS2VABTLlX
PL905CqdiL3YOwLBG5A4rkQQnH8dlve15BpCIY/eOoU4FX13kl9SokbRavrOOKPY
GNnenxFZZ6n+DHR9R68OfyIFOJhTuyVpfyzajD7kS6Plvq3DNtvZ+07ViNT7OaFl
yoDlYHrfvMKNYxrbOIW874/yFerUs/e/v9APkTf6h4xz5RLVBhLx4PasrM35vqCt
E1Ql0FyVmxsP3SqhfHSJAHcD15x9IJKkWKVN3f8KX6M=
5 changes: 5 additions & 0 deletions core/signal2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Alwatr Signal - `@alwatr/signal2`

Elegant powerful event system for handle global signals and states written in tiny TypeScript module.

Every signal has own value and can be used as a advance **state management** like redux and recoil without the complexities and unnecessary facilities of those libraries.
38 changes: 38 additions & 0 deletions core/signal2/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "@alwatr/signal2",
"version": "0.32.0",
"description": "Elegant powerful event system for handle global signals and states written in tiny TypeScript module.",
"keywords": [
"signal",
"typescript",
"esm",
"alwatr"
],
"main": "index.js",
"type": "module",
"types": "index.d.ts",
"author": "S. Ali Mihandoost <[email protected]> (https://ali.mihandoost.com)",
"license": "MIT",
"private": true,
"files": [
"**/*.{d.ts.map,d.ts,js.map,js,html,md}"
],
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/AliMD/alwatr",
"directory": "core/signal2"
},
"homepage": "https://github.com/AliMD/alwatr/tree/main/core/signal2#readme",
"bugs": {
"url": "https://github.com/AliMD/alwatr/issues"
},
"dependencies": {
"@alwatr/logger": "^0.32.0",
"@alwatr/type": "^0.32.0",
"@alwatr/util": "^0.32.0",
"tslib": "^2.5.2"
}
}
141 changes: 141 additions & 0 deletions core/signal2/src/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import {createLogger} from '@alwatr/logger';

import type {SubscribeOptions, ListenerCallback, ListenerObject, SubscribeResult as SubscribeResult} from './type.js';

/**
* Alwatr base signal.
*/
export abstract class AlwatrBaseSignal<TDetail> {
protected _logger;
protected _$detail?: TDetail;
protected _$listenerList: Array<ListenerObject<TDetail>> = [];

constructor(protected name: string, loggerPrefix: string) {
this._logger = createLogger(`{${loggerPrefix}: ${name}}`);
this._logger.logMethod?.('constructor');
}

/**
* Get detail.
*
* Return undefined if context not set before or expired.
*/
protected _getDetail(): TDetail | undefined {
this._logger.logMethodFull?.('_getDetail', {}, this._$detail);
return this._$detail;
}

/**
* change _detail and execute all listeners.
*/
protected _dispatch(detail: TDetail): void {
this._logger.logMethodArgs?.('_dispatch', {detail});
this._$detail = detail;
setTimeout(() => this._$executeListeners(), 0);
}

/**
* Execute all listeners callback.
*/
protected _$executeListeners(): void {
this._logger.logMethod?.('_$executeListeners');
if (this._$detail === undefined) return;

const removeList: Array<ListenerObject<TDetail>> = [];

for (const listener of this._$listenerList) {
if (listener.options.disabled) continue;
if (listener.options.once) removeList.push(listener);

try {
const ret = listener.callback(this._$detail);
if (ret instanceof Promise) {
ret.catch((err) => this._logger.error('_executeListeners', 'call_listener_failed', err));
}
}
catch (err) {
this._logger.error('_executeListeners', 'call_listener_failed', err);
}
}

for (const listener of removeList) {
this.unsubscribe(listener.callback);
}
}

/**
* Subscribe to context changes.
*/
subscribe(listenerCallback: ListenerCallback<TDetail>, options: SubscribeOptions = {}): SubscribeResult {
this._logger.logMethodArgs?.('subscribe', {options});

const _listenerObject: ListenerObject<TDetail> = {
callback: listenerCallback,
options,
};

let callbackExecuted = false;
const detail = this._$detail;
if (detail !== undefined && options.receivePrevious === true && options.disabled !== true) {
// Run callback for old dispatch signal
callbackExecuted = true;
setTimeout(() => {
try {
listenerCallback(detail);
}
catch (err) {
this._logger.error('subscribe.receivePrevious', 'call_signal_callback_failed', err);
}
}, 0);
}

// if once then must remove listener after fist callback called! then why push it to listenerList?!
if (options.once !== true || callbackExecuted === true) {
if (options.priority === true) {
this._$listenerList.unshift(_listenerObject);
}
else {
this._$listenerList.push(_listenerObject);
}
}

return {
unsubscribe: this.unsubscribe.bind(this, listenerCallback),
};
}

/**
* Unsubscribe from context.
*/
unsubscribe(listenerCallback: ListenerCallback<TDetail>): void {
this._logger.logMethod?.('unsubscribe');
const listenerIndex = this._$listenerList.findIndex((listener) => listener.callback === listenerCallback);
if (listenerIndex !== -1) {
void this._$listenerList.splice(listenerIndex, 1);
}
}

/**
* Clear current context detail without notify subscribers.
*
* `receivePrevious` in new subscribers not work until new context changes.
*/
protected _expire(): void {
this._logger.logMethod?.('expire');
this._$detail = undefined;
}

/**
* Get the detail of the next context changes.
*/
protected _untilChange(): Promise<TDetail> {
this._logger.logMethod?.('untilNext');
return new Promise((resolve) => {
this.subscribe(resolve, {
once: true,
priority: true,
receivePrevious: false,
});
});
}
}
43 changes: 43 additions & 0 deletions core/signal2/src/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {AlwatrBaseSignal} from './base.js';

/**
* Alwatr context signal.
*/
export class AlwatrContextSignal<TValue> extends AlwatrBaseSignal<TValue> {
constructor(public override name: string) {
super(name, 'context-signal');
}

/**
* Get context value.
*
* Return undefined if context not set before or expired.
*/
getValue(): TValue | undefined {
return this._getDetail();
}

/**
* Set context value and notify all subscribers.
*/
setValue(value: TValue): void {
this._logger.logMethodArgs?.('setValue', {value});
this._dispatch(value);
}

/**
* Clear current context value without notify subscribers.
*
* `receivePrevious` in new subscribers not work until new context changes.
*/
expire(): void {
this._expire();
}

/**
* Get the value of the next context changes.
*/
untilChange(): Promise<TValue> {
return this._untilChange();
}
}
17 changes: 17 additions & 0 deletions core/signal2/src/event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {AlwatrBaseSignal} from './base.js';

/**
* Alwatr event signal.
*/
export class AlwatrEventSignal<TDetail> extends AlwatrBaseSignal<TDetail> {
constructor(public override name: string) {
super(name, 'event-signal');
}

/**
* Dispatch an event to all listeners.
*/
dispatch(detail: TDetail): void {
this._dispatch(detail);
}
}
11 changes: 11 additions & 0 deletions core/signal2/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {globalAlwatr} from '@alwatr/logger';

export * from './simple-signal.js';
export * from './event.js';
export * from './context.js';
export * from './multithread-context.js';

globalAlwatr.registeredList.push({
name: '@alwatr/signal2',
version: _ALWATR_VERSION_,
});
62 changes: 62 additions & 0 deletions core/signal2/src/multithread-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {createLogger} from '@alwatr/logger';

import {AlwatrContextSignal} from './context.js';

type AlwatrContextChangedMessage = {
type: 'alwatr_context_changed';
name: string;
payload: unknown;
};

/**
* Alwatr multithread context signal.
*/
export class AlwatrMultithreadContextSignal<TValue> extends AlwatrContextSignal<TValue> {
protected static _logger = createLogger(`alwatr/mt-context`);
protected static _worker?: Worker;
protected static _registry: Record<string, AlwatrMultithreadContextSignal<unknown> | undefined> = {};

static setupChannel(worker: Worker = self as unknown as Worker): void {
AlwatrMultithreadContextSignal._worker = worker;
worker.addEventListener('message', AlwatrMultithreadContextSignal._onMessage);
}

static _onMessage(event: MessageEvent): void {
const message = event.data as AlwatrContextChangedMessage;
if (message.type !== 'alwatr_context_changed') return;
AlwatrMultithreadContextSignal._logger.logMethodArgs?.('_onMessage', {message});
const context = AlwatrMultithreadContextSignal._registry[message.name];
if (context === undefined) {
throw new Error('context_not_define', {cause: 'context not define in this thread yet!'});
}
context._dispatch(message.payload);
}

static _postMessage(name: string, payload: unknown): void {
AlwatrMultithreadContextSignal._logger.logMethodArgs?.('_postMessage', {name, payload});
if (AlwatrMultithreadContextSignal._worker === undefined) {
throw new Error('worker_not_defined', {cause: 'setupChannel must be called before any setValue.'});
}
AlwatrMultithreadContextSignal._worker.postMessage(<AlwatrContextChangedMessage>{
type: 'alwatr_context_changed',
name,
payload,
});
}

constructor(name: string) {
super(name);
if (AlwatrMultithreadContextSignal._registry[name] !== undefined) {
throw new Error('context_name_exist');
}
AlwatrMultithreadContextSignal._registry[name] = this as AlwatrMultithreadContextSignal<unknown>;
}

/**
* Set context value and notify all subscribers.
*/
override setValue(value: TValue): void {
super.setValue(value);
AlwatrMultithreadContextSignal._postMessage(this.name, value);
}
}
17 changes: 17 additions & 0 deletions core/signal2/src/simple-signal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {AlwatrBaseSignal} from './base.js';

/**
* Alwatr event signal without any detail.
*/
export class AlwatrSimpleSignal extends AlwatrBaseSignal<undefined> {
constructor(public override name: string) {
super(name, 'event-signal');
}

/**
* Dispatch an event to all listeners.
*/
dispatch(): void {
this._dispatch(undefined);
}
}
Loading

0 comments on commit 544e16a

Please sign in to comment.