From 99d1bfe83a8b5296ee0dd2d0c2bf856afaa77636 Mon Sep 17 00:00:00 2001 From: Emil Hammarstedt Date: Thu, 11 Feb 2021 20:05:21 +0100 Subject: [PATCH 1/4] Add Eventlistener feature - It is now possible to add an eventlistener to be notified after beeing queued and before beeing dequeued for further processing. --- src/QueueLink.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/QueueLink.ts b/src/QueueLink.ts index 55064d8..b8e444a 100644 --- a/src/QueueLink.ts +++ b/src/QueueLink.ts @@ -17,17 +17,38 @@ interface OperationQueueEntry { } export default class QueueLink extends ApolloLink { + static listeners: Record< string, ((entry: any) => void)[] > = {}; private opQueue: OperationQueueEntry[] = []; private isOpen = true; public open() { this.isOpen = true; this.opQueue.forEach(({ operation, forward, observer }) => { + const key: string = QueueLink.key(operation.operationName, 'dequeue'); + if (key in QueueLink.listeners) { + QueueLink.listeners[key].forEach((listener) => { + return listener({ operation, forward, observer }); + }); + } forward(operation).subscribe(observer); }); this.opQueue = []; } + public static addLinkQueueEventListener = (opName: string, event: 'dequeue' | 'enqueue', listener: (entry: any) => void) => { + const key: string = QueueLink.key(opName, event); + + const newListener = { [key]: [ + ...(key in QueueLink.listeners ? QueueLink.listeners[key] : []), + ...[listener], ] + }; + + QueueLink.listeners = { ...QueueLink.listeners, ...newListener }; + }; + + private static key(op: string, ev: string) { + return `${op}${ev}`.toLocaleLowerCase(); + } public close() { this.isOpen = false; } @@ -52,5 +73,10 @@ export default class QueueLink extends ApolloLink { private enqueue(entry: OperationQueueEntry) { this.opQueue.push(entry); + + const key: string = QueueLink.key(entry.operation.operationName, 'enqueue'); + if (key in QueueLink.listeners) { + QueueLink.listeners[key].forEach((listener) => listener(entry)); + } } } From 67dd809d1f4640394d4ae9f5680d1b36d778fbc2 Mon Sep 17 00:00:00 2001 From: Emil Hammarstedt Date: Thu, 11 Feb 2021 20:10:55 +0100 Subject: [PATCH 2/4] Code clean up --- src/QueueLink.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/QueueLink.ts b/src/QueueLink.ts index b8e444a..adce3b9 100644 --- a/src/QueueLink.ts +++ b/src/QueueLink.ts @@ -27,7 +27,7 @@ export default class QueueLink extends ApolloLink { const key: string = QueueLink.key(operation.operationName, 'dequeue'); if (key in QueueLink.listeners) { QueueLink.listeners[key].forEach((listener) => { - return listener({ operation, forward, observer }); + listener({ operation, forward, observer }); }); } forward(operation).subscribe(observer); @@ -76,7 +76,9 @@ export default class QueueLink extends ApolloLink { const key: string = QueueLink.key(entry.operation.operationName, 'enqueue'); if (key in QueueLink.listeners) { - QueueLink.listeners[key].forEach((listener) => listener(entry)); + QueueLink.listeners[key].forEach((listener) => { + listener(entry) + }); } } } From 9941fbc08bb3d594435ef538683d080d5cc80312 Mon Sep 17 00:00:00 2001 From: Emil Hammarstedt Date: Thu, 11 Feb 2021 20:13:39 +0100 Subject: [PATCH 3/4] Code clean up --- src/QueueLink.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/QueueLink.ts b/src/QueueLink.ts index adce3b9..a470b8e 100644 --- a/src/QueueLink.ts +++ b/src/QueueLink.ts @@ -77,7 +77,7 @@ export default class QueueLink extends ApolloLink { const key: string = QueueLink.key(entry.operation.operationName, 'enqueue'); if (key in QueueLink.listeners) { QueueLink.listeners[key].forEach((listener) => { - listener(entry) + listener(entry); }); } } From 584c26d22dbb3568a28d807e58a67aaffe875ec6 Mon Sep 17 00:00:00 2001 From: remote Date: Thu, 29 Apr 2021 10:31:30 +0200 Subject: [PATCH 4/4] Removal of event listeners (#2) * Removal of old listeners - Return a GUID on creation of a listener - Function for removing the listener by passing GUID, opName and event * Remove tslint rules that are no longer in use --- src/QueueLink.ts | 195 ++++++++++++++++++++++++++++++++--------------- src/Utils.ts | 7 ++ tslint.json | 23 +----- 3 files changed, 141 insertions(+), 84 deletions(-) create mode 100644 src/Utils.ts diff --git a/src/QueueLink.ts b/src/QueueLink.ts index a470b8e..2fea904 100644 --- a/src/QueueLink.ts +++ b/src/QueueLink.ts @@ -1,84 +1,153 @@ import { - ApolloLink, - Operation, - FetchResult, - NextLink, + ApolloLink, + Operation, + FetchResult, + NextLink, } from '@apollo/client/link/core'; -import { - Observable, - Observer, -} from '@apollo/client/utilities'; +import { Observable, Observer } from '@apollo/client/utilities'; +import { createGuid } from './Utils'; interface OperationQueueEntry { - operation: Operation; - forward: NextLink; - observer: Observer; - subscription?: { unsubscribe: () => void }; + operation: Operation; + forward: NextLink; + observer: Observer; + subscription?: { unsubscribe: () => void }; +} + +type event = 'dequeue' | 'enqueue' | 'change'; + +interface Listener { + id: string; + callback: (entry: any) => void; } export default class QueueLink extends ApolloLink { - static listeners: Record< string, ((entry: any) => void)[] > = {}; - private opQueue: OperationQueueEntry[] = []; - private isOpen = true; - - public open() { - this.isOpen = true; - this.opQueue.forEach(({ operation, forward, observer }) => { - const key: string = QueueLink.key(operation.operationName, 'dequeue'); - if (key in QueueLink.listeners) { - QueueLink.listeners[key].forEach((listener) => { - listener({ operation, forward, observer }); - }); - } - forward(operation).subscribe(observer); - }); - this.opQueue = []; + static listeners: Record = {}; + private opQueue: OperationQueueEntry[] = []; + private isOpen = true; + + public clear() { + this.opQueue = []; + QueueLink.listeners = {}; + } + + public open() { + this.isOpen = true; + + const first: OperationQueueEntry | undefined = this.opQueue.shift(); + + if (first !== undefined) { + const { operation, forward, observer } = first; + + this.triggerListeners(first, 'dequeue'); + + forward(operation).subscribe( + (value) => { + if (observer && observer.next) { + observer?.next(value); + } + }, + (error) => { + if (observer && observer.error) { + observer?.error(error); + } + this.open(); + }, + () => { + if (observer && observer.complete) { + observer?.complete(); + } + this.open(); + } + ); } + } - public static addLinkQueueEventListener = (opName: string, event: 'dequeue' | 'enqueue', listener: (entry: any) => void) => { - const key: string = QueueLink.key(opName, event); + public static addLinkQueueEventListener = ( + opName: string, + event: event, + callback: (entry: any) => void + ) => { + if (event === 'change') opName = ''; + const key: string = QueueLink.key(opName, event); - const newListener = { [key]: [ - ...(key in QueueLink.listeners ? QueueLink.listeners[key] : []), - ...[listener], ] - }; + const newGuid = createGuid(); - QueueLink.listeners = { ...QueueLink.listeners, ...newListener }; + const newListener = { + [key]: [ + ...(key in QueueLink.listeners ? QueueLink.listeners[key] : []), + ...[{ id: newGuid, callback }], + ], }; - private static key(op: string, ev: string) { - return `${op}${ev}`.toLocaleLowerCase(); - } - public close() { - this.isOpen = false; - } + QueueLink.listeners = { ...QueueLink.listeners, ...newListener }; - public request(operation: Operation, forward: NextLink) { - if (this.isOpen) { - return forward(operation); - } - if (operation.getContext().skipQueue) { - return forward(operation); - } - return new Observable((observer: Observer) => { - const operationEntry = { operation, forward, observer }; - this.enqueue(operationEntry); - return () => this.cancelOperation(operationEntry); - }); + return newGuid; + }; + + public static removeLinkQueueEventListener = ( + opName: string, + event: event, + id: string + ) => { + if (event === 'change') opName = ''; + const key: string = QueueLink.key(opName, event); + + if (QueueLink.listeners[key] !== undefined) { + QueueLink.listeners[key] = QueueLink.listeners[key].filter( + (listener) => listener.id !== id + ); + + if (QueueLink.listeners[key].length === 0) { + delete QueueLink.listeners[key]; + } } + }; + + public close() { + this.isOpen = false; + } - private cancelOperation(entry: OperationQueueEntry) { - this.opQueue = this.opQueue.filter(e => e !== entry); + public request(operation: Operation, forward: NextLink) { + if (this.isOpen) { + return forward(operation); } + if (operation.getContext().skipQueue) { + return forward(operation); + } + return new Observable((observer: Observer) => { + const operationEntry = { operation, forward, observer }; + this.enqueue(operationEntry); + return () => this.cancelOperation(operationEntry); + }); + } - private enqueue(entry: OperationQueueEntry) { - this.opQueue.push(entry); + private static key(op: string, ev: string) { + return `${op}${ev}`.toLocaleLowerCase(); + } - const key: string = QueueLink.key(entry.operation.operationName, 'enqueue'); - if (key in QueueLink.listeners) { - QueueLink.listeners[key].forEach((listener) => { - listener(entry); - }); - } + private cancelOperation(entry: OperationQueueEntry) { + this.opQueue = this.opQueue.filter((e) => e !== entry); + } + + private enqueue(entry: OperationQueueEntry) { + this.opQueue.push(entry); + + this.triggerListeners(entry, 'enqueue'); + } + + private triggerListeners(entry: OperationQueueEntry, event: string) { + let key: string = QueueLink.key(entry.operation.operationName, event); + if (key in QueueLink.listeners) { + QueueLink.listeners[key].forEach((listener) => { + listener.callback(entry); + }); + } + key = QueueLink.key('', 'change'); + if (key in QueueLink.listeners) { + QueueLink.listeners[key].forEach((listener) => { + listener.callback(this.opQueue); + }); } + } } diff --git a/src/Utils.ts b/src/Utils.ts new file mode 100644 index 0000000..e2a3963 --- /dev/null +++ b/src/Utils.ts @@ -0,0 +1,7 @@ +export const createGuid = () => { + function _p8(s: boolean) { + const p = (Math.random().toString(16) + '000000000').substr(2, 8); + return s ? '-' + p.substr(0, 4) + '-' + p.substr(4, 4) : p; + } + return _p8(false) + _p8(true) + _p8(true) + _p8(false); +}; \ No newline at end of file diff --git a/tslint.json b/tslint.json index 3703cb6..3841866 100644 --- a/tslint.json +++ b/tslint.json @@ -1,8 +1,6 @@ { "extends": "tslint:latest", "rules": { - "no-unnecessary-type-assertion": true, - "array-type": [true, "array"], "ban-types": { "options": [ @@ -13,13 +11,11 @@ ["String", "Avoid using the `String` type. Did you mean `string`?"] ] }, - "boolean-trivia": true, "class-name": true, "comment-format": [true, "check-space" ], "curly":[true, "ignore-same-line"], - "debug-assert": true, "indent": [true, "spaces" ], @@ -27,24 +23,13 @@ "interface-over-type-literal": true, "jsdoc-format": true, "linebreak-style": [true, "LF"], - "next-line": [true, - "check-catch", - "check-else" - ], - "no-bom": true, - "no-double-space": true, - "no-in-operator": true, - "no-increment-decrement": true, "no-inferrable-types": true, "no-internal-module": true, "no-null-keyword": true, "no-switch-case-fall-through": true, "no-trailing-whitespace": [true, "ignore-template-strings"], - "no-type-assertion-whitespace": true, - "no-unnecessary-qualifier": true, "no-var-keyword": true, "object-literal-shorthand": true, - "object-literal-surrounding-space": true, "one-line": [true, "check-open-brace", "check-whitespace" @@ -57,7 +42,6 @@ "semicolon": [true, "always", "ignore-bound-class-methods"], "space-within-parens": true, "triple-equals": true, - "type-operator-spacing": true, "typedef-whitespace": [ true, { @@ -90,7 +74,6 @@ "arrow-parens": false, "arrow-return-shorthand": false, - "ban-types": false, "forin": false, "member-access": false, "no-conditional-assignment": false, @@ -106,13 +89,11 @@ "prefer-conditional-expression": false, "radix": false, "trailing-comma": false, - "align": false, "eofline": false, "max-line-length": false, "no-consecutive-blank-lines": false, "space-before-function-paren": false, - "ban-comma-operator": false, "max-classes-per-file": false, "member-ordering": false, @@ -122,5 +103,5 @@ "no-reference": false, "object-literal-sort-keys": false, "one-variable-per-declaration": false - } -} + } +} \ No newline at end of file