@ovencord/util 1.1.3 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@ovencord/util",
4
- "version": "1.1.3",
4
+ "version": "1.1.5",
5
5
  "description": "Utilities shared across Discord.js packages",
6
6
  "scripts": {
7
7
  "test": "bun test",
@@ -36,14 +36,9 @@
36
36
  "dependencies": {
37
37
  "discord-api-types": "^0.38.36"
38
38
  },
39
- "devDependencies": {
40
- "@types/bun": "^1.3.8",
41
- "eslint": "^9.39.2",
42
- "typescript": "^5.9.3",
43
- "typescript-eslint": "^8.54.0"
44
- },
39
+ "devDependencies": {},
45
40
  "engines": {
46
- "bun": ">=1.0.0"
41
+ "bun": ">=1.3.0"
47
42
  },
48
43
  "publishConfig": {
49
44
  "access": "public"
@@ -1,28 +1,203 @@
1
- import { EventEmitter } from 'node:events';
2
-
3
- /**
4
- * A typed EventEmitter shim to replace @vladfrangu/async_event_emitter
5
- * This version uses the native node:events EventEmitter.
6
- * Note: strict async emission (awaiting listeners) is not implemented as it was not used by the library internally.
7
- */
8
- export class AsyncEventEmitter<TEvents extends { [K in keyof TEvents]: any[] }> extends EventEmitter {
9
- public override on<K extends keyof TEvents & string>(event: K, listener: (...args: TEvents[K]) => void): this {
10
- return super.on(event, listener as any);
11
- }
12
-
13
- public override once<K extends keyof TEvents & string>(event: K, listener: (...args: TEvents[K]) => void): this {
14
- return super.once(event, listener as any);
15
- }
16
-
17
- public override emit<K extends keyof TEvents & string>(event: K, ...args: TEvents[K]): boolean {
18
- return super.emit(event, ...args);
19
- }
20
-
21
- public override off<K extends keyof TEvents & string>(event: K, listener: (...args: TEvents[K]) => void): this {
22
- return super.off(event, listener as any);
23
- }
24
-
25
- public override removeListener<K extends keyof TEvents & string>(event: K, listener: (...args: TEvents[K]) => void): this {
26
- return super.removeListener(event, listener as any);
1
+ import { AsyncQueue } from './AsyncQueue.js';
2
+
3
+ export type EventMap = Record<string | symbol, any[]>;
4
+
5
+ export class AsyncEventEmitter<Events extends Record<keyof Events, any[]> = any> {
6
+ private _listeners = new Map<keyof Events | string | symbol, Set<Function>>();
7
+ private _maxListeners = 10;
8
+
9
+ public on<K extends keyof Events>(event: K, listener: (...args: Events[K]) => void | Promise<void>): this;
10
+ public on<K extends string | symbol>(event: K, listener: (...args: any[]) => void | Promise<void>): this;
11
+ public on(event: string | symbol, listener: Function): this {
12
+ if (!this._listeners.has(event)) {
13
+ this._listeners.set(event, new Set());
14
+ }
15
+
16
+ const listeners = this._listeners.get(event)!;
17
+ listeners.add(listener);
18
+
19
+ if (listeners.size > this._maxListeners) {
20
+ console.warn(
21
+ `Possible AsyncEventEmitter memory leak detected. ` +
22
+ `${listeners.size} ${String(event)} listeners added. ` +
23
+ `Use emitter.setMaxListeners() to increase limit`
24
+ );
25
+ }
26
+
27
+ return this;
28
+ }
29
+
30
+ public addListener<K extends keyof Events>(event: K, listener: (...args: Events[K]) => void | Promise<void>): this;
31
+ public addListener<K extends string | symbol>(event: K, listener: (...args: any[]) => void | Promise<void>): this;
32
+ public addListener(event: string | symbol, listener: Function): this {
33
+ return this.on(event, listener as any);
34
+ }
35
+
36
+ public once<K extends keyof Events>(event: K, listener: (...args: Events[K]) => void | Promise<void>): this;
37
+ public once<K extends string | symbol>(event: K, listener: (...args: any[]) => void | Promise<void>): this;
38
+ public once(event: string | symbol, listener: Function): this {
39
+ const wrapper = async (...args: any[]) => {
40
+ this.off(event, wrapper);
41
+ await listener(...args);
42
+ };
43
+
44
+ Object.defineProperty(wrapper, '_originalListener', {
45
+ value: listener,
46
+ writable: false,
47
+ enumerable: false,
48
+ });
49
+
50
+ return this.on(event, wrapper);
51
+ }
52
+
53
+ public off<K extends keyof Events>(event: K, listener: (...args: Events[K]) => void | Promise<void>): this;
54
+ public off<K extends string | symbol>(event: K, listener: (...args: any[]) => void | Promise<void>): this;
55
+ public off(event: string | symbol, listener: Function): this {
56
+ const listeners = this._listeners.get(event);
57
+ if (!listeners) return this;
58
+
59
+ for (const fn of listeners) {
60
+ if (fn === listener || (fn as any)._originalListener === listener) {
61
+ listeners.delete(fn);
62
+ }
63
+ }
64
+
65
+ if (listeners.size === 0) {
66
+ this._listeners.delete(event);
67
+ }
68
+
69
+ return this;
70
+ }
71
+
72
+ public removeListener<K extends keyof Events>(event: K, listener: (...args: Events[K]) => void | Promise<void>): this;
73
+ public removeListener<K extends string | symbol>(event: K, listener: (...args: any[]) => void | Promise<void>): this;
74
+ public removeListener(event: string | symbol, listener: Function): this {
75
+ return this.off(event, listener as any);
76
+ }
77
+
78
+ public async emit<K extends keyof Events>(event: K, ...args: Events[K]): Promise<boolean>;
79
+ public async emit<K extends string | symbol>(event: K, ...args: any[]): Promise<boolean>;
80
+ public async emit(event: string | symbol, ...args: any[]): Promise<boolean> {
81
+ const listeners = this._listeners.get(event);
82
+ if (!listeners?.size) return false;
83
+
84
+ // We copy the listeners to ensure that if a listener is removed during execution,
85
+ // the loop still iterates over the snapshot of listeners at the time of emission.
86
+ // However, for strict sequential execution where one might remove another,
87
+ // iterating over the live Set or a copy is a design choice.
88
+ // Discord.js usually handles this by copying or iterating safe.
89
+ // The user's snippet iterates directly over the listeners collection from .get().
90
+ // If we use 'for of' on a Set, it handles deletions gracefully (the deleted item won't be visited if not reached yet),
91
+ // but additions might be visited.
92
+ // For now, adhering to user's "for (const listener of listeners)" pattern.
93
+
94
+ // NOTE: Iterating over the Set directly allows listeners to remove themselves safely.
95
+ for (const listener of listeners) {
96
+ try {
97
+ await listener(...args);
98
+ } catch (error) {
99
+ if (event === 'error') {
100
+ console.error('Error in error handler:', error);
101
+ } else if (this.listenerCount('error') > 0) {
102
+ await this.emit('error', error);
103
+ } else {
104
+ console.error(`Unhandled error in ${String(event)} event:`, error);
105
+ throw error;
106
+ }
107
+ }
108
+ }
109
+
110
+ return true;
111
+ }
112
+
113
+ public removeAllListeners(event?: keyof Events | string | symbol): this {
114
+ if (event !== undefined) {
115
+ this._listeners.delete(event);
116
+ } else {
117
+ this._listeners.clear();
118
+ }
119
+ return this;
120
+ }
121
+
122
+ public setMaxListeners(n: number): this {
123
+ this._maxListeners = n;
124
+ return this;
125
+ }
126
+
127
+ public getMaxListeners(): number {
128
+ return this._maxListeners;
129
+ }
130
+
131
+ public listenerCount(event: keyof Events | string | symbol): number {
132
+ return this._listeners.get(event)?.size ?? 0;
133
+ }
134
+
135
+ public eventNames(): Array<keyof Events | string | symbol> {
136
+ return Array.from(this._listeners.keys());
137
+ }
138
+
139
+ public rawListeners(event: keyof Events | string | symbol): Function[] {
140
+ return Array.from(this._listeners.get(event) ?? []);
141
+ }
142
+
143
+ public waitFor<K extends keyof Events>(event: K, timeout?: number): Promise<Events[K]>;
144
+ public waitFor(event: string | symbol, timeout?: number): Promise<any[]> {
145
+ return new Promise((resolve, reject) => {
146
+ let timeoutId: Timer | undefined;
147
+
148
+ const listener = (...args: any[]) => {
149
+ if (timeoutId) clearTimeout(timeoutId);
150
+ resolve(args);
151
+ };
152
+
153
+ this.once(event, listener);
154
+
155
+ if (timeout) {
156
+ timeoutId = setTimeout(() => {
157
+ this.off(event, listener);
158
+ reject(new Error(`Timeout waiting for ${String(event)}`));
159
+ }, timeout);
160
+ }
161
+ });
162
+ }
163
+
164
+ public static async *on<T extends AsyncEventEmitter<any>, K extends keyof (T extends AsyncEventEmitter<infer U> ? U : never)>(
165
+ emitter: T,
166
+ eventName: K,
167
+ options?: { signal?: AbortSignal }
168
+ ): AsyncIterableIterator<(T extends AsyncEventEmitter<infer U> ? U : never)[K]> {
169
+ const signal = options?.signal;
170
+ if (signal?.aborted) throw new Error('AbortError');
171
+
172
+ const queue = new AsyncQueue();
173
+ const items: any[][] = [];
174
+
175
+ const listener = (...args: any[]) => {
176
+ items.push(args);
177
+ queue.shift();
178
+ };
179
+
180
+ const abortHandler = () => {
181
+ emitter.off(eventName as any, listener);
182
+ queue.shift();
183
+ };
184
+
185
+ emitter.on(eventName as any, listener);
186
+ signal?.addEventListener('abort', abortHandler, { once: true });
187
+
188
+ try {
189
+ while (true) {
190
+ if (signal?.aborted && items.length === 0) break;
191
+ if (items.length === 0) {
192
+ await queue.wait(options);
193
+ }
194
+ if (signal?.aborted && items.length === 0) break;
195
+
196
+ yield items.shift()! as any;
197
+ }
198
+ } finally {
199
+ emitter.off(eventName as any, listener);
200
+ signal?.removeEventListener('abort', abortHandler);
27
201
  }
202
+ }
28
203
  }
@@ -5,29 +5,27 @@
5
5
  */
6
6
  export function getUserAgentAppendix(): string {
7
7
  // https://vercel.com/docs/concepts/functions/edge-functions/edge-runtime#check-if-you're-running-on-the-edge-runtime
8
- // @ts-expect-error Vercel Edge functions
9
- if (typeof globalThis.EdgeRuntime !== 'undefined') {
8
+ if (typeof (globalThis as any).EdgeRuntime !== 'undefined') {
10
9
  return 'Vercel-Edge-Functions';
11
10
  }
12
-
13
- // @ts-expect-error Cloudflare Workers
14
- if (typeof globalThis.R2 !== 'undefined' && typeof globalThis.WebSocketPair !== 'undefined') {
11
+
12
+ // Cloudflare Workers
13
+ if (typeof (globalThis as any).R2 !== 'undefined' && typeof (globalThis as any).WebSocketPair !== 'undefined') {
15
14
  // https://developers.cloudflare.com/workers/runtime-apis/web-standards/#navigatoruseragent
16
15
  return 'Cloudflare-Workers';
17
16
  }
18
-
17
+
19
18
  // https://docs.netlify.com/edge-functions/api/#netlify-global-object
20
- // @ts-expect-error Netlify Edge functions
21
- if (typeof globalThis.Netlify !== 'undefined') {
19
+ if (typeof (globalThis as any).Netlify !== 'undefined') {
22
20
  return 'Netlify-Edge-Functions';
23
21
  }
24
-
22
+
25
23
  // Most (if not all) edge environments will have `process` defined. Within a web browser we'll extract it using `navigator.userAgent`.
26
- if (typeof globalThis.process !== 'object') {
27
- if (typeof globalThis.navigator === 'object') {
28
- return globalThis.navigator.userAgent;
24
+ if (typeof (globalThis as any).process !== 'object') {
25
+ if (typeof (globalThis as any).navigator === 'object') {
26
+ return (globalThis as any).navigator.userAgent;
29
27
  }
30
-
28
+
31
29
  return 'UnknownEnvironment';
32
30
  }
33
31