Skip to content
Commits on Source (4)
......@@ -69,7 +69,8 @@ publish-npm:
echo 'For more information, see https://docs.gitlab.com/ee/user/packages/npm_registry/#package-naming-convention'
exit 1
fi
- npm run build --cache
- npm run build --cache .npm
- npm run genDocs --cache .npm
- npm run semantic-release --cache .npm
only:
- main
......
## [2.0.0](https://gitlab.zweieuro.at/wui/web-user-interface-node/compare/v1.0.8...v2.0.0) (2024-05-05)
### ⚠ BREAKING CHANGES
* **persistent callback identifier system:** callback identification changed fundermentally
### Features
* **persistent callback identifier system:** change persistent cb id from function to symbol ([457cac6](https://gitlab.zweieuro.at/wui/web-user-interface-node/commit/457cac6e12b1ec723cce51e818b218670e64c800)), closes [#6](https://gitlab.zweieuro.at/wui/web-user-interface-node/issues/6)
### Miscellaneous Chores
* **jsdocs:** add Better JSDocs and make use of JSdocs to md with a TS plugin add it to the pipeline ([d4b02eb](https://gitlab.zweieuro.at/wui/web-user-interface-node/commit/d4b02ebcf59deaf12e3fd1f0a40945eda59a7433))
### [1.0.8](https://gitlab.zweieuro.at/wui/web-user-interface-node/compare/v1.0.7...v1.0.8) (2024-03-03)
......
# @zweieuro/web-user-interface
# @zweieuro/web-user-interface-node
Release of npm module.
Make a fix push.
> My awesome module
This is the node backend of WUI. Go https://wui.pages.zweieuro.at/web-user-interface-docs/docs/introduction to see the documentation.
## Install
```bash
# add this to ~/.npmrc
@wui:registry=https://gitlab.zweieuro.at/api/v4/packages/npm/
```
# add instructions here of how to add gitlab to your personal registry
and install it to your project as a **dependency**:
npm i @zweieuro/web-user-interface
```bash
npm i @wui/web-user-interface-node
```
TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false.
\ No newline at end of file
@wui/web-user-interface-node / [Exports](modules.md)
# @zweieuro/web-user-interface-node
This is the node backend of WUI. Go https://wui.pages.zweieuro.at/web-user-interface-docs/docs/introduction to see the documentation.
## Install
```bash
# add this to ~/.npmrc
@wui:registry=https://gitlab.zweieuro.at/api/v4/packages/npm/
```
and install it to your project as a **dependency**:
```bash
npm i @wui/web-user-interface-node
```
[@wui/web-user-interface-node](README.md) / Exports
# @wui/web-user-interface-node
## Table of contents
### Functions
- [WuiSupported](modules.md#wuisupported)
- [registerEventListener](modules.md#registereventlistener)
- [registerFailureCallback](modules.md#registerfailurecallback)
- [sendEvent](modules.md#sendevent)
- [unregisterEventListener](modules.md#unregistereventlistener)
- [unregisterFailureCallback](modules.md#unregisterfailurecallback)
## Functions
### WuiSupported
**WuiSupported**(): `boolean`
#### Returns
`boolean`
true if WUI is supported, false otherwise.
**`Brief`**
WUI is only supported in the WUI library and its internal functions. Detected via the global object.
#### Defined in
types.ts:34
___
### registerEventListener
**registerEventListener**\<`payload_t`\>(`eventName`, `callback`): `symbol`
#### Type parameters
| Name | Type |
| :------ | :------ |
| `payload_t` | `Record`\<`string`, `unknown`\> |
#### Parameters
| Name | Type | Description |
| :------ | :------ | :------ |
| `eventName` | `string` | for which the backend is listening |
| `callback` | (`payload`: `payload_t`) => `void` | to call when the event is received |
#### Returns
`symbol`
**`Brief`**
Register a listener for a given event name.
#### Defined in
PersistentCallback.ts:93
___
### registerFailureCallback
**registerFailureCallback**(`callback`): `void`
#### Parameters
| Name | Type | Description |
| :------ | :------ | :------ |
| `callback` | (`errorCode`: `number`, `errorMessage`: `string`) => `void` | to call when a persistent query fails |
#### Returns
`void`
**`Brief`**
register a single callback that is called when any persistent query fails. If none is registered a console warning is printed instead.
#### Defined in
PersistentCallback.ts:127
___
### sendEvent
**sendEvent**\<`payload_t`, `successObject_t`\>(`eventName`, `payload`): `Promise`\<`successObject_t`\>
#### Type parameters
| Name | Type |
| :------ | :------ |
| `payload_t` | extends `Record`\<`string`, `unknown`\> |
| `successObject_t` | `Record`\<`string`, `unknown`\> |
#### Parameters
| Name | Type | Description |
| :------ | :------ | :------ |
| `eventName` | `string` | for which the backend is listening |
| `payload` | `payload_t` | |
#### Returns
`Promise`\<`successObject_t`\>
resolve on success, reject on failure
**`Brief`**
Send a single event to a backend destination which is listening for it.
#### Defined in
singleEvents.ts:10
___
### unregisterEventListener
**unregisterEventListener**(`eventName`, `callbackIdentifier`): `boolean`
#### Parameters
| Name | Type | Description |
| :------ | :------ | :------ |
| `eventName` | `string` | for which the backend is listening |
| `callbackIdentifier` | `symbol` | - |
#### Returns
`boolean`
true if the callback was removed, false otherwise (e.g. if it was not registered)
**`Brief`**
Unregister a listener for a given event name.
#### Defined in
PersistentCallback.ts:112
___
### unregisterFailureCallback
**unregisterFailureCallback**(): `void`
#### Returns
`void`
**`Brief`**
unregister the failure callback
#### Defined in
PersistentCallback.ts:136
{
"name": "@zweieuro/web-user-interface",
"version": "0.0.1",
"name": "@wui/web-user-interface-node",
"version": "1.0.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@zweieuro/web-user-interface",
"version": "0.0.1",
"name": "@wui/web-user-interface-node",
"version": "1.0.8",
"license": "MIT",
"devDependencies": {
"@semantic-release/changelog": "6.0.3",
......@@ -31,19 +31,21 @@
"eslint-plugin-jest": "27.9.0",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-prettier": "5.1.3",
"husky": "^9.0.10",
"husky": "9.0.10",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"jest-junit": "16.0.0",
"jsdom": "24.0.0",
"lint-staged": "^15.2.2",
"lint-staged": "15.2.2",
"prettier": "3.2.5",
"react": "18.2.0",
"react-dom": "18.2.0",
"semantic-release": "23.0.2",
"sort-package-json": "^2.8.0",
"sort-package-json": "2.8.0",
"standard-version": "9.5.0",
"ts-jest": "29.1.2",
"typedoc": "^0.25.12",
"typedoc-plugin-markdown": "^3.17.1",
"typescript": "5.3.3"
},
"engines": {
......@@ -3016,6 +3018,12 @@
"node": ">=8"
}
},
"node_modules/ansi-sequence-parser": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz",
"integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==",
"dev": true
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"dev": true,
......@@ -9460,6 +9468,12 @@
"node": ">=6"
}
},
"node_modules/jsonc-parser": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz",
"integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==",
"dev": true
},
"node_modules/jsonfile": {
"version": "6.1.0",
"dev": true,
......@@ -10144,6 +10158,12 @@
"node": ">=10"
}
},
"node_modules/lunr": {
"version": "2.3.9",
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
"integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==",
"dev": true
},
"node_modules/lz-string": {
"version": "1.5.0",
"dev": true,
......@@ -14756,6 +14776,18 @@
"node": ">=8"
}
},
"node_modules/shiki": {
"version": "0.14.7",
"resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.7.tgz",
"integrity": "sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==",
"dev": true,
"dependencies": {
"ansi-sequence-parser": "^1.1.0",
"jsonc-parser": "^3.2.0",
"vscode-oniguruma": "^1.7.0",
"vscode-textmate": "^8.0.0"
}
},
"node_modules/side-channel": {
"version": "1.0.5",
"dev": true,
......@@ -16019,6 +16051,75 @@
"dev": true,
"license": "MIT"
},
"node_modules/typedoc": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.12.tgz",
"integrity": "sha512-F+qhkK2VoTweDXd1c42GS/By2DvI2uDF4/EpG424dTexSHdtCH52C6IcAvMA6jR3DzAWZjHpUOW+E02kyPNUNw==",
"dev": true,
"dependencies": {
"lunr": "^2.3.9",
"marked": "^4.3.0",
"minimatch": "^9.0.3",
"shiki": "^0.14.7"
},
"bin": {
"typedoc": "bin/typedoc"
},
"engines": {
"node": ">= 16"
},
"peerDependencies": {
"typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x"
}
},
"node_modules/typedoc-plugin-markdown": {
"version": "3.17.1",
"resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.17.1.tgz",
"integrity": "sha512-QzdU3fj0Kzw2XSdoL15ExLASt2WPqD7FbLeaqwT70+XjKyTshBnUlQA5nNREO1C2P8Uen0CDjsBLMsCQ+zd0lw==",
"dev": true,
"dependencies": {
"handlebars": "^4.7.7"
},
"peerDependencies": {
"typedoc": ">=0.24.0"
}
},
"node_modules/typedoc/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/typedoc/node_modules/marked": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
"integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==",
"dev": true,
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/typedoc/node_modules/minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/typescript": {
"version": "5.3.3",
"dev": true,
......@@ -16209,6 +16310,18 @@
"spdx-expression-parse": "^3.0.0"
}
},
"node_modules/vscode-oniguruma": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz",
"integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==",
"dev": true
},
"node_modules/vscode-textmate": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz",
"integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==",
"dev": true
},
"node_modules/w3c-xmlserializer": {
"version": "5.0.0",
"dev": true,
......
{
"name": "@wui/web-user-interface-node",
"version": "1.0.8",
"version": "2.0.0",
"description": "Renderer side code for the WUI (Web User Interface) library.",
"bugs": {
"url": "https://gitlab.zweieuro.at/wui/web-user-interface-node/-/issues"
......@@ -34,6 +34,7 @@
"build": "tsc --project tsconfig.build.json",
"build:watch": "tsc --project tsconfig.build.json --watch",
"clean": "rm -rf ./lib/",
"genDocs": "typedoc --plugin typedoc-plugin-markdown --out docs src",
"lint": "eslint ./src/ --fix",
"prepare": "husky install",
"release": "semantic-release",
......@@ -88,6 +89,8 @@
"sort-package-json": "2.8.0",
"standard-version": "9.5.0",
"ts-jest": "29.1.2",
"typedoc": "0.25.12",
"typedoc-plugin-markdown": "3.17.1",
"typescript": "5.3.3"
},
"peerDependencies": {
......
......@@ -86,6 +86,7 @@ module.exports = {
assets: [
{ path: 'package.json', label: 'Module' },
{ path: 'CHANGELOG.md', label: 'Changelog' },
{ path: 'docs/*', label: 'Documentation' },
],
gitlabUrl: 'https://gitlab.zweieuro.at',
......@@ -95,7 +96,7 @@ module.exports = {
[
'@semantic-release/git',
{
assets: ['package.json', 'CHANGELOG.md'],
assets: ['package.json', 'CHANGELOG.md', 'docs/*'],
message:
'chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}',
},
......
......@@ -4,7 +4,7 @@ export class PersistentCallbacksManager {
private queryId_: WuiQueryId | null = null;
private eventName_: string;
private dataCallbacks_: Array<(arg: unknown) => void> = [];
private dataCallbacks_: Map<symbol, (arg: unknown) => void> = new Map();
constructor(eventName: string) {
this.eventName_ = eventName;
......@@ -26,8 +26,10 @@ export class PersistentCallbacksManager {
}
}
addOnData(callback: (arg: unknown) => void) {
this.dataCallbacks_.push(callback);
addOnData(callback: (arg: unknown) => void): symbol {
const newSymbol = Symbol();
this.dataCallbacks_.set(newSymbol, callback);
if (this.queryId_ === null) {
this.queryId_ = globalThis.window.WuiQuery({
......@@ -37,17 +39,18 @@ export class PersistentCallbacksManager {
onFailure: this.onFailure.bind(this),
});
}
return newSymbol;
}
removeOnData(callback: (arg: unknown) => void): boolean {
const index = this.dataCallbacks_.indexOf(callback);
if (index !== -1) {
this.dataCallbacks_.splice(index, 1);
} else {
removeOnData(callbackIdentifier: symbol): boolean {
if (!this.dataCallbacks_.has(callbackIdentifier)) {
return false;
}
if (this.dataCallbacks_.length === 0) {
this.dataCallbacks_.delete(callbackIdentifier);
if (this.dataCallbacks_.size === 0) {
if (this.queryId_ !== null) {
if (globalThis.window.WuiQueryCancel(this.queryId_) === false) {
console.error(
......@@ -80,7 +83,7 @@ function getPersistentCallbacksManager(eventName: string) {
}
/**
* Register a listener for a given event name.
* @brief Register a listener for a given event name.
*
* @param eventName for which the backend is listening
* @param callback to call when the event is received
......@@ -90,15 +93,15 @@ function getPersistentCallbacksManager(eventName: string) {
export function registerEventListener<payload_t = Record<string, unknown>>(
eventName: string,
callback: (payload: payload_t) => void
): void {
): symbol {
checkWuiSupported();
const manager = getPersistentCallbacksManager(eventName);
manager.addOnData(callback as (arg: unknown) => void);
return manager.addOnData(callback as (arg: unknown) => void);
}
/**
* Unregister a listener for a given event name.
* @brief Unregister a listener for a given event name.
*
* @param eventName for which the backend is listening
* @param callback to call when the event is received
......@@ -106,22 +109,30 @@ export function registerEventListener<payload_t = Record<string, unknown>>(
* @returns true if the callback was removed, false otherwise (e.g. if it was not registered)
*
*/
export function unregisterEventListener<payload_t = Record<string, unknown>>(
export function unregisterEventListener(
eventName: string,
callback: (payload: payload_t) => void
callbackIdentifier: symbol
): boolean {
checkWuiSupported();
const manager = getPersistentCallbacksManager(eventName);
return manager.removeOnData(callback as (arg: unknown) => void);
return manager.removeOnData(callbackIdentifier);
}
/**
* @brief register a single callback that is called when any persistent query fails. If none is registered a console warning is printed instead.
*
* @param callback to call when a persistent query fails
*/
export function registerFailureCallback(
callback: (errorCode: number, errorMessage: string) => void
): void {
failureCallback = callback;
}
/**
* @brief unregister the failure callback
*/
export function unregisterFailureCallback(): void {
failureCallback = undefined;
}
......
......@@ -10,17 +10,20 @@ export function useEventListener<T extends Record<string, unknown>>(
const [data, setData] = useState<T | null>(null);
useEffect(() => {
const receiver = (data: T) => {
setData(data);
};
let callbackIdentifier: symbol | undefined = undefined;
try {
registerEventListener(eventName, receiver);
callbackIdentifier = registerEventListener(eventName, (data: T) => {
setData(data);
});
} catch (e) {
console.error('Error registering event listener', e);
}
return () => {
unregisterEventListener(eventName, receiver);
if (callbackIdentifier) {
unregisterEventListener(eventName, callbackIdentifier);
}
};
}, []);
......
import { checkWuiSupported, makeRejectString } from './types';
/**
* Send a single event to a backend destination which is listening for it.
* @brief Send a single event to a backend destination which is listening for it.
*
* @param eventName for which the backend is listening
* @param payload
......@@ -23,7 +23,10 @@ export function sendEvent<
resolve(JSON.parse(response) as successObject_t);
},
onFailure: function (errorCode: number, errorMessage: string) {
reject(makeRejectString(errorCode, errorMessage, eventName));
const errString = makeRejectString(errorCode, errorMessage, eventName);
console.debug('sendEvent failed', errString);
reject(errString);
},
});
});
......
......@@ -27,9 +27,9 @@ export function makeRejectString(
}
/**
* WUI is only supported in the WUI library and its internal functions
* @brief WUI is only supported in the WUI library and its internal functions. Detected via the global object.
*
* @returns true if WUI is supported, false otherwise
* @returns true if WUI is supported, false otherwise.
*/
export function WuiSupported(): boolean {
if (
......@@ -42,7 +42,7 @@ export function WuiSupported(): boolean {
}
/**
* throws an error if WUI is not supported
* @brief Check if wui is supported, will throw if it isn't.
*/
export function checkWuiSupported(): void {
if (!WuiSupported()) {
......
......@@ -21,7 +21,7 @@ describe('tests no throw', () => {
});
it('and unsub working normally', () => {
registerEventListener('test', listener);
const listenerSymbol = registerEventListener('test', listener);
expect(jestExport.getCurrentPersistentQueriesToId()['test']).toBeDefined();
......@@ -33,26 +33,26 @@ describe('tests no throw', () => {
expect(console.warn).toHaveBeenCalledTimes(1);
// unsubscribe
expect(unregisterEventListener('test', listener)).toBe(true);
expect(unregisterEventListener('test', listenerSymbol)).toBe(true);
});
it('unsub twice', () => {
registerEventListener('test', listener);
const listenerSymbol = registerEventListener('test', listener);
expect(jestExport.getCurrentPersistentQueriesToId()['test']).toBeDefined();
wuiMock.wuiMockOKAll(); // send success to everone
// unsubscribe
expect(unregisterEventListener('test', listener)).toBe(true);
expect(unregisterEventListener('test', listenerSymbol)).toBe(true);
// unsubscribe again
expect(unregisterEventListener('test', listener)).toBe(false);
expect(unregisterEventListener('test', listenerSymbol)).toBe(false);
});
it('unsub something that was never registered', () => {
// unsubscribe
expect(unregisterEventListener('test', listener)).toBe(false);
expect(unregisterEventListener('test', Symbol())).toBe(false);
});
it('unregisterEventListener not throwing', () => {
......@@ -60,12 +60,19 @@ describe('tests no throw', () => {
expect(x).toEqual({ mockSuccess: true });
};
let listenerSymbol: symbol | undefined = undefined;
expect(() => {
registerEventListener('test', listener);
listenerSymbol = registerEventListener('test', listener);
}).not.toThrow();
expect(listenerSymbol).toBeDefined();
expect(() => {
unregisterEventListener('test', listener);
if (listenerSymbol === undefined) {
throw new Error('listenerSymbol is undefined');
}
unregisterEventListener('test', listenerSymbol);
}).not.toThrow();
});
});
......@@ -105,10 +112,10 @@ describe('throw on cancel', () => {
});
it('failure on cancel', () => {
registerEventListener('test', listener);
const listenerSymbol = registerEventListener('test', listener);
expect(() => {
unregisterEventListener('test', listener);
unregisterEventListener('test', listenerSymbol);
}).toThrow();
});
});
......@@ -121,8 +128,8 @@ describe('failure on cancel', () => {
});
it('failure on cancel', () => {
registerEventListener('test', listener); // success
unregisterEventListener('test', listener);
const listenerSymbol = registerEventListener('test', listener); // success
unregisterEventListener('test', listenerSymbol);
expect(console.error).toHaveBeenCalledTimes(1);
......
......@@ -2,11 +2,12 @@
"compilerOptions": {
"outDir": "lib",
"module": "esnext",
"target": "ES5",
"target": "ES2016",
"lib": ["dom", "esnext"],
"moduleResolution": "node",
"jsx": "react",
"sourceMap": true,
"inlineSources": true,
"declaration": true,
"esModuleInterop": true,
"noImplicitReturns": true,
......