feat(docgen): proper event parsing for typescript

This commit is contained in:
iCrawl
2022-06-10 16:22:11 +02:00
parent 0415300243
commit d4b41dd081
15 changed files with 113 additions and 140 deletions

View File

@@ -36,7 +36,7 @@ export class Documentation {
case 'Class': { case 'Class': {
this.classes.set(item.name, new DocumentedClass(item, config)); this.classes.set(item.name, new DocumentedClass(item, config));
if (item.children) { if (item.children) {
this.parse(item.children, item.name); this.parse(item.children, item);
} }
break; break;
} }
@@ -51,7 +51,7 @@ export class Documentation {
case 'Enumeration': case 'Enumeration':
this.typedefs.set(item.name, new DocumentedTypeDef(item, config)); this.typedefs.set(item.name, new DocumentedTypeDef(item, config));
if (item.children) { if (item.children) {
this.parse(item.children, item.name); this.parse(item.children, item);
} }
break; break;
@@ -101,7 +101,7 @@ export class Documentation {
} }
} }
public parse(items: ChildTypes[] | DeclarationReflection[], memberOf = '') { public parse(items: ChildTypes[] | DeclarationReflection[], p?: DeclarationReflection) {
if (this.config.typescript) { if (this.config.typescript) {
const it = items as DeclarationReflection[]; const it = items as DeclarationReflection[];
@@ -114,6 +114,12 @@ export class Documentation {
break; break;
} }
case 'Method': { case 'Method': {
const event = p?.groups?.find((group) => group.title === 'Events');
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if ((event?.children as unknown as number[])?.includes(member.id)) {
item = new DocumentedEvent(member, this.config);
break;
}
item = new DocumentedMethod(member, this.config); item = new DocumentedMethod(member, this.config);
break; break;
} }
@@ -121,17 +127,13 @@ export class Documentation {
item = new DocumentedMember(member, this.config); item = new DocumentedMember(member, this.config);
break; break;
} }
case 'Event': {
item = new DocumentedEvent(member, this.config);
break;
}
default: { default: {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
console.warn(`- Unknown documentation kind "${member.kindString}" - \n${JSON.stringify(member)}\n`); console.warn(`- Unknown documentation kind "${member.kindString}" - \n${JSON.stringify(member)}\n`);
} }
} }
const parent = this.classes.get(memberOf) ?? this.interfaces.get(memberOf); const parent = this.classes.get(p!.name) ?? this.interfaces.get(p!.name);
if (parent) { if (parent) {
if (item) { if (item) {
parent.add(item); parent.add(item);
@@ -154,8 +156,8 @@ export class Documentation {
path: dirname(member.sources?.[0]?.fileName ?? ''), path: dirname(member.sources?.[0]?.fileName ?? ''),
}; };
if (memberOf) { if (p!.name) {
info.push(`member of "${memberOf}"`); info.push(`member of "${p!.name}"`);
} }
if (meta) { if (meta) {
info.push( info.push(

View File

@@ -2,7 +2,9 @@ import type { DeclarationReflection, SignatureReflection } from 'typedoc';
import { DocumentedItemMeta } from './item-meta.js'; import { DocumentedItemMeta } from './item-meta.js';
import { DocumentedItem } from './item.js'; import { DocumentedItem } from './item.js';
import { DocumentedParam } from './param.js'; import { DocumentedParam } from './param.js';
import { DocumentedVarType } from './var-type.js';
import type { Event } from '../interfaces/index.js'; import type { Event } from '../interfaces/index.js';
import { parseType } from '../util/parseType.js';
export class DocumentedEvent extends DocumentedItem<Event | DeclarationReflection> { export class DocumentedEvent extends DocumentedItem<Event | DeclarationReflection> {
public override serializer() { public override serializer() {
@@ -23,11 +25,27 @@ export class DocumentedEvent extends DocumentedItem<Event | DeclarationReflectio
.map((t) => t.content.find((c) => c.kind === 'text')?.text.trim()) .map((t) => t.content.find((c) => c.kind === 'text')?.text.trim())
: undefined; : undefined;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const examples = signature.comment?.blockTags?.filter((t) => t.tag === '@example').length
? signature.comment.blockTags
.filter((t) => t.tag === '@example')
.map((t) => t.content.reduce((prev, curr) => (prev += curr.text), '').trim())
: undefined;
return { return {
name: signature.name, // @ts-expect-error
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
name: signature.parameters?.[0]?.type?.value,
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, @typescript-eslint/prefer-nullish-coalescing // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, @typescript-eslint/prefer-nullish-coalescing
description: signature.comment?.summary?.reduce((prev, curr) => (prev += curr.text), '').trim() || undefined, description: signature.comment?.summary?.reduce((prev, curr) => (prev += curr.text), '').trim() || undefined,
see, see,
access:
data.flags.isPrivate ||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
signature.comment?.blockTags?.some((t) => t.tag === '@private' || t.tag === '@internal')
? 'private'
: undefined,
examples,
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
deprecated: signature.comment?.blockTags?.some((t) => t.tag === '@deprecated') deprecated: signature.comment?.blockTags?.some((t) => t.tag === '@deprecated')
? signature.comment.blockTags ? signature.comment.blockTags
@@ -37,8 +55,34 @@ export class DocumentedEvent extends DocumentedItem<Event | DeclarationReflectio
: undefined, : undefined,
// @ts-expect-error // @ts-expect-error
params: signature.parameters params: signature.parameters
? (signature as SignatureReflection).parameters?.map((p) => new DocumentedParam(p, this.config).serialize()) ? (signature as SignatureReflection).parameters
?.slice(1)
.map((p) => new DocumentedParam(p, this.config).serialize())
: undefined, : undefined,
returns: signature.type
? [
new DocumentedVarType(
{
names: [parseType(signature.type)],
description:
signature.comment?.blockTags
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
?.find((t) => t.tag === '@returns')
?.content.reduce((prev, curr) => (prev += curr.text), '')
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
.trim() || undefined,
},
this.config,
).serialize(),
]
: undefined,
returnsDescription:
signature.comment?.blockTags
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
?.find((t) => t.tag === '@returns')
?.content.reduce((prev, curr) => (prev += curr.text), '')
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
.trim() || undefined,
meta, meta,
}; };
} }

View File

@@ -11,8 +11,8 @@ describe('SpeakingMap', () => {
const starts: string[] = []; const starts: string[] = [];
const ends: string[] = []; const ends: string[] = [];
speaking.on('start', (userId) => void starts.push(userId)); speaking.on('start', (userId: string) => void starts.push(userId));
speaking.on('end', (userId) => void ends.push(userId)); speaking.on('end', (userId: string) => void ends.push(userId));
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
speaking.onPacket(userId); speaking.onPacket(userId);

View File

@@ -54,7 +54,6 @@
"@types/ws": "^8.5.3", "@types/ws": "^8.5.3",
"discord-api-types": "^0.33.5", "discord-api-types": "^0.33.5",
"prism-media": "^1.3.2", "prism-media": "^1.3.2",
"tiny-typed-emitter": "^2.1.0",
"tslib": "^2.4.0", "tslib": "^2.4.0",
"ws": "^8.8.0" "ws": "^8.8.0"
}, },

View File

@@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/prefer-ts-expect-error */ import { EventEmitter } from 'node:events';
import type { GatewayVoiceServerUpdateDispatchData, GatewayVoiceStateUpdateDispatchData } from 'discord-api-types/v10'; import type { GatewayVoiceServerUpdateDispatchData, GatewayVoiceStateUpdateDispatchData } from 'discord-api-types/v10';
import { TypedEmitter } from 'tiny-typed-emitter';
import type { CreateVoiceConnectionOptions } from '.'; import type { CreateVoiceConnectionOptions } from '.';
import { import {
getVoiceConnection, getVoiceConnection,
@@ -15,7 +14,7 @@ import type { VoiceWebSocket, VoiceUDPSocket } from './networking';
import { Networking, NetworkingState, NetworkingStatusCode } from './networking/Networking'; import { Networking, NetworkingState, NetworkingStatusCode } from './networking/Networking';
import { VoiceReceiver } from './receive'; import { VoiceReceiver } from './receive';
import type { DiscordGatewayAdapterImplementerMethods } from './util/adapter'; import type { DiscordGatewayAdapterImplementerMethods } from './util/adapter';
import { Awaited, noop } from './util/util'; import { noop } from './util/util';
/** /**
* The various status codes a voice connection can hold at any one time. * The various status codes a voice connection can hold at any one time.
@@ -162,21 +161,10 @@ export type VoiceConnectionState =
| VoiceConnectionReadyState | VoiceConnectionReadyState
| VoiceConnectionDestroyedState; | VoiceConnectionDestroyedState;
export type VoiceConnectionEvents = {
error: (error: Error) => Awaited<void>;
debug: (message: string) => Awaited<void>;
stateChange: (oldState: VoiceConnectionState, newState: VoiceConnectionState) => Awaited<void>;
} & {
[status in VoiceConnectionStatus]: (
oldState: VoiceConnectionState,
newState: VoiceConnectionState & { status: status },
) => Awaited<void>;
};
/** /**
* A connection to the voice server of a Guild, can be used to play audio in voice channels. * A connection to the voice server of a Guild, can be used to play audio in voice channels.
*/ */
export class VoiceConnection extends TypedEmitter<VoiceConnectionEvents> { export class VoiceConnection extends EventEmitter {
/** /**
* The number of consecutive rejoin attempts. Initially 0, and increments for each rejoin. * The number of consecutive rejoin attempts. Initially 0, and increments for each rejoin.
* When a connection is successfully established, it resets to 0. * When a connection is successfully established, it resets to 0.
@@ -673,7 +661,6 @@ export class VoiceConnection extends TypedEmitter<VoiceConnectionEvents> {
* *
* @param subscription - The removed subscription * @param subscription - The removed subscription
*/ */
// @ts-ignore
private onSubscriptionRemoved(subscription: PlayerSubscription) { private onSubscriptionRemoved(subscription: PlayerSubscription) {
if (this.state.status !== VoiceConnectionStatus.Destroyed && this.state.subscription === subscription) { if (this.state.status !== VoiceConnectionStatus.Destroyed && this.state.subscription === subscription) {
this.state = { this.state = {

View File

@@ -1,11 +1,11 @@
/* eslint-disable @typescript-eslint/prefer-ts-expect-error */ /* eslint-disable @typescript-eslint/prefer-ts-expect-error */
import { TypedEmitter } from 'tiny-typed-emitter'; import EventEmitter from 'node:events';
import { AudioPlayerError } from './AudioPlayerError'; import { AudioPlayerError } from './AudioPlayerError';
import type { AudioResource } from './AudioResource'; import type { AudioResource } from './AudioResource';
import { PlayerSubscription } from './PlayerSubscription'; import { PlayerSubscription } from './PlayerSubscription';
import { addAudioPlayer, deleteAudioPlayer } from '../DataStore'; import { addAudioPlayer, deleteAudioPlayer } from '../DataStore';
import { VoiceConnection, VoiceConnectionStatus } from '../VoiceConnection'; import { VoiceConnection, VoiceConnectionStatus } from '../VoiceConnection';
import { Awaited, noop } from '../util/util'; import { noop } from '../util/util';
// The Opus "silent" frame // The Opus "silent" frame
export const SILENCE_FRAME = Buffer.from([0xf8, 0xff, 0xfe]); export const SILENCE_FRAME = Buffer.from([0xf8, 0xff, 0xfe]);
@@ -151,18 +151,14 @@ export type AudioPlayerState =
| AudioPlayerPlayingState | AudioPlayerPlayingState
| AudioPlayerPausedState; | AudioPlayerPausedState;
export type AudioPlayerEvents = { export interface AudioPlayer extends EventEmitter {
error: (error: AudioPlayerError) => Awaited<void>; /**
debug: (message: string) => Awaited<void>; * Emitted when there is an error emitted from the audio resource played by the audio player
stateChange: (oldState: AudioPlayerState, newState: AudioPlayerState) => Awaited<void>; *
subscribe: (subscription: PlayerSubscription) => Awaited<void>; * @event
unsubscribe: (subscription: PlayerSubscription) => Awaited<void>; */
} & { on: (event: 'error', listener: (error: AudioPlayerError) => void) => this;
[status in AudioPlayerStatus]: ( }
oldState: AudioPlayerState,
newState: AudioPlayerState & { status: status },
) => Awaited<void>;
};
/** /**
* Stringifies an AudioPlayerState instance. * Stringifies an AudioPlayerState instance.
@@ -187,7 +183,7 @@ function stringifyState(state: AudioPlayerState) {
* The AudioPlayer drives the timing of playback, and therefore is unaffected by voice connections * The AudioPlayer drives the timing of playback, and therefore is unaffected by voice connections
* becoming unavailable. Its behavior in these scenarios can be configured. * becoming unavailable. Its behavior in these scenarios can be configured.
*/ */
export class AudioPlayer extends TypedEmitter<AudioPlayerEvents> { export class AudioPlayer extends EventEmitter {
/** /**
* The state that the AudioPlayer is in. * The state that the AudioPlayer is in.
*/ */
@@ -372,12 +368,6 @@ export class AudioPlayer extends TypedEmitter<AudioPlayerEvents> {
// state if the resource is still being used. // state if the resource is still being used.
const onStreamError = (error: Error) => { const onStreamError = (error: Error) => {
if (this.state.status !== AudioPlayerStatus.Idle) { if (this.state.status !== AudioPlayerStatus.Idle) {
/**
* Emitted when there is an error emitted from the audio resource played by the audio player
*
* @event AudioPlayer#error
* @type {AudioPlayerError}
*/
this.emit('error', new AudioPlayerError(error, this.state.resource)); this.emit('error', new AudioPlayerError(error, this.state.resource));
} }

View File

@@ -9,7 +9,6 @@ export {
AudioPlayerPausedState, AudioPlayerPausedState,
AudioPlayerPlayingState, AudioPlayerPlayingState,
CreateAudioPlayerOptions, CreateAudioPlayerOptions,
AudioPlayerEvents,
} from './AudioPlayer'; } from './AudioPlayer';
export { AudioPlayerError } from './AudioPlayerError'; export { AudioPlayerError } from './AudioPlayerError';

View File

@@ -16,7 +16,6 @@ export {
VoiceConnectionDisconnectReason, VoiceConnectionDisconnectReason,
VoiceConnectionReadyState, VoiceConnectionReadyState,
VoiceConnectionSignallingState, VoiceConnectionSignallingState,
VoiceConnectionEvents,
} from './VoiceConnection'; } from './VoiceConnection';
export { JoinConfig, getVoiceConnection, getVoiceConnections, getGroups } from './DataStore'; export { JoinConfig, getVoiceConnection, getVoiceConnections, getGroups } from './DataStore';

View File

@@ -1,10 +1,11 @@
/* eslint-disable @typescript-eslint/method-signature-style */
import { EventEmitter } from 'node:events';
import { VoiceOpcodes } from 'discord-api-types/voice/v4'; import { VoiceOpcodes } from 'discord-api-types/voice/v4';
import { TypedEmitter } from 'tiny-typed-emitter';
import type { CloseEvent } from 'ws'; import type { CloseEvent } from 'ws';
import { VoiceUDPSocket } from './VoiceUDPSocket'; import { VoiceUDPSocket } from './VoiceUDPSocket';
import { VoiceWebSocket } from './VoiceWebSocket'; import { VoiceWebSocket } from './VoiceWebSocket';
import * as secretbox from '../util/Secretbox'; import * as secretbox from '../util/Secretbox';
import { Awaited, noop } from '../util/util'; import { noop } from '../util/util';
// The number of audio channels required by Discord // The number of audio channels required by Discord
const CHANNELS = 2; const CHANNELS = 2;
@@ -150,11 +151,16 @@ export interface ConnectionData {
*/ */
const nonce = Buffer.alloc(24); const nonce = Buffer.alloc(24);
export interface NetworkingEvents { export interface Networking extends EventEmitter {
debug: (message: string) => Awaited<void>; /**
error: (error: Error) => Awaited<void>; * Debug event for Networking.
stateChange: (oldState: NetworkingState, newState: NetworkingState) => Awaited<void>; *
close: (code: number) => Awaited<void>; * @event
*/
on(event: 'debug', listener: (message: string) => void): this;
on(event: 'error', listener: (error: Error) => void): this;
on(event: 'stateChange', listener: (oldState: NetworkingState, newState: NetworkingState) => void): this;
on(event: 'close', listener: (code: number) => void): this;
} }
/** /**
@@ -195,7 +201,7 @@ function randomNBit(n: number) {
/** /**
* Manages the networking required to maintain a voice connection and dispatch audio packets * Manages the networking required to maintain a voice connection and dispatch audio packets
*/ */
export class Networking extends TypedEmitter<NetworkingEvents> { export class Networking extends EventEmitter {
private _state: NetworkingState; private _state: NetworkingState;
/** /**
@@ -274,12 +280,6 @@ export class Networking extends TypedEmitter<NetworkingEvents> {
this._state = newState; this._state = newState;
this.emit('stateChange', oldState, newState); this.emit('stateChange', oldState, newState);
/**
* Debug event for Networking.
*
* @event Networking#debug
* @type {string}
*/
this.debug?.(`state change:\nfrom ${stringifyState(oldState)}\nto ${stringifyState(newState)}`); this.debug?.(`state change:\nfrom ${stringifyState(oldState)}\nto ${stringifyState(newState)}`);
} }

View File

@@ -1,7 +1,6 @@
import { createSocket, Socket } from 'node:dgram'; import { createSocket, Socket } from 'node:dgram';
import { EventEmitter } from 'node:events';
import { isIPv4 } from 'node:net'; import { isIPv4 } from 'node:net';
import { TypedEmitter } from 'tiny-typed-emitter';
import type { Awaited } from '../util/util';
/** /**
* Stores an IP address and port. Used to store socket details for the local client as well as * Stores an IP address and port. Used to store socket details for the local client as well as
@@ -17,13 +16,6 @@ interface KeepAlive {
timestamp: number; timestamp: number;
} }
export interface VoiceUDPSocketEvents {
error: (error: Error) => Awaited<void>;
close: () => Awaited<void>;
debug: (message: string) => Awaited<void>;
message: (message: Buffer) => Awaited<void>;
}
/** /**
* Parses the response from Discord to aid with local IP discovery. * Parses the response from Discord to aid with local IP discovery.
* *
@@ -61,7 +53,7 @@ const MAX_COUNTER_VALUE = 2 ** 32 - 1;
/** /**
* Manages the UDP networking for a voice connection. * Manages the UDP networking for a voice connection.
*/ */
export class VoiceUDPSocket extends TypedEmitter<VoiceUDPSocketEvents> { export class VoiceUDPSocket extends EventEmitter {
/** /**
* The underlying network Socket for the VoiceUDPSocket. * The underlying network Socket for the VoiceUDPSocket.
*/ */

View File

@@ -1,28 +1,31 @@
/* eslint-disable @typescript-eslint/method-signature-style */
import { EventEmitter } from 'node:events';
import { VoiceOpcodes } from 'discord-api-types/voice/v4'; import { VoiceOpcodes } from 'discord-api-types/voice/v4';
import { TypedEmitter } from 'tiny-typed-emitter';
import WebSocket, { MessageEvent } from 'ws'; import WebSocket, { MessageEvent } from 'ws';
import type { Awaited } from '../util/util';
/** export interface VoiceWebSocket extends EventEmitter {
* Debug event for VoiceWebSocket. on(event: 'error', listener: (error: Error) => void): this;
* on(event: 'open', listener: (event: WebSocket.Event) => void): this;
* @event VoiceWebSocket#debug on(event: 'close', listener: (event: WebSocket.CloseEvent) => void): this;
* @type {string} /**
*/ * Debug event for VoiceWebSocket.
*
export interface VoiceWebSocketEvents { * @event
error: (error: Error) => Awaited<void>; */
open: (event: WebSocket.Event) => Awaited<void>; on(event: 'debug', listener: (message: string) => void): this;
close: (event: WebSocket.CloseEvent) => Awaited<void>; /**
debug: (message: string) => Awaited<void>; * Packet event.
packet: (packet: any) => Awaited<void>; *
* @event
*/
on(event: 'packet', listener: (packet: any) => void): this;
} }
/** /**
* An extension of the WebSocket class to provide helper functionality when interacting * An extension of the WebSocket class to provide helper functionality when interacting
* with the Discord Voice gateway. * with the Discord Voice gateway.
*/ */
export class VoiceWebSocket extends TypedEmitter<VoiceWebSocketEvents> { export class VoiceWebSocket extends EventEmitter {
/** /**
* The current heartbeat interval, if any. * The current heartbeat interval, if any.
*/ */
@@ -122,12 +125,6 @@ export class VoiceWebSocket extends TypedEmitter<VoiceWebSocketEvents> {
this.ping = this.lastHeartbeatAck - this.lastHeartbeatSend; this.ping = this.lastHeartbeatAck - this.lastHeartbeatSend;
} }
/**
* Packet event.
*
* @event VoiceWebSocket#packet
* @type {any}
*/
this.emit('packet', packet); this.emit('packet', packet);
} }

View File

@@ -1,5 +1,4 @@
import { TypedEmitter } from 'tiny-typed-emitter'; import { EventEmitter } from 'node:events';
import type { Awaited } from '../util/util';
/** /**
* The known data for a user in a Discord voice connection. * The known data for a user in a Discord voice connection.
@@ -22,19 +21,10 @@ export interface VoiceUserData {
userId: string; userId: string;
} }
/**
* The events that an SSRCMap may emit.
*/
export interface SSRCMapEvents {
create: (newData: VoiceUserData) => Awaited<void>;
update: (oldData: VoiceUserData | undefined, newData: VoiceUserData) => Awaited<void>;
delete: (deletedData: VoiceUserData) => Awaited<void>;
}
/** /**
* Maps audio SSRCs to data of users in voice connections. * Maps audio SSRCs to data of users in voice connections.
*/ */
export class SSRCMap extends TypedEmitter<SSRCMapEvents> { export class SSRCMap extends EventEmitter {
/** /**
* The underlying map. * The underlying map.
*/ */

View File

@@ -1,25 +1,9 @@
import { TypedEmitter } from 'tiny-typed-emitter'; import { EventEmitter } from 'node:events';
import type { Awaited } from '../util/util';
/**
* The events that a SpeakingMap can emit.
*/
export interface SpeakingMapEvents {
/**
* Emitted when a user starts speaking.
*/
start: (userId: string) => Awaited<void>;
/**
* Emitted when a user stops speaking.
*/
end: (userId: string) => Awaited<void>;
}
/** /**
* Tracks the speaking states of users in a voice channel. * Tracks the speaking states of users in a voice channel.
*/ */
export class SpeakingMap extends TypedEmitter<SpeakingMapEvents> { export class SpeakingMap extends EventEmitter {
/** /**
* The delay after a packet is received from a user until they're marked as not speaking anymore. * The delay after a packet is received from a user until they're marked as not speaking anymore.
*/ */

View File

@@ -1,4 +1,2 @@
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
export const noop = () => {}; export const noop = () => {};
export type Awaited<T> = T | Promise<T>;

View File

@@ -2019,7 +2019,6 @@ __metadata:
mock-socket: ^9.1.5 mock-socket: ^9.1.5
prettier: ^2.6.2 prettier: ^2.6.2
prism-media: ^1.3.2 prism-media: ^1.3.2
tiny-typed-emitter: ^2.1.0
tslib: ^2.4.0 tslib: ^2.4.0
tsup: ^6.1.0 tsup: ^6.1.0
tweetnacl: ^1.0.3 tweetnacl: ^1.0.3
@@ -15927,13 +15926,6 @@ dts-critic@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"tiny-typed-emitter@npm:^2.1.0":
version: 2.1.0
resolution: "tiny-typed-emitter@npm:2.1.0"
checksum: 709bca410054e08df4dc29d5ea0916328bb2900d60245c6a743068ea223887d9fd2c945b6070eb20336275a557a36c2808e5c87d2ed4b60633458632be4a3e10
languageName: node
linkType: hard
"tinypool@npm:^0.1.3": "tinypool@npm:^0.1.3":
version: 0.1.3 version: 0.1.3
resolution: "tinypool@npm:0.1.3" resolution: "tinypool@npm:0.1.3"