├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── docs ├── better-dx.gif ├── better-dx.mp4 └── solving-some-from-event-flaws.md ├── package.json ├── prep-publish.sh ├── src ├── package │ ├── compat.ts │ ├── index.ts │ └── types │ │ ├── index.ts │ │ ├── inferences.ts │ │ ├── listener-handler.ts │ │ ├── preset-emitter.ts │ │ └── utils.ts └── spec │ ├── custom-method-and-badly-typed.spec.ts │ ├── dom-mixtyped.spec.ts │ ├── dom-typed.spec.ts │ ├── dom-untyped.spec.ts │ ├── extras.spec.ts │ ├── inferences-length.spec.ts │ ├── inferences.spec.ts │ ├── no-types.spec.ts │ ├── node-mixtyped.spec.ts │ ├── node-typed.spec.ts │ ├── node-untyped.spec.ts │ ├── partially-mixtyped.spec.ts │ ├── random-real-world.spec.ts │ ├── too-many-events.spec.ts │ └── utils.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = tab 3 | indent_size = 4 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | /distribution 4 | /.vscode -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Devansh Jethmalani 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rxjs-from-emitter 2 | 3 | A statically typed, more powerful alternative to RxJS's `fromEvent`. 4 | 5 | ```shell 6 | $ npm i rxjs-from-emitter 7 | ``` 8 | 9 | Also there's [proposal (rxjs #4891)](https://github.com/ReactiveX/rxjs/issues/4891) to put this in RxJS itself 10 | 11 | ## Features & Comparison with RxJS's `fromEvent` 12 | 13 | In the following examples, by "error" I mean compile-time static errors not runtime. Also examples work for all kinds of event emitters not just DOM's `EventTarget` or node's `EventEmitter`. 14 | 15 | ### Observable inferences corresponding to listener's arguments 16 | 17 | ```typescript 18 | fromEvent(document.body, "click") // Observable 19 | fromEmitter(document.body).event("click") // Observable 20 | 21 | fromEvent(spawn("echo", ["hello"]), "exit") // Observable 22 | fromEmitter(spawn("echo", ["hello"])).event("exit") // Observable<[number | null, string | null]> 23 | 24 | const myEmitter = new class { 25 | on( 26 | name: "event-1", 27 | listener: (arg1: "something", arg2: number) => void 28 | ): void 29 | on( 30 | name: "event-2", 31 | listener: (arg1: "onlyOneArgumentSoNoArray") => void 32 | ): void 33 | on(name: string, listener: (...args: any[]) => any ) {} 34 | 35 | off(name: string, listener: Function) {} 36 | } 37 | 38 | fromEvent(myEmitter, "event-1"); // Observable 39 | fromEvent(myEmitter, "event-2"); // Observable 40 | // in strict mode both of them will give unwarrant errors 41 | 42 | fromEmitter(myEmitter).event("event-1"); // Observable<["something", number]> 43 | fromEmitter(myEmitter).event("event-2"); // Observable<"onlyOneArgumentSoNoArray"> 44 | 45 | ``` 46 | 47 | ### Error on invalid event identifiers 48 | 49 | ```typescript 50 | fromEmitter(document.body).event("foo"); 51 | // allowed because you can do document.body.dispatch(new Event("foo")) 52 | 53 | fromEmitter(document.body).eventStrict("foo"); // error 54 | // not allowed in strict version which takes only literals defined in the type. 55 | 56 | fromEmitter(process).event("foo"); 57 | fromEmitter(process).eventStrict("foo") 58 | // error in both because `process.addListener("foo", () => {})` gives error 59 | 60 | fromEvent(document.body, "foo") // no error 61 | fromEvent(process, "foo") // no error 62 | ``` 63 | 64 | ### Extras arguments taken into consideration 65 | 66 | ```typescript 67 | const myEmitter = new class { 68 | on( 69 | name: "event-1", 70 | listener: (arg1: "something", arg2: number) => void, 71 | emitInterval: number 72 | ) {} 73 | 74 | off(name: "event-1", listener: (arg1: "something", arg2: number) => void) {} 75 | } 76 | 77 | fromEmitter(myEmitter).event("event-1"); // error: Expected 2 arguments, but got 1. 78 | fromEmitter(myEmitter).event("event-1", 1000); // no error, Observable<["something", number]> 79 | 80 | fromEvent(myEmitter, "event-1") // no error 81 | ``` 82 | 83 | ### Support for custom method names without any compromise on types 84 | 85 | ```typescript 86 | const myEmitter = new class { 87 | anotherMethod() { } 88 | aProperty = "hello"; 89 | 90 | register(name: "event-1", listener: (arg1: "something", arg2: number) => void) {} 91 | unregister(name: "event-1", listener: (arg1: "something", arg2: number) => void) {} 92 | } 93 | 94 | fromEmitter(myEmitter).event("event-1") // error couldn't identify methods 95 | 96 | fromEmitter(myEmitter) 97 | .withMethods("register", "unregister") 98 | .event("event-1"); 99 | // ok, Observable<["something", number]> 100 | 101 | fromEmitter(myEmitter) 102 | .withMethods("register", "anotherMethod") 103 | .event("event-1"); 104 | // error `anotherMethod` doesn't satisfy the required type 105 | 106 | fromEmitter(myEmitter) 107 | .withMethods("register", "register") 108 | .event("event-1"); 109 | // error, can't pass the same methods. 110 | 111 | const io = require("socket.io")(); 112 | 113 | fromEmitter(io).event("connect") 114 | // error because couldn't find "off" method 115 | 116 | fromEmitter(io) 117 | .withMethods("on", null) 118 | .event("connect"); 119 | // ok, Observable 120 | 121 | // fromEvent supports non of this, you'll have to use fromEventPattern 122 | ``` 123 | 124 | ### Doesn't assume the event identifiers type 125 | 126 | ```typescript 127 | const myEmitter = new class { 128 | on( 129 | event: 0, 130 | listener: (arg1: "something", arg2: number) => void 131 | ): void 132 | on( 133 | event: { type: "foo" }, 134 | listener: (arg1: "onlyOneArgumentSoNoArray") => void 135 | ): void 136 | on(name: string, listener: (...args: any[]) => any ) {} 137 | 138 | off(name: any, listener: Function) {} 139 | } 140 | 141 | fromEmitter(myEmitter).event(0) // no error, Observable<["something", number]> 142 | fromEmitter(myEmitter).event({ type: "foo" }) // no error, Observable<"onlyOneArgumentSoNoArray"> 143 | 144 | fromEvent(myEmitter, 0) // error 145 | ``` 146 | 147 | ### Type-level code, but no hacks 148 | 149 | There is a lot of TypeScript type-level code involved. As a matter of fact type-level code is almost 6 times more than the runtime code. 150 | But there is no hackish stuff like recursive types that drain CPU & RAM and are [also not recommend to be used by the TypeScript folks](https://github.com/microsoft/TypeScript/pull/24897#issuecomment-401418254). They can also break in future [as they broke in past](https://github.com/microsoft/TypeScript/issues/30188#issue-416399563). `rxjs-from-emitter` has none of those. 151 | 152 | ### Auto-completion, better DX 153 | 154 | You get auto-completion for event identifiers, and also since the Observable is correctly inferred you get auto-completions because of that too. 155 | 156 | ![Auto-completion, better DX](docs/better-dx.gif) 157 | 158 | ### Don't like the API? You can still have some features using `fromEvent` and `fromEventStrict` exported from `rxjs-from-emitter/compat`. It's not fully compatible with RxJS's `fromEvent` though 159 | 160 | ```typescript 161 | import { fromEvent } from "rxjs-from-emitter/compat" 162 | 163 | fromEvent(process, "exit" as "exit"); // Observable 164 | ``` 165 | 166 | ### [It also solves some \*cough* flaws \*cough* of `fromEvent`](https://github.com/devanshj/rxjs-from-emitter/blob/master/docs/solving-some-from-event-flaws.md) 167 | 168 | ## What do you lose? 169 | 170 | `fromEvent` can take array of event emitters, but `fromEmitter` takes only one. This is to keep static cheking easier and avoid complexity 171 | 172 | 173 | Tested with TS 3.5 with strict mode, probably won't work for previous versions. For previous versions you can still try writing `"click" as "click"` instead of just `"click"`, it might work. 174 | 175 | Also `document.querySelector` returns `Element`, make sure to make it `HTMLElement` either via assertion (cast) or via type parameter 176 | -------------------------------------------------------------------------------- /docs/better-dx.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devanshj/rxjs-from-emitter/aae00affa5eedbdca6cebe1fa29cb5af6b6c5b41/docs/better-dx.gif -------------------------------------------------------------------------------- /docs/better-dx.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devanshj/rxjs-from-emitter/aae00affa5eedbdca6cebe1fa29cb5af6b6c5b41/docs/better-dx.mp4 -------------------------------------------------------------------------------- /docs/solving-some-from-event-flaws.md: -------------------------------------------------------------------------------- 1 | 2 | # Problems 3 | 4 | Apart from not being type-safe, there are some things that `fromEvent` got wrong on a more design-level. They are as follows – 5 | 6 | Also, I may come across as someone picking on RxJS or it's types. Absolutely not, I love RxJS and am grateful to the smart and selfless folks working on it. I'm criticizing only to show how `fromEmitter` is solving these problems. 7 | 8 | ## The way `fromEvent` checks if the first argument passed is an emitter or not is incorrect 9 | 10 | Let's say you wrote a NodeJS code like – 11 | 12 | ```typescript 13 | const exit$ = fromEvent(process, "exit") 14 | ``` 15 | 16 | You actually get a TypeScript error if you are in strict mode (which is recommended) saying – 17 | 18 | ```plaintext 19 | Argument of type 'Process' is not assignable to parameter of type 'FromEventTarget'. 20 | Type 'Process' is not assignable to type 'JQueryStyleEventEmitter'. 21 | Types of property 'on' are incompatible. 22 | ... 23 | ... 24 | Type 'string' is not assignable to type '"beforeExit"'.ts(2345) 25 | ``` 26 | 27 | Huh? An error? But you can listen to "exit" event with... 28 | 29 | ```typescript 30 | process.addListener("exit", code => { 31 | // do somthing 32 | }) 33 | ``` 34 | 35 | Also `typeof process` should clearly extend [`NodeStyleEventEmitter`](https://github.com/ReactiveX/rxjs/blob/a9fa9d421d69e6e07aec0fa835b273283f8a034c/src/internal/observable/fromEvent.ts#L9-L12) – 36 | 37 | ```typescript 38 | interface NodeStyleEventEmitter { 39 | addListener: (eventName: string | symbol, handler: NodeEventHandler) => this; 40 | removeListener: (eventName: string | symbol, handler: NodeEventHandler) => this; 41 | } 42 | ``` 43 | 44 | Then what's going on :thinking: 45 | 46 | The thing is `process` doesn't extend `NodeStyleEventEmitter`. Because type definition looks like [this](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/d200340ecb521b3856f8bbf6e5b61a33182f9363/types/node/globals.d.ts#L940-L951) – 47 | 48 | ```typescript 49 | interface Process extends EventEmitter { 50 | 51 | ... 52 | addListener(event: "beforeExit", listener: BeforeExitListener): this; 53 | addListener(event: "disconnect", listener: DisconnectListener): this; 54 | addListener(event: "exit", listener: ExitListener): this; 55 | addListener(event: "rejectionHandled", listener: RejectionHandledListener): this; 56 | addListener(event: "uncaughtException", listener: UncaughtExceptionListener): this; 57 | addListener(event: "unhandledRejection", listener: UnhandledRejectionListener): this; 58 | addListener(event: "warning", listener: WarningListener): this; 59 | addListener(event: "message", listener: MessageListener): this; 60 | addListener(event: Signals, listener: SignalsListener): this; 61 | addListener(event: "newListener", listener: NewListenerListener): this; 62 | addListener(event: "removeListener", listener: RemoveListenerListener): this; 63 | addListener(event: "multipleResolves", listener: MultipleResolveListener): this; 64 | ... 65 | 66 | } 67 | ``` 68 | 69 | If you notice none of the overloads have `event: string`. All of them are string literals, they are narrower/more specific that string. Hence they can't extend string and hence `typeof process` can't extend `NodeStyleEventEmitter` 70 | 71 | ```typescript 72 | type IsNodeStyleEventEmitter = typeof process extends NodeStyleEventEmitter ? "yep" : "nope"; 73 | // "nope" 74 | ``` 75 | 76 | Also having string literals is a good practice, they make the user unable to write code like `process.addListener("foo", ...)`. 77 | 78 | This doesn't stop at `process`. You can't use `fromEvent` for anything that is typed like this. Example – 79 | 80 | ```typescript 81 | class MyEmitter { 82 | ... 83 | on(name: "event-1", listener: (event: SomeEvent) => void); 84 | on(name: "event-2", listener: (event: SomeEvent) => void); 85 | ... 86 | off(name: "event-1", listener: (event: SomeEvent) => void); 87 | off(name: "event-2", listener: (event: SomeEvent) => void); 88 | } 89 | ``` 90 | 91 | ## `fromEvent` has different goals 92 | 93 | All the [emitter interfaces](https://github.com/ReactiveX/rxjs/blob/a9fa9d421d69e6e07aec0fa835b273283f8a034c/src/internal/observable/fromEvent.ts#L9-L47) are designed to support well-known emitter styles like `DOM`'s `EventTarget`, node's `EventEmitter`, and jQuery style that is with methods `on` & `off`. 94 | 95 | Even a minor change in the style will make it incompatible with `fromEvent`. For example let's say the you want to use `fromEvent` with a 3rd party event emitter that is jQuery style except `on` takes number as event names instead of string. Now if someone is writing JS, they can use `fromEvent` and it would work because it passes whatever the type is to the `on` method. On the otherhand in TS you'll get a error saying `on` methods are not compatible because it wants string. 96 | 97 | Also in case of DOM style emitters, it actually just assumes that it's DOM's EventTarget and thus it returns observable of type [`Event`](https://github.com/microsoft/TypeScript/blob/3e6856137ad2618dcdfe13ee49a06cca8e4d7ee2/lib/lib.dom.d.ts#L5092-L5099) in lib.dom.d.ts 98 | 99 | ## It's a little too strict 100 | 101 | You know when you create a observable via the constructor, [it's optional to return `TearDownLogic`](https://github.com/ReactiveX/rxjs/blob/01a09789a0a9484c368b7bd6ed37f94d25490a00/src/internal/types.ts#L30). But for event emitters it's compulsory to have a function that removes the listener. A lot of 3rd party event emitters don't have removers. For example there is no `off` method in socket.io's socket, there is only `on`. 102 | 103 | # Solution 104 | 105 | Firstly `fromEmitter` can take any emitters that have the style – 106 | 107 | ```typescript 108 | { 109 | [addMethod]: ( 110 | eventIdentifier: any, 111 | listener: (...args: any[]) => any, 112 | ...extras: any[] 113 | ) => any 114 | 115 | [removeMethod]?: ( 116 | eventIdentifier: any, 117 | listener: (...args: any[]) => any, 118 | ...extra: any[] 119 | ) => any 120 | } 121 | ``` 122 | 123 | The user has to provide name of addMethod and name of removeMethod or `null` via the `withMethods` method. But only when we can't find the popular pairs like `on` & `off` and others. 124 | 125 | The style doesn't care about any things that it doesn't depend on. For example the return types, extra arguments etc. The style is just strict enough to not cause runtime errors. 126 | 127 | This little (or huge xD) change in the design solves all the above problems. 128 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rxjs-from-emitter", 3 | "version": "0.0.9", 4 | "description": "A statically typed, more powerful alternative to RxJS's `fromEvent`", 5 | "main": "index.js", 6 | "peerDependencies": { 7 | "rxjs": "^6.5.2", 8 | "typescript": "^3.5.2" 9 | }, 10 | "devDependencies": { 11 | "@types/node": "^12.7.2", 12 | "@types/socket.io": "^2.1.2", 13 | "chokidar": "^3.0.2", 14 | "rxjs": "^6.5.2", 15 | "typescript": "^3.5.3" 16 | }, 17 | "keywords": [ 18 | "RxJS", 19 | "TypeScript" 20 | ], 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/devanshj/rxjs-from-emitter.git" 24 | }, 25 | "author": "Devansh Jethmalani", 26 | "license": "MIT" 27 | } 28 | -------------------------------------------------------------------------------- /prep-publish.sh: -------------------------------------------------------------------------------- 1 | read -p "you sure? (y/*) " shouldContinue 2 | if [ "$shouldContinue" != "y" ]; then 3 | exit 4 | fi 5 | 6 | tsc --noEmitOnError 7 | if [ "$?" != "0" ]; then 8 | echo "tsc exited with non-zero code" 9 | exit 10 | fi 11 | 12 | cp -r dist/package/ distribution/ 13 | cp LICENSE distribution/LICENSE 14 | cp README.md distribution/README.md 15 | cp package.json distribution/package.json -------------------------------------------------------------------------------- /src/package/compat.ts: -------------------------------------------------------------------------------- 1 | import { EventIdentifier, EventIdentifierStrict, ObservedValue } from "./types"; 2 | import { Observable, fromEvent as rxjsFromEvent } from "rxjs"; 3 | import { DomEmitter, JqueryEmitter, NodeEmitter } from "./types/preset-emitter"; 4 | 5 | type Method = 6 | E extends DomEmitter ? "addEventListener" : 7 | E extends NodeEmitter ? "addListener" : 8 | E extends JqueryEmitter ? "on" : 9 | never 10 | 11 | type RxjsFromEvent = { 12 | < 13 | E extends DomEmitter | JqueryEmitter | NodeEmitter, 14 | N extends ( 15 | S extends true 16 | ? EventIdentifierStrict> 17 | : EventIdentifier> 18 | ), 19 | X extends ( 20 | E extends DomEmitter 21 | ? [(boolean | AddEventListenerOptions)?] 22 | : [] 23 | ) 24 | > 25 | (emitter: E, event: N, ...options: X): 26 | Observable, N>>; 27 | 28 | ( 29 | emitter: 30 | | DomEmitter 31 | | JqueryEmitter 32 | | NodeEmitter, 33 | event: string | symbol, 34 | options?: boolean | AddEventListenerOptions 35 | ): 36 | Observable; 37 | } 38 | 39 | export const fromEvent = ( 40 | (emitter: any, event: any, options: any) => 41 | rxjsFromEvent(emitter, event, options) 42 | ) as RxjsFromEvent; 43 | 44 | export const fromEventStrict = ( 45 | (emitter: any, event: any, options: any) => 46 | rxjsFromEvent(emitter, event, options) 47 | ) as RxjsFromEvent; -------------------------------------------------------------------------------- /src/package/index.ts: -------------------------------------------------------------------------------- 1 | import { EventIdentifier, EventExtras, ObservedValue, EventIdentifierStrict, Method } from "./types"; 2 | import { Observable } from "rxjs" 3 | import { DomEmitter, NodeEmitter, JqueryEmitter } from "./types/preset-emitter"; 4 | import { ListenerHandlerKey } from "./types/listener-handler"; 5 | import { AreEqual } from "./types/utils"; 6 | 7 | 8 | 9 | type FromEmitter = 10 | (emitter: E) => 11 | & { withMethods: WithMethods } 12 | & ( 13 | AreEqual, never> extends false 14 | ? { 15 | event: EventSelector>, 16 | eventStrict: EventSelectorStrict> 17 | } 18 | : {} 19 | ); 20 | 21 | type EventSelector = 22 | > 23 | (identifier: I, ...extras: EventExtras) 24 | => Observable>; 25 | 26 | type EventSelectorStrict = 27 | > 28 | (identifier: I, ...extras: EventExtras) 29 | => Observable>; 30 | 31 | type WithMethods = 32 | < 33 | A extends ListenerHandlerKey & string, 34 | R extends 35 | | Exclude, A> & string 36 | | null 37 | >( 38 | addListenerMethodName: A, 39 | removeListenerMethodName: R 40 | ) => 41 | { 42 | event: EventSelector 43 | eventStrict: EventSelectorStrict 44 | }; 45 | 46 | export const fromEmitter = ( 47 | (emitter: any) => 48 | ({ 49 | withMethods: (a: string, r: string | null) => ({ 50 | event: (identifier: any, ...extras: any[]) => 51 | fromEvent( 52 | emitter, 53 | [a, r], 54 | identifier, 55 | ...extras 56 | ), 57 | eventStrict: (identifier: any, ...extras: any[]) => 58 | fromEvent( 59 | emitter, 60 | [a, r], 61 | identifier, 62 | ...extras 63 | ) 64 | }), 65 | ...( 66 | isDomEmitter(emitter) ? { 67 | event: (identifier: any, ...extras: any[]) => 68 | fromEvent( 69 | emitter, 70 | ["addEventListener", "removeEventListener"], 71 | identifier, 72 | ...extras 73 | ), 74 | eventStrict: (identifier: any, ...extras: any[]) => 75 | fromEvent( 76 | emitter, 77 | ["addEventListener", "removeEventListener"], 78 | identifier, 79 | ...extras 80 | ) 81 | } : 82 | 83 | isNodeEmitter(emitter) ? { 84 | event: (identifier: any, ...extras: any[]) => 85 | fromEvent( 86 | emitter, 87 | ["addListener", "removeListener"], 88 | identifier, 89 | ...extras 90 | ), 91 | eventStrict: (identifier: any, ...extras: any[]) => 92 | fromEvent( 93 | emitter, 94 | ["addListener", "removeListener"], 95 | identifier, 96 | ...extras 97 | ) 98 | } : 99 | 100 | isJqueryEmitter(emitter) ? { 101 | event: (identifier: any, ...extras: any[]) => 102 | fromEvent( 103 | emitter, 104 | ["on", "off"], 105 | identifier, 106 | ...extras 107 | ), 108 | eventStrict: (identifier: any, ...extras: any[]) => 109 | fromEvent( 110 | emitter, 111 | ["on", "off"], 112 | identifier, 113 | ...extras 114 | ) 115 | } : 116 | 117 | {} 118 | ) 119 | }) 120 | ) as FromEmitter; 121 | 122 | const fromEvent = ( 123 | emitter: any, 124 | methods: [string, string | null], 125 | eventIdentifier: any, 126 | ...extras: any[] 127 | ) => 128 | new Observable(observer => { 129 | const listener = 130 | (...args: any[]) => 131 | observer.next( 132 | args.length > 1 133 | ? args 134 | : args[0] 135 | ); 136 | emitter[methods[0]](eventIdentifier, listener, ...extras); 137 | 138 | () => methods[1] && emitter[methods[1]](eventIdentifier, listener); 139 | }) 140 | 141 | const isDomEmitter = 142 | (emitter: any): emitter is DomEmitter => 143 | emitter && 144 | typeof emitter.addEventListener === "function" && 145 | typeof emitter.removeEventListener === "function"; 146 | 147 | const isNodeEmitter = 148 | (emitter: any): emitter is NodeEmitter => 149 | emitter && 150 | typeof emitter.addListener === "function" && 151 | typeof emitter.removeListener === "function"; 152 | 153 | const isJqueryEmitter = 154 | (emitter: any): emitter is JqueryEmitter => 155 | emitter && 156 | typeof emitter.on === "function" && 157 | typeof emitter.off === "function"; 158 | 159 | export { EventIdentifier, EventIdentifierStrict, EventExtras, ObservedValue } -------------------------------------------------------------------------------- /src/package/types/index.ts: -------------------------------------------------------------------------------- 1 | import { And, AreEqual, IsLiteral, IsLessThan15, IsAnyArray, DoesExtend, IsUnion, Not, AssertedProp } from "./utils"; 2 | import { Inferences, InferencesLength } from "./inferences"; 3 | import { DomEmitter, NodeEmitter, JqueryEmitter } from "./preset-emitter"; 4 | 5 | 6 | // ------------------------- 7 | // Method 8 | 9 | export type Method = 10 | E extends JqueryEmitter ? 11 | "on" : 12 | E extends NodeEmitter ? 13 | "addListener" : 14 | E extends DomEmitter ? 15 | "addEventListener" : 16 | never; 17 | 18 | 19 | // ------------------------- 20 | // EventIdentifier 21 | 22 | type _EventIdentifier = 23 | | Inferences[number]["I"] 24 | | ( 25 | And<[ 26 | E extends HTMLElement ? true : false, 27 | M extends "addEventListener" ? true : false 28 | ]> extends true 29 | ? keyof HTMLElementEventMap 30 | : never 31 | ); 32 | 33 | export type EventIdentifier = 34 | | _EventIdentifier 35 | | ( 36 | IsLessThan15>> extends true 37 | ? never 38 | : unknown 39 | ); 40 | 41 | // ------------------------- 42 | // EventIdentifierStrict 43 | 44 | export type __EventIdentifierStrict< 45 | E, 46 | M extends string, 47 | G extends Inferences = Inferences 48 | > = 49 | | { 50 | [K in keyof G]: 51 | G[K] extends { "I": infer I } 52 | ? IsLiteral extends true 53 | ? I 54 | : never 55 | : never 56 | }[number] 57 | | ( 58 | And<[ 59 | E extends HTMLElement ? true : false, 60 | M extends "addEventListener" ? true : false 61 | ]> extends true 62 | ? { 63 | [I in keyof HTMLElementEventMap]: 64 | IsLiteral extends true 65 | ? I 66 | : never 67 | }[keyof HTMLElementEventMap] 68 | : never 69 | ); 70 | 71 | type _EventIdentifierStrict< 72 | E, 73 | M extends string, 74 | G extends Inferences = Inferences 75 | > = 76 | | __EventIdentifierStrict 77 | | ( 78 | IsLessThan15> extends true 79 | ? never 80 | : unknown 81 | ); 82 | 83 | export type EventIdentifierStrict = 84 | _EventIdentifierStrict; 85 | 86 | // ------------------------- 87 | // EventExtras 88 | 89 | type _EventExtras = 90 | Exclude[number]["X"], never[]>; 91 | 92 | export type EventExtras = 93 | _EventExtras extends never 94 | ? [] 95 | : _EventExtras; 96 | 97 | 98 | // ------------------------- 99 | // ObservedValue 100 | 101 | type _ObservedValue< 102 | E, 103 | M extends string, 104 | I extends EventIdentifier, 105 | G extends Inferences = Inferences 106 | > = 107 | TransformArgs< 108 | | { 109 | [K in keyof G]: 110 | G[K] extends { "I": infer GI, "A": infer GA } 111 | ? AreEqual extends true 112 | ? GA 113 | : never 114 | : never 115 | }[number] 116 | | { 117 | [K in keyof G]: 118 | G[K] extends { "I": infer GI, "A": infer GA } 119 | ? And<[ 120 | IsLiteral, 121 | IsLiteral, 122 | DoesExtend, 123 | Not>> 124 | ]> extends true 125 | ? GA 126 | : never 127 | : never 128 | }[number] 129 | | (I extends __EventIdentifierStrict 130 | ? never 131 | : { 132 | [K in keyof G]: 133 | G[K] extends { "I": infer GI, "A": infer GA } 134 | ? I extends GI 135 | ? GA 136 | : never 137 | : never 138 | }[number] 139 | ) 140 | | ( 141 | I extends __EventIdentifierStrict 142 | ? never 143 | : IsLessThan15> extends true 144 | ? never 145 | : any[] 146 | ) 147 | | ( 148 | And<[ 149 | E extends HTMLElement ? true : false, 150 | M extends "addEventListener" ? true : false 151 | ]> extends true 152 | ? I extends keyof HTMLElementEventMap 153 | ? [HTMLElementEventMap[I]] 154 | : never 155 | : never 156 | ) 157 | >; 158 | 159 | export type ObservedValue< 160 | E, 161 | M extends string, 162 | I extends EventIdentifier 163 | > = 164 | _ObservedValue 165 | 166 | type TransformArgs = 167 | IsAnyArray extends true 168 | ? unknown : 169 | A extends any[] 170 | ? A["length"] extends 0 171 | ? never 172 | : A["length"] extends 1 173 | ? A[0] 174 | : A : 175 | never; -------------------------------------------------------------------------------- /src/package/types/inferences.ts: -------------------------------------------------------------------------------- 1 | import { ListenerHandler } from "./listener-handler"; 2 | import { AreEqual, AreExact, MathMax, ToNumber, SubtractFrom15, Add1 } from "./utils"; 3 | 4 | export type Inferences = Exclude<{ 5 | [K in keyof E]: 6 | K extends M 7 | ? E[K] extends ListenerHandler< 8 | infer I0, infer A0, infer X0, 9 | infer I1, infer A1, infer X1, 10 | infer I2, infer A2, infer X2, 11 | infer I3, infer A3, infer X3, 12 | infer I4, infer A4, infer X4, 13 | infer I5, infer A5, infer X5, 14 | infer I6, infer A6, infer X6, 15 | infer I7, infer A7, infer X7, 16 | infer I8, infer A8, infer X8, 17 | infer I9, infer A9, infer X9, 18 | infer I10, infer A10, infer X10, 19 | infer I11, infer A11, infer X11, 20 | infer I12, infer A12, infer X12, 21 | infer I13, infer A13, infer X13, 22 | infer I14, infer A14, infer X14 23 | > 24 | ? [ 25 | TransfromInference<{ "I": I0, "A": A0, "X": X0 }>, 26 | TransfromInference<{ "I": I1, "A": A1, "X": X1 }>, 27 | TransfromInference<{ "I": I2, "A": A2, "X": X2 }>, 28 | TransfromInference<{ "I": I3, "A": A3, "X": X3 }>, 29 | TransfromInference<{ "I": I4, "A": A4, "X": X4 }>, 30 | TransfromInference<{ "I": I5, "A": A5, "X": X5 }>, 31 | TransfromInference<{ "I": I6, "A": A6, "X": X6 }>, 32 | TransfromInference<{ "I": I7, "A": A7, "X": X7 }>, 33 | TransfromInference<{ "I": I8, "A": A8, "X": X8 }>, 34 | TransfromInference<{ "I": I9, "A": A9, "X": X9 }>, 35 | TransfromInference<{ "I": I10, "A": A10, "X": X10 }>, 36 | TransfromInference<{ "I": I11, "A": A11, "X": X11 }>, 37 | TransfromInference<{ "I": I12, "A": A12, "X": X12 }>, 38 | TransfromInference<{ "I": I13, "A": A13, "X": X13 }>, 39 | TransfromInference<{ "I": I14, "A": A14, "X": X14 }>, 40 | ] 41 | : never 42 | : never 43 | }[keyof E], undefined>; 44 | 45 | export type EmptyInference = 46 | { "I": never, "A": never[], "X": never[] }; 47 | 48 | type TransfromInference = 49 | AreEqual extends true 50 | ? EmptyInference 51 | : T; 52 | 53 | export type InferencesLength< 54 | G extends unknown[] 55 | > = 56 | SubtractFrom15< 57 | MathMax<{ 58 | [K in keyof G]: 59 | AreExact extends true 60 | ? Add1> 61 | : never 62 | }> 63 | >; -------------------------------------------------------------------------------- /src/package/types/listener-handler.ts: -------------------------------------------------------------------------------- 1 | import { AreEqual } from "./utils"; 2 | 3 | export type ListenerHandler< 4 | I0 extends any, A0 extends any[], X0 extends any[], 5 | I1 extends any, A1 extends any[], X1 extends any[], 6 | I2 extends any, A2 extends any[], X2 extends any[], 7 | I3 extends any, A3 extends any[], X3 extends any[], 8 | I4 extends any, A4 extends any[], X4 extends any[], 9 | I5 extends any, A5 extends any[], X5 extends any[], 10 | I6 extends any, A6 extends any[], X6 extends any[], 11 | I7 extends any, A7 extends any[], X7 extends any[], 12 | I8 extends any, A8 extends any[], X8 extends any[], 13 | I9 extends any, A9 extends any[], X9 extends any[], 14 | I10 extends any, A10 extends any[], X10 extends any[], 15 | I11 extends any, A11 extends any[], X11 extends any[], 16 | I12 extends any, A12 extends any[], X12 extends any[], 17 | I13 extends any, A13 extends any[], X13 extends any[], 18 | I14 extends any, A14 extends any[], X14 extends any[], 19 | > = { 20 | 21 | ( 22 | identifier: I0, 23 | listener: (...args: A0) => any, 24 | ...extras: X0 25 | ): any; 26 | 27 | ( 28 | identifier: I1, 29 | listener: (...args: A1) => any, 30 | ...extras: X1 31 | ): any; 32 | 33 | ( 34 | identifier: I2, 35 | listener: (...args: A2) => any, 36 | ...extras: X2 37 | ): any; 38 | 39 | ( 40 | identifier: I3, 41 | listener: (...args: A3) => any, 42 | ...extras: X3 43 | ): any; 44 | 45 | ( 46 | identifier: I4, 47 | listener: (...args: A4) => any, 48 | ...extras: X4 49 | ): any; 50 | 51 | ( 52 | identifier: I5, 53 | listener: (...args: A5) => any, 54 | ...extras: X5 55 | ): any; 56 | 57 | ( 58 | identifier: I6, 59 | listener: (...args: A6) => any, 60 | ...extras: X6 61 | ): any; 62 | 63 | ( 64 | identifier: I7, 65 | listener: (...args: A7) => any, 66 | ...extras: X7 67 | ): any; 68 | 69 | ( 70 | identifier: I8, 71 | listener: (...args: A8) => any, 72 | ...extras: X8 73 | ): any; 74 | 75 | ( 76 | identifier: I9, 77 | listener: (...args: A9) => any, 78 | ...extras: X9 79 | ): any; 80 | 81 | ( 82 | identifier: I10, 83 | listener: (...args: A10) => any, 84 | ...extras: X10 85 | ): any; 86 | 87 | ( 88 | identifier: I11, 89 | listener: (...args: A11) => any, 90 | ...extras: X11 91 | ): any; 92 | 93 | ( 94 | identifier: I12, 95 | listener: (...args: A12) => any, 96 | ...extras: X12 97 | ): any; 98 | 99 | ( 100 | identifier: I13, 101 | listener: (...args: A13) => any, 102 | ...extras: X13 103 | ): any; 104 | 105 | ( 106 | identifier: I14, 107 | listener: (...args: A14) => any, 108 | ...extras: X14 109 | ): any; 110 | 111 | } 112 | 113 | type IsListenerHandler = 114 | AreEqual any, 117 | ...extras: any[] 118 | ) => any>; 119 | 120 | export type ListenerHandlerKey = { 121 | [K in keyof T]: 122 | IsListenerHandler extends true 123 | ? K 124 | : never 125 | }[keyof T]; -------------------------------------------------------------------------------- /src/package/types/preset-emitter.ts: -------------------------------------------------------------------------------- 1 | export type DomEmitter = { 2 | addEventListener( 3 | identifier: any, 4 | listener: (...args: any[]) => any, 5 | ...extras: any[] 6 | ): any; 7 | 8 | removeEventListener( 9 | identifier: any, 10 | listener: (...args: any[]) => any, 11 | ...extras: any[] 12 | ): any; 13 | } 14 | 15 | export type NodeEmitter = { 16 | addListener( 17 | identifier: any, 18 | listener: (...args: any[]) => any, 19 | ...extras: any[] 20 | ): any; 21 | 22 | removeListener( 23 | identifier: any, 24 | listener: (...args: any[]) => any, 25 | ...extras: any[] 26 | ): any; 27 | } 28 | 29 | export type JqueryEmitter = { 30 | on( 31 | identifier: any, 32 | listener: (...args: any[]) => any, 33 | ...extras: any[] 34 | ): any; 35 | 36 | off( 37 | identifier: any, 38 | listener: (...args: any[]) => any, 39 | ...extras: any[] 40 | ): any; 41 | } -------------------------------------------------------------------------------- /src/package/types/utils.ts: -------------------------------------------------------------------------------- 1 | export type And = 2 | AreEqual; 3 | 4 | export type Or = 5 | AreEqual extends true 6 | ? false 7 | : true; 8 | 9 | export type Not = 10 | AreEqual extends true ? false : 11 | AreEqual extends true ? true : 12 | boolean; 13 | 14 | export type AreEqual = 15 | IsAny extends true 16 | ? IsAny extends true 17 | ? true 18 | : false : 19 | IsAny extends true 20 | ? IsAny extends true 21 | ? true 22 | : false : 23 | _AreEqual; 24 | 25 | export type DoesExtend = 26 | A extends B 27 | ? true 28 | : false; 29 | 30 | export type IsAny = 31 | _AreEqual< 32 | _AreEqual, 33 | boolean 34 | >; 35 | 36 | type _AreEqual = 37 | Exclude extends never 38 | ? Exclude extends never 39 | ? true 40 | : false 41 | : false; 42 | 43 | export type IsStringLiteral = 44 | T extends string 45 | ? string extends T 46 | ? false 47 | : true 48 | : false; 49 | 50 | export type IsSymbolLiteral = 51 | T extends symbol 52 | ? symbol extends T 53 | ? false 54 | : true 55 | : false; 56 | 57 | export type IsNumberLiteral = 58 | T extends symbol 59 | ? symbol extends T 60 | ? false 61 | : true 62 | : false; 63 | 64 | export type IsLiteral = 65 | Or<[ 66 | IsStringLiteral, 67 | IsNumberLiteral, 68 | IsSymbolLiteral 69 | ]>; 70 | 71 | // https://github.com/Microsoft/TypeScript/issues/27024#issuecomment-421529650 72 | export type AreExact = 73 | (() => T extends A ? 0 : 1) extends (() => T extends B ? 0 : 1) 74 | ? true 75 | : false; 76 | 77 | export type IsAnyArray = 78 | T extends any[] 79 | ? IsAny extends true 80 | ? AreEqual extends true 81 | ? true 82 | : false 83 | : false 84 | : false; 85 | 86 | export type IsUnion = Not>> 87 | 88 | // jcalz https://stackoverflow.com/a/50375286/9591609 89 | type UnionToIntersection = 90 | (T extends any ? (_: T) => void : never) extends ((_: infer I) => void) 91 | ? I 92 | : never 93 | 94 | export type IsLessThan15 = 95 | T extends 0 ? true : 96 | T extends 1 ? true : 97 | T extends 2 ? true : 98 | T extends 3 ? true : 99 | T extends 4 ? true : 100 | T extends 5 ? true : 101 | T extends 6 ? true : 102 | T extends 7 ? true : 103 | T extends 8 ? true : 104 | T extends 9 ? true : 105 | T extends 10 ? true : 106 | T extends 11 ? true : 107 | T extends 12 ? true : 108 | T extends 13 ? true : 109 | T extends 14 ? true : 110 | false 111 | 112 | export type ToNumber = 113 | T extends "0" ? 0 : 114 | T extends "1" ? 1 : 115 | T extends "2" ? 2 : 116 | T extends "3" ? 3 : 117 | T extends "4" ? 4 : 118 | T extends "5" ? 5 : 119 | T extends "6" ? 6 : 120 | T extends "7" ? 7 : 121 | T extends "8" ? 8 : 122 | T extends "9" ? 9 : 123 | T extends "10" ? 10 : 124 | T extends "11" ? 11 : 125 | T extends "12" ? 12 : 126 | T extends "13" ? 13 : 127 | T extends "14" ? 14 : 128 | T extends "15" ? 15 : 129 | number; 130 | 131 | export type MathMax = 132 | 15 extends Xs[number] ? 15 : 133 | 14 extends Xs[number] ? 14 : 134 | 13 extends Xs[number] ? 13 : 135 | 12 extends Xs[number] ? 12 : 136 | 11 extends Xs[number] ? 11 : 137 | 10 extends Xs[number] ? 10 : 138 | 9 extends Xs[number] ? 9 : 139 | 8 extends Xs[number] ? 8 : 140 | 7 extends Xs[number] ? 7 : 141 | 6 extends Xs[number] ? 6 : 142 | 5 extends Xs[number] ? 5 : 143 | 4 extends Xs[number] ? 4 : 144 | 3 extends Xs[number] ? 3 : 145 | 2 extends Xs[number] ? 2 : 146 | 1 extends Xs[number] ? 1 : 147 | 0 extends Xs[number] ? 0 : 148 | number; 149 | 150 | export type SubtractFrom15 = 151 | 0 extends T ? 15 : 152 | 1 extends T ? 14 : 153 | 2 extends T ? 13 : 154 | 3 extends T ? 12 : 155 | 4 extends T ? 11 : 156 | 5 extends T ? 10 : 157 | 6 extends T ? 9 : 158 | 7 extends T ? 8 : 159 | 8 extends T ? 7 : 160 | 9 extends T ? 6 : 161 | 10 extends T ? 5 : 162 | 11 extends T ? 4 : 163 | 12 extends T ? 3 : 164 | 13 extends T ? 2 : 165 | 14 extends T ? 1 : 166 | 15 extends T ? 0 : 167 | number; 168 | 169 | export type Add1 = 170 | 0 extends T ? 1 : 171 | 1 extends T ? 2 : 172 | 2 extends T ? 3 : 173 | 3 extends T ? 4 : 174 | 4 extends T ? 5 : 175 | 5 extends T ? 6 : 176 | 6 extends T ? 7 : 177 | 7 extends T ? 8 : 178 | 8 extends T ? 9 : 179 | 9 extends T ? 10 : 180 | 10 extends T ? 11 : 181 | 11 extends T ? 12 : 182 | 12 extends T ? 13 : 183 | 13 extends T ? 14 : 184 | 14 extends T ? 15 : 185 | 15 extends T ? 16 : 186 | number; 187 | 188 | export type AssertedProp = 189 | P extends keyof T 190 | ? T[P] 191 | : F; -------------------------------------------------------------------------------- /src/spec/custom-method-and-badly-typed.spec.ts: -------------------------------------------------------------------------------- 1 | import { EventExtras, EventIdentifier, EventIdentifierStrict, ObservedValue } from "../package"; 2 | import { AssertTrue, AreEqual, Method } from "./utils"; 3 | import { ListenerHandlerKey } from "../package/types/listener-handler"; 4 | import { AreExact } from "../package/types/utils"; 5 | 6 | class Emitter { 7 | register( 8 | eventName: string, 9 | listener: Function 10 | ): void; 11 | 12 | register( 13 | eventName: "a", 14 | listener: (a0: "a", a1: "b") => void 15 | ): true 16 | 17 | register( 18 | eventName: { foo: "b" }, 19 | listener: (a0: any, a1: "c") => void 20 | ): { "foo": "foo" } 21 | 22 | register( 23 | eventName: any, 24 | listener: any 25 | ): true | { "foo": "foo" } { 26 | return Math.random() ? true : { "foo": "foo" } ; 27 | } 28 | 29 | unregister( 30 | eventName: string, 31 | listener: Function 32 | ) {} 33 | 34 | somethingElse( 35 | foo: string, 36 | bar: Function 37 | ) {} 38 | 39 | somethingElseElse( 40 | bar: string, 41 | baz: number 42 | ) {} 43 | 44 | foobar() {} 45 | } 46 | 47 | type ExpectedEventIdentifier = 48 | | string 49 | | "a" 50 | | { foo: "b" }; 51 | type ExpectedEventIdentifierStrict = "a"; 52 | type ExpectedObservedValue = 53 | I extends "a" ? ["a", "b"] : 54 | I extends { foo: "b" } ? [any, "c"]: 55 | unknown; 56 | 57 | type ExpectedListenerHandlerKey = 58 | | "unregister" 59 | | "register" 60 | | "somethingElse" 61 | 62 | type E = Emitter; 63 | type M = "register"; 64 | type ActualEventIdentifier = EventIdentifier; 65 | type ActualEventIdentifierStrict = EventIdentifierStrict; 66 | type ActualObservedValue> = ObservedValue; 67 | type ActualListenerHandlerKey = ListenerHandlerKey; 68 | 69 | type debug = AreExact<[any, "c"], any[]> 70 | 71 | type Tests = [ 72 | AreEqual< 73 | ExpectedEventIdentifier, 74 | ActualEventIdentifier 75 | >, 76 | AreEqual< 77 | ExpectedEventIdentifierStrict, 78 | ActualEventIdentifierStrict 79 | >, 80 | AreEqual< 81 | ExpectedObservedValue<"a">, 82 | ActualObservedValue<"a"> 83 | >, 84 | AreEqual< 85 | ExpectedObservedValue<"b">, 86 | ActualObservedValue<"b"> 87 | >, 88 | AreEqual< 89 | ExpectedObservedValue<"non-existent-event">, 90 | ActualObservedValue<"non-existent-event"> 91 | >, 92 | AreEqual< 93 | ExpectedListenerHandlerKey, 94 | ActualListenerHandlerKey 95 | > 96 | ]; 97 | 98 | type Works = AssertTrue -------------------------------------------------------------------------------- /src/spec/dom-mixtyped.spec.ts: -------------------------------------------------------------------------------- 1 | import { ObservedValue, EventIdentifier, EventIdentifierStrict } from "../package"; 2 | import { AssertTrue, AreEqual, Method } from "./utils"; 3 | 4 | export class CustomEventA { private _ = "" } 5 | export class CustomEventB { private _ = "" } 6 | 7 | export class MixtypedDOMEmitter { 8 | 9 | addEventListener( 10 | eventIdentifier: string, 11 | listener: (...args: any[]) => void 12 | ): void; 13 | 14 | addEventListener( 15 | eventIdentifier: "event-a", 16 | listener: (event: CustomEventA) => void 17 | ): void; 18 | 19 | addEventListener( 20 | eventIdentifier: "event-b", 21 | listener: (event: CustomEventB) => void 22 | ): void; 23 | 24 | addEventListener( 25 | eventIdentifier: 26 | | "event-a" 27 | | "event-b", 28 | listener: 29 | | ((event: CustomEventA) => void) 30 | | ((event: CustomEventB) => void) 31 | ) {} 32 | 33 | removeEventListener( 34 | eventIdentifier: string, 35 | listener: (...args: any[]) => void 36 | ): void; 37 | 38 | removeEventListener( 39 | eventIdentifier: "event-a", 40 | listener: (event: CustomEventA) => void 41 | ): void; 42 | 43 | removeEventListener( 44 | eventIdentifier: "event-b", 45 | listener: (event: CustomEventB) => void 46 | ): void; 47 | 48 | removeEventListener( 49 | eventIdentifier: 50 | | "event-a" 51 | | "event-b", 52 | listener: 53 | | ((event: CustomEventA) => void) 54 | | ((event: CustomEventB) => void) 55 | ) {} 56 | } 57 | 58 | type ExpectedEventIdentifier = 59 | | "event-a" 60 | | "event-b" 61 | | string; 62 | 63 | type ExpectedStrictEventIdentifier = 64 | | "event-a" 65 | | "event-b"; 66 | 67 | type ExpectedObservedValue = 68 | I extends "event-a" ? CustomEventA : 69 | I extends "event-b" ? CustomEventB : 70 | I extends string ? unknown : 71 | never; 72 | 73 | type E = MixtypedDOMEmitter; 74 | type M = Method 75 | type ActualEventIdentifier = EventIdentifier; 76 | type ActualEventIdentifierStrict = EventIdentifierStrict; 77 | type ActualObservedValue> = ObservedValue; 78 | 79 | type Tests = [ 80 | AreEqual< 81 | ExpectedEventIdentifier, 82 | ActualEventIdentifier 83 | >, 84 | AreEqual< 85 | ExpectedStrictEventIdentifier, 86 | ActualEventIdentifierStrict 87 | >, 88 | AreEqual< 89 | ExpectedObservedValue<"event-a">, 90 | ActualObservedValue<"event-a"> 91 | >, 92 | AreEqual< 93 | ExpectedObservedValue<"event-b">, 94 | ActualObservedValue<"event-b"> 95 | >, 96 | AreEqual< 97 | ExpectedObservedValue, 98 | ActualObservedValue 99 | >, 100 | AreEqual< 101 | ExpectedObservedValue<"non-existent-event">, 102 | ActualObservedValue<"non-existent-event"> 103 | > 104 | ]; 105 | 106 | type Works = AssertTrue -------------------------------------------------------------------------------- /src/spec/dom-typed.spec.ts: -------------------------------------------------------------------------------- 1 | import { EventIdentifier, EventIdentifierStrict, ObservedValue } from "../package"; 2 | import { AssertTrue, Method, AreEqual } from "./utils"; 3 | 4 | class CustomEventA { private _ = "" } 5 | class CustomEventB { private _ = "" } 6 | 7 | class TypedDOMEmitter { 8 | 9 | addEventListener( 10 | eventName: "event-a", 11 | listener: (event: CustomEventA) => void 12 | ): void; 13 | 14 | addEventListener( 15 | eventName: "event-b", 16 | listener: (event: CustomEventB) => void 17 | ): void; 18 | 19 | addEventListener( 20 | eventName: 21 | | "event-a" 22 | | "event-b", 23 | listener: 24 | | ((event: CustomEventA) => void) 25 | | ((event: CustomEventB) => void) 26 | ) {} 27 | 28 | removeEventListener( 29 | eventName: "event-a", 30 | listener: (event: CustomEventA) => void 31 | ): void; 32 | 33 | removeEventListener( 34 | eventName: "event-b", 35 | listener: (event: CustomEventB) => void 36 | ): void; 37 | 38 | removeEventListener( 39 | eventName: 40 | | "event-a" 41 | | "event-b", 42 | listener: 43 | | ((event: CustomEventA) => void) 44 | | ((event: CustomEventB) => void) 45 | ) {} 46 | } 47 | 48 | type ExpectedEventIdentifier = 49 | | "event-a" 50 | | "event-b"; 51 | 52 | type ExpectedEventIdentifierStrict = 53 | | "event-a" 54 | | "event-b"; 55 | 56 | type ExpectedObservedValue = 57 | I extends "event-a" ? CustomEventA : 58 | I extends "event-b" ? CustomEventB : 59 | never; 60 | 61 | type E = TypedDOMEmitter; 62 | type M = Method; 63 | type ActualEventIdentifier = EventIdentifier; 64 | type ActualEventIdentifierStrict = EventIdentifierStrict; 65 | type ActualObservedValue> = ObservedValue; 66 | 67 | type Tests = [ 68 | AreEqual< 69 | ExpectedEventIdentifier, 70 | ActualEventIdentifier 71 | >, 72 | AreEqual< 73 | ExpectedEventIdentifierStrict, 74 | ActualEventIdentifierStrict 75 | >, 76 | AreEqual< 77 | ExpectedObservedValue<"event-a">, 78 | ActualObservedValue<"event-a"> 79 | >, 80 | AreEqual< 81 | ExpectedObservedValue<"event-b">, 82 | ActualObservedValue<"event-b"> 83 | > 84 | ]; 85 | 86 | type Works = AssertTrue; -------------------------------------------------------------------------------- /src/spec/dom-untyped.spec.ts: -------------------------------------------------------------------------------- 1 | import { EventIdentifier, ObservedValue, EventIdentifierStrict } from "../package"; 2 | import { AssertTrue, AreEqual, Method } from "./utils"; 3 | 4 | class UntypedDOMEmitter { 5 | 6 | addEventListener( 7 | eventName: string, 8 | listener: (event: any) => void 9 | ) {} 10 | 11 | removeEventListener( 12 | eventName: string, 13 | listener: (event: any) => void 14 | ) {} 15 | 16 | } 17 | 18 | type ExpectedEventIdentifier = string; 19 | type ExpectedStrictEventIdentifier = never; 20 | type ExpectedObservedValue = any; 21 | 22 | type E = UntypedDOMEmitter; 23 | type M = Method 24 | type ActualEventIdentifier = EventIdentifier; 25 | type ActualEventIdentifierStrict = EventIdentifierStrict; 26 | type ActualObservedValue> = ObservedValue; 27 | 28 | type Tests = [ 29 | AreEqual< 30 | ExpectedEventIdentifier, 31 | ActualEventIdentifier 32 | >, 33 | AreEqual< 34 | ExpectedStrictEventIdentifier, 35 | ActualEventIdentifierStrict 36 | >, 37 | AreEqual< 38 | ExpectedObservedValue, 39 | ActualObservedValue 40 | > 41 | ]; 42 | type Works = AssertTrue; -------------------------------------------------------------------------------- /src/spec/extras.spec.ts: -------------------------------------------------------------------------------- 1 | import { EventExtras } from "../package"; 2 | import { AssertTrue, AreEqual, Method } from "./utils"; 3 | 4 | class Emitter { 5 | on( 6 | eventName: string, 7 | listener: Function, 8 | extra0: boolean, 9 | extra1?: number 10 | ) {} 11 | 12 | off( 13 | eventName: string, 14 | listener: Function 15 | ) {} 16 | } 17 | 18 | type ExpectedEventExtras = [boolean, number?]; 19 | 20 | type E = Emitter; 21 | type M = Method; 22 | type ActualEventExtras = EventExtras 23 | 24 | type Tests = [ 25 | AreEqual< 26 | ExpectedEventExtras, 27 | ActualEventExtras 28 | > 29 | ]; 30 | 31 | type Works = AssertTrue -------------------------------------------------------------------------------- /src/spec/inferences-length.spec.ts: -------------------------------------------------------------------------------- 1 | import { AreEqual } from "../package/types/utils"; 2 | import { InferencesLength, EmptyInference } from "../package/types/inferences"; 3 | import { AssertTrue } from "./utils"; 4 | 5 | type Tests = [ 6 | AreEqual< 7 | InferencesLength<[ 8 | EmptyInference, 9 | EmptyInference, 10 | EmptyInference, 11 | EmptyInference, 12 | EmptyInference, 13 | EmptyInference, 14 | EmptyInference, 15 | EmptyInference, 16 | EmptyInference, 17 | EmptyInference, 18 | EmptyInference, 19 | EmptyInference, 20 | EmptyInference, 21 | { I: "click", A: ["somthing"], X: [] }, 22 | { I: "move", A: ["somthing-else"], X: [number?] }, 23 | ]>, 24 | 2 25 | >, 26 | AreEqual< 27 | InferencesLength<[ 28 | EmptyInference, 29 | EmptyInference, 30 | EmptyInference, 31 | EmptyInference, 32 | EmptyInference, 33 | EmptyInference, 34 | EmptyInference, 35 | EmptyInference, 36 | { I: any, A: any, X: any }, 37 | { I: never, A: never, X: never }, 38 | { I: never[], A: never, X: never }, 39 | { I: never[], A: never, X: any }, 40 | { I: any, A: any, X: any[] }, 41 | { I: any, A: any[], X: [] }, 42 | { I: any, A: [], X: [] } 43 | ]>, 44 | 7 45 | > 46 | ] 47 | type Works = AssertTrue; -------------------------------------------------------------------------------- /src/spec/inferences.spec.ts: -------------------------------------------------------------------------------- 1 | import { AssertTrue, Method } from "./utils"; 2 | import { AreEqual } from "../package/types/utils"; 3 | import { Inferences, EmptyInference } from "../package/types/inferences"; 4 | import { MixtypedDOMEmitter, CustomEventA, CustomEventB } from "./dom-mixtyped.spec"; 5 | 6 | type Works = AssertTrue< 7 | AreEqual< 8 | Inferences>, 9 | [ 10 | EmptyInference, 11 | EmptyInference, 12 | EmptyInference, 13 | EmptyInference, 14 | EmptyInference, 15 | EmptyInference, 16 | EmptyInference, 17 | EmptyInference, 18 | EmptyInference, 19 | EmptyInference, 20 | EmptyInference, 21 | EmptyInference, 22 | { I: string, A: any[], X: [] }, 23 | { I: "event-a", A: [CustomEventA], X: [] }, 24 | { I: "event-b", A: [CustomEventB], X: [] } 25 | ] 26 | > 27 | > -------------------------------------------------------------------------------- /src/spec/no-types.spec.ts: -------------------------------------------------------------------------------- 1 | import { Method, AssertTrue } from "./utils"; 2 | import { EventIdentifier, EventIdentifierStrict, ObservedValue } from "../package"; 3 | import { AreEqual } from "../package/types/utils"; 4 | 5 | class NoTypesEmitter { 6 | on( 7 | name: any, 8 | handler: any 9 | ) {}; 10 | 11 | off( 12 | name: any, 13 | handler: any 14 | ) {}; 15 | } 16 | 17 | type E = NoTypesEmitter; 18 | type M = Method; 19 | type ExpectedEventIdentifier = any; 20 | type ExpectedEventIdentifierStrict = never; 21 | type ExpectedObservedValue = unknown; 22 | 23 | type ActualEventIdentifier = EventIdentifier; 24 | type ActualEventIdentifierStrict = EventIdentifierStrict; 25 | type ActualObservedValue = ObservedValue; 26 | 27 | type Tests = [ 28 | AreEqual< 29 | M, 30 | "on" 31 | >, 32 | AreEqual< 33 | ExpectedEventIdentifier, 34 | ActualEventIdentifier 35 | >, 36 | AreEqual< 37 | ExpectedEventIdentifierStrict, 38 | ActualEventIdentifierStrict 39 | >, 40 | AreEqual< 41 | ExpectedObservedValue<"something">, 42 | ActualObservedValue<"something"> 43 | > 44 | ]; 45 | 46 | type Works = AssertTrue -------------------------------------------------------------------------------- /src/spec/node-mixtyped.spec.ts: -------------------------------------------------------------------------------- 1 | import { EventIdentifier, ObservedValue, EventIdentifierStrict } from "../package"; 2 | import { AssertTrue, AreEqual, Method } from "./utils"; 3 | 4 | class SomeType { private _ = "" } 5 | const someSymbol = Symbol("foo"); 6 | 7 | class MixtypedNodeEmitter { 8 | 9 | addListener( 10 | eventName: string | symbol, 11 | listener: (...args: any[]) => void 12 | ): void; 13 | 14 | addListener( 15 | eventName: "event-a", 16 | listener: (a0: number, a1: string) => void 17 | ): void; 18 | 19 | addListener( 20 | eventName: typeof someSymbol, 21 | listener: (a0: SomeType) => void 22 | ): void; 23 | 24 | addListener( 25 | eventName: 26 | | "event-a" 27 | | typeof someSymbol, 28 | listener: 29 | | ((a0: number, a1: string) => void) 30 | | ((a0: SomeType) => void) 31 | ) {} 32 | 33 | removeListener( 34 | eventName: string | symbol, 35 | listener: (...args: any[]) => void 36 | ): void; 37 | 38 | removeListener( 39 | eventName: "event-a", 40 | listener: (a0: number, a1: string) => void 41 | ): void; 42 | 43 | removeListener( 44 | eventName: typeof someSymbol, 45 | listener: (a0: SomeType) => void 46 | ): void; 47 | 48 | removeListener( 49 | eventName: 50 | | "event-a" 51 | | typeof someSymbol, 52 | listener: 53 | | ((a0: number, a1: string) => void) 54 | | ((a0: SomeType) => void) 55 | ) {} 56 | 57 | } 58 | 59 | type ExpectedEventIdentifier = 60 | | "event-a" 61 | | typeof someSymbol 62 | | symbol 63 | | string; 64 | 65 | type ExpectedStrictEventIdentifier = 66 | | "event-a" 67 | | typeof someSymbol; 68 | 69 | type ExpectedObservedValue = 70 | I extends "event-a" ? [number, string] : 71 | I extends typeof someSymbol ? SomeType : 72 | I extends string | symbol ? unknown : 73 | never; 74 | 75 | type E = MixtypedNodeEmitter; 76 | type M = Method; 77 | type ActualEventIdentifier = EventIdentifier; 78 | type ActualEventIdentifierStrict = EventIdentifierStrict; 79 | type ActualObservedValue> = ObservedValue; 80 | 81 | type Tests = [ 82 | AreEqual< 83 | ExpectedEventIdentifier, 84 | ActualEventIdentifier 85 | >, 86 | AreEqual< 87 | ExpectedStrictEventIdentifier, 88 | ActualEventIdentifierStrict 89 | >, 90 | AreEqual< 91 | ExpectedObservedValue<"event-a">, 92 | ActualObservedValue<"event-a"> 93 | >, 94 | AreEqual< 95 | ExpectedObservedValue, 96 | ActualObservedValue 97 | >, 98 | AreEqual< 99 | ExpectedObservedValue, 100 | ActualObservedValue 101 | >, 102 | AreEqual< 103 | ExpectedObservedValue<"non-exsistent-event">, 104 | ActualObservedValue<"non-exsistent-event"> 105 | > 106 | ]; 107 | 108 | type Works = AssertTrue -------------------------------------------------------------------------------- /src/spec/node-typed.spec.ts: -------------------------------------------------------------------------------- 1 | import { EventIdentifier, ObservedValue, EventIdentifierStrict } from "../package"; 2 | import { AssertTrue, AreEqual, Method } from "./utils"; 3 | 4 | class SomeType { private _ = "" } 5 | const someSymbol = Symbol("foo"); 6 | 7 | class TypedNodeEmitter { 8 | 9 | addListener( 10 | eventName: "event-a", 11 | listener: (a0: number, a1: string) => void 12 | ): void; 13 | 14 | addListener( 15 | eventName: typeof someSymbol, 16 | listener: (a0: SomeType) => void 17 | ): void; 18 | 19 | addListener( 20 | eventName: 21 | | "event-a" 22 | | typeof someSymbol, 23 | listener: 24 | | ((a0: number, a1: string) => void) 25 | | ((a0: SomeType) => void) 26 | ) {} 27 | 28 | removeListener( 29 | eventName: "event-a", 30 | listener: (a0: number, a1: string) => void 31 | ): void; 32 | 33 | removeListener( 34 | eventName: typeof someSymbol, 35 | listener: (a0: SomeType) => void 36 | ): void; 37 | 38 | removeListener( 39 | eventName: 40 | | "event-a" 41 | | typeof someSymbol, 42 | listener: 43 | | ((a0: number, a1: string) => void) 44 | | ((a0: SomeType) => void) 45 | ) {} 46 | 47 | } 48 | 49 | type ExpectedEventName = 50 | | "event-a" 51 | | typeof someSymbol; 52 | 53 | type ExpectedStrictEventName = 54 | | "event-a" 55 | | typeof someSymbol; 56 | 57 | type ExpectedObservedValue = 58 | I extends "event-a" ? [number, string] : 59 | I extends typeof someSymbol ? SomeType : 60 | never; 61 | 62 | type E = TypedNodeEmitter; 63 | type M = Method; 64 | type ActualEventIdentifier = EventIdentifier; 65 | type ActualEventIdentifierStrict = EventIdentifierStrict; 66 | type ActualObservedValue> = ObservedValue; 67 | 68 | type Tests = [ 69 | AreEqual< 70 | ExpectedEventName, 71 | ActualEventIdentifier 72 | >, 73 | AreEqual< 74 | ExpectedStrictEventName, 75 | ActualEventIdentifierStrict 76 | >, 77 | AreEqual< 78 | ExpectedObservedValue<"event-a">, 79 | ActualObservedValue<"event-a"> 80 | >, 81 | AreEqual< 82 | ExpectedObservedValue, 83 | ActualObservedValue 84 | > 85 | ]; 86 | 87 | type Works = AssertTrue -------------------------------------------------------------------------------- /src/spec/node-untyped.spec.ts: -------------------------------------------------------------------------------- 1 | import { EventIdentifier, ObservedValue, EventIdentifierStrict } from "../package"; 2 | import { AssertTrue, AreEqual, Method } from "./utils"; 3 | 4 | class UntypedNodeEmitter { 5 | 6 | addListener( 7 | eventName: string | symbol, 8 | listener: (...args: any[]) => void 9 | ) {} 10 | 11 | removeListener( 12 | eventName: string | symbol, 13 | listener: (...args: any[]) => void 14 | ) {} 15 | 16 | } 17 | 18 | type ExpectedEventName = 19 | | symbol 20 | | string; 21 | 22 | type ExpectedStrictEventName = never; 23 | 24 | type ExpectedObservedValue = 25 | I extends string | symbol ? unknown : 26 | never; 27 | 28 | type E = UntypedNodeEmitter; 29 | type M = Method; 30 | type ActualEventIdentifier = EventIdentifier; 31 | type ActualEventIdentifierStrict = EventIdentifierStrict; 32 | type ActualObservedValue> = ObservedValue; 33 | 34 | type Tests = [ 35 | AreEqual< 36 | ExpectedEventName, 37 | ActualEventIdentifier 38 | >, 39 | AreEqual< 40 | ExpectedStrictEventName, 41 | ActualEventIdentifierStrict 42 | >, 43 | AreEqual< 44 | ExpectedObservedValue, 45 | ActualObservedValue 46 | >, 47 | AreEqual< 48 | ExpectedObservedValue<"non-exsistent-event">, 49 | ActualObservedValue<"non-exsistent-event"> 50 | > 51 | ]; 52 | 53 | type Works = AssertTrue -------------------------------------------------------------------------------- /src/spec/partially-mixtyped.spec.ts: -------------------------------------------------------------------------------- 1 | import { EventIdentifier, ObservedValue, EventIdentifierStrict } from "../package"; 2 | import { AssertTrue, AreEqual, Method } from "./utils"; 3 | 4 | class SomeType { private _ = "" } 5 | const someSymbol = Symbol("foo"); 6 | 7 | class PartiallyMixtypedNodeEmitter { 8 | 9 | addListener( 10 | eventName: string | symbol, 11 | listener: ((a0: number) => void) // it's number instead of ...args: any[] 12 | ): void; 13 | 14 | addListener( 15 | eventName: "event-a", 16 | listener: (a0: number, a1: string) => void 17 | ): void; 18 | 19 | addListener( 20 | eventName: typeof someSymbol, 21 | listener: (a0: SomeType) => void 22 | ): void; 23 | 24 | addListener( 25 | eventName: 26 | | "event-a" 27 | | typeof someSymbol, 28 | listener: 29 | | ((a0: number) => void) 30 | | ((a0: number, a1: string) => void) 31 | | ((a0: SomeType) => void) 32 | ) {} 33 | 34 | removeListener( 35 | eventName: string | symbol, 36 | listener: ((a0: number) => void) 37 | ): void; 38 | 39 | removeListener( 40 | eventName: "event-a", 41 | listener: (a0: number, a1: string) => void 42 | ): void; 43 | 44 | removeListener( 45 | eventName: typeof someSymbol, 46 | listener: (a0: SomeType) => void 47 | ): void; 48 | 49 | removeListener( 50 | eventName: 51 | | "event-a" 52 | | typeof someSymbol, 53 | listener: 54 | | ((a0: number) => void) 55 | | ((a0: number, a1: string) => void) 56 | | ((a0: SomeType) => void) 57 | ) {} 58 | 59 | } 60 | 61 | type ExpectedEventName = 62 | | "event-a" 63 | | typeof someSymbol 64 | | symbol 65 | | string; 66 | 67 | type ExpectedStrictEventName = 68 | | "event-a" 69 | | typeof someSymbol; 70 | 71 | type ExpectedObservedValue = 72 | I extends "event-a" ? [number, string] : 73 | I extends typeof someSymbol ? SomeType : 74 | I extends string | symbol ? number : 75 | never; 76 | 77 | type E = PartiallyMixtypedNodeEmitter; 78 | type M = Method; 79 | type ActualEventIdentifier = EventIdentifier; 80 | type ActualEventIdentifierStrict = EventIdentifierStrict; 81 | type ActualObservedValue> = ObservedValue; 82 | 83 | type Tests = [ 84 | AreEqual< 85 | ExpectedEventName, 86 | ActualEventIdentifier 87 | >, 88 | AreEqual< 89 | ExpectedStrictEventName, 90 | ActualEventIdentifierStrict 91 | >, 92 | AreEqual< 93 | ExpectedObservedValue<"event-a">, 94 | ActualObservedValue<"event-a"> 95 | >, 96 | AreEqual< 97 | ExpectedObservedValue, 98 | ActualObservedValue 99 | >, 100 | AreEqual< 101 | ExpectedObservedValue, 102 | ActualObservedValue 103 | >, 104 | AreEqual< 105 | ExpectedObservedValue<"non-existent-event">, 106 | ActualObservedValue<"non-existent-event"> 107 | > 108 | ]; 109 | 110 | type Works = AssertTrue -------------------------------------------------------------------------------- /src/spec/random-real-world.spec.ts: -------------------------------------------------------------------------------- 1 | import { fromEmitter } from "../package"; 2 | import { AreEqual } from "../package/types/utils"; 3 | import { Observable } from "rxjs"; 4 | import { ChildProcess } from "child_process"; 5 | import { AssertTrue } from "./utils"; 6 | import chokidar from "chokidar" 7 | import { Stats } from "fs"; 8 | 9 | 10 | const click$ = fromEmitter(document.querySelector(".something")!).eventStrict("click"); 11 | const unknownDomEvent$ = fromEmitter(document.body).event("random"); 12 | 13 | const exit$ = fromEmitter(process).eventStrict("exit"); 14 | const childExit$ = fromEmitter({} as ChildProcess).eventStrict("exit"); 15 | const unknownNodeEvent$ = fromEmitter({} as ChildProcess).event("random"); 16 | 17 | const connection$ = 18 | fromEmitter({} as SocketIO.Server) 19 | .withMethods("on", null) 20 | .eventStrict("connection"); 21 | 22 | const request$ = 23 | fromEmitter({} as SocketIO.Server) 24 | .withMethods("checkRequest", null) 25 | .event("somethingIdk"); 26 | 27 | const message$ = 28 | fromEmitter({} as SocketIO.Socket) 29 | .event("some-message-event") 30 | 31 | const fileChange$ = 32 | fromEmitter(chokidar.watch("foo")) 33 | .withMethods("on", "off") 34 | .eventStrict("change"); 35 | 36 | type Tests = [ 37 | AreEqual< 38 | typeof click$, 39 | Observable 40 | >, 41 | AreEqual< 42 | typeof unknownDomEvent$, 43 | Observable 44 | >, 45 | AreEqual< 46 | typeof exit$, 47 | Observable 48 | >, 49 | AreEqual< 50 | typeof childExit$, 51 | Observable<[number | null, string | null]> 52 | >, 53 | AreEqual< 54 | typeof unknownNodeEvent$, 55 | Observable 56 | >, 57 | AreEqual< 58 | typeof connection$, 59 | Observable 60 | >, 61 | AreEqual< 62 | typeof request$, 63 | Observable<[any, boolean]> 64 | >, 65 | AreEqual< 66 | typeof message$, 67 | Observable 68 | >, 69 | AreEqual< 70 | typeof fileChange$, 71 | Observable<[string, Stats?]> 72 | > 73 | ]; 74 | 75 | type Works = AssertTrue; -------------------------------------------------------------------------------- /src/spec/too-many-events.spec.ts: -------------------------------------------------------------------------------- 1 | import { Method, AssertTrue } from "./utils"; 2 | import { EventIdentifier, EventIdentifierStrict, ObservedValue } from "../package"; 3 | import { AreEqual } from "../package/types/utils"; 4 | 5 | class EmitterWith15Events { 6 | 7 | on( 8 | eventName: string, 9 | listener: (_: "something-something") => void 10 | ): void; 11 | 12 | on( 13 | eventName: "event-0", 14 | listener: (_: "event-0-result") => void 15 | ): void; 16 | 17 | 18 | on( 19 | eventName: "event-1", 20 | listener: (_: "event-1-result") => void 21 | ): void; 22 | 23 | 24 | on( 25 | eventName: "event-2", 26 | listener: (_: "event-2-result") => void 27 | ): void; 28 | 29 | 30 | on( 31 | eventName: "event-3", 32 | listener: (_: "event-3-result") => void 33 | ): void; 34 | 35 | 36 | on( 37 | eventName: "event-4", 38 | listener: (_: "event-4-result") => void 39 | ): void; 40 | 41 | 42 | on( 43 | eventName: "event-5", 44 | listener: (_: "event-5-result") => void 45 | ): void; 46 | 47 | 48 | on( 49 | eventName: "event-6", 50 | listener: (_: "event-6-result") => void 51 | ): void; 52 | 53 | 54 | on( 55 | eventName: "event-7", 56 | listener: (_: "event-7-result") => void 57 | ): void; 58 | 59 | 60 | on( 61 | eventName: "event-8", 62 | listener: (_: "event-8-result") => void 63 | ): void; 64 | 65 | 66 | on( 67 | eventName: "event-9", 68 | listener: (_: "event-9-result") => void 69 | ): void; 70 | 71 | 72 | on( 73 | eventName: "event-10", 74 | listener: (_: "event-10-result") => void 75 | ): void; 76 | 77 | 78 | on( 79 | eventName: "event-11", 80 | listener: (_: "event-11-result") => void 81 | ): void; 82 | 83 | on( 84 | eventName: "event-12", 85 | listener: (_: "event-12-result") => void 86 | ): void; 87 | 88 | on( 89 | eventName: "event-13", 90 | listener: (_: "event-13-result") => void 91 | ): void; 92 | 93 | on( 94 | eventName: "event-14", 95 | listener: (_: "event-14-result") => void 96 | ): void; 97 | 98 | on( 99 | eventName: string, 100 | listener: Function 101 | ) {} 102 | 103 | off( 104 | eventName: string, 105 | listener: Function 106 | ) {} 107 | } 108 | 109 | type E = EmitterWith15Events; 110 | type M = Method; 111 | 112 | type ExpectedEventIdentifier = unknown; 113 | type ExpectedEventIdentifierStrict = unknown; 114 | type ExpectedObservedValue = 115 | I extends "event-0" ? "event-0-result" : 116 | I extends "event-1" ? "event-1-result" : 117 | I extends "event-2" ? "event-2-result" : 118 | I extends "event-3" ? "event-3-result" : 119 | I extends "event-4" ? "event-4-result" : 120 | I extends "event-5" ? "event-5-result" : 121 | I extends "event-6" ? "event-6-result" : 122 | I extends "event-7" ? "event-7-result" : 123 | I extends "event-8" ? "event-8-result" : 124 | I extends "event-9" ? "event-9-result" : 125 | I extends "event-10" ? "event-10-result" : 126 | I extends "event-11" ? "event-11-result" : 127 | I extends string ? unknown : 128 | unknown; 129 | 130 | type ActualEventIdentifier = EventIdentifier 131 | type ActualEventIdentifierStrict = EventIdentifierStrict; 132 | type ActualObservedValue> = ObservedValue; 133 | 134 | 135 | type Tests = [ 136 | AreEqual< 137 | ExpectedEventIdentifier, 138 | ActualEventIdentifier 139 | >, 140 | AreEqual< 141 | ExpectedEventIdentifierStrict, 142 | ActualEventIdentifierStrict 143 | >, 144 | AreEqual< 145 | ExpectedObservedValue<"event-5">, 146 | ActualObservedValue<"event-5"> 147 | >, 148 | AreEqual< 149 | ExpectedObservedValue<"non-exsistent-event">, 150 | ActualObservedValue<"non-exsistent-event"> 151 | > 152 | ]; 153 | 154 | type Works = AssertTrue; 155 | -------------------------------------------------------------------------------- /src/spec/utils.ts: -------------------------------------------------------------------------------- 1 | import { DomEmitter, NodeEmitter, JqueryEmitter } from "../package/types/preset-emitter"; 2 | 3 | export type AssertTrue = T; 4 | 5 | export { Method } from "../package/types/"; 6 | export { AreEqual } from "../package/types/utils"; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "strict": true, 6 | "noImplicitAny": true, 7 | "esModuleInterop": true, 8 | "rootDir": "./src", 9 | "outDir": "./dist", 10 | "declaration": true 11 | } 12 | } --------------------------------------------------------------------------------