Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add type declarations. #69

Merged
merged 6 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "arnavmq",
"version": "0.16.0",
"version": "0.16.1",
"description": "ArnavMQ is a RabbitMQ wrapper",
"keywords": [
"rabbitmq",
Expand All @@ -12,8 +12,9 @@
"subscribe"
],
"main": "src/index.js",
"types": "types/index.d.ts",
"scripts": {
"lint": "eslint . && prettier -c .",
"lint": "eslint . && prettier -c . && tsc --project types/tsconfig.types.json",
Copy link
Member

@shamil shamil Mar 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how lint relates to types?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't really build the project, just makes sure the types are well formed. I think it's too much to add a "build" step to it.

"format": "eslint --fix . && prettier --write .",
"cover": "test -d .nyc_output && nyc report --reporter lcov",
"test": "dot-only-hunter test && nyc mocha --recursive --exit"
Expand All @@ -39,6 +40,7 @@
"uuid": "^9.0.0"
},
"devDependencies": {
"@types/amqplib": "^0.10.5",
"child-process-promise": "^2.2.1",
"dot-only-hunter": "^1.0.3",
"eslint": "^8.25.0",
Expand All @@ -48,7 +50,8 @@
"mocha": "^10.0.0",
"nyc": "^15.1.0",
"prettier": "^3.0.0",
"sinon": "^17.0.1"
"sinon": "^17.0.1",
"typescript": "^5.3.3"
},
"engines": {
"node": ">=14"
Expand Down
28 changes: 28 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Just for parsing the types in the types folder. Not actually for compilation.
yosiat marked this conversation as resolved.
Show resolved Hide resolved

{
"compilerOptions": {
"allowJs": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"declaration": true,
"declarationMap": true,
"forceConsistentCasingInFileNames": true,
"inlineSources": true,
"module": "commonjs",
"noEmitOnError": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"pretty": true,
"sourceMap": true,
"strict": true,
"strictNullChecks": true,
"target": "es2017",
"incremental": false,
"newLine": "LF",
"outDir": "types"
},
"files": ["src/index.js"]
}
24 changes: 24 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Base types were generated using the command:
*
* npx -p typescript tsc src/index.js --declaration --allowJs --emitDeclarationOnly --outDir types
*
* Then, all private methods were removed from the types, and all the 'any' placeholders were replaced with handcrafted types to match the expected values, and exported in addition to the actual classes as part of the module.
* Any jsdoc comments with parameter descriptions were updated accordingly.
*/

import { ConnectionConfig, Connection } from './modules/connection';
import Consumer = require('./modules/consumer');
import Producer = require('./modules/producer');
import { ConnectionHooks, ConsumerHooks, ProducerHooks } from './modules/hooks';
import arnavmq = require('./modules/arnavmq');

declare function arnavmqFactory(config: ConnectionConfig): arnavmq.Arnavmq;

declare namespace arnavmqFactory {
export type ArnavmqFactory = (config: ConnectionConfig) => arnavmq.Arnavmq;

export { ConnectionConfig, Connection, Consumer, Producer, ConnectionHooks, ConsumerHooks, ProducerHooks };
}

export = arnavmqFactory;
31 changes: 31 additions & 0 deletions types/modules/arnavmq.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import Producer = require('./producer');
import Consumer = require('./consumer');
import { Connection } from './connection';
import { ConnectionHooks, ConsumerHooks, ProducerHooks } from './hooks';

declare function arnavmq(connection: Connection): arnavmq.Arnavmq;

declare namespace arnavmq {
export type Arnavmq = {
connection: Connection;
consume: typeof Consumer.prototype.consume;
subscribe: typeof Consumer.prototype.consume;
produce: typeof Producer.prototype.produce;
publish: typeof Producer.prototype.produce;
consumer: {
consume: typeof Consumer.prototype.consume;
subscribe: typeof Consumer.prototype.consume;
};
producer: {
produce: typeof Producer.prototype.produce;
publish: typeof Producer.prototype.produce;
};
hooks: {
connection: ConnectionHooks;
consumer: ConsumerHooks;
producer: ProducerHooks;
};
};
}

export = arnavmq;
32 changes: 32 additions & 0 deletions types/modules/channels.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type amqp = require('amqplib');

interface ChannelConfig {
prefetch: number;
}

declare class Channels {
constructor(connection: amqp.Connection, config: ChannelConfig);
private readonly _connection: amqp.Connection;
private readonly _config: ChannelConfig;
private readonly _channels: Map<string, { chann: Promise<amqp.Channel>; config: ChannelConfig }>;
get(queue: string, config: ChannelConfig): Promise<amqp.Channel>;
defaultChannel(): Promise<amqp.Channel>;
/**
* Creates or returns an existing channel by it's key and config.
* @return {Promise} A promise that resolve with an amqp.node channel object
*/
private _get(key: string, config?: ChannelConfig): Promise<amqp.Channel>;
private _initNewChannel(key: string, config: ChannelConfig): Promise<amqp.Channel>;
}

declare class ChannelAlreadyExistsError extends Error {
constructor(name: string, config: ChannelConfig);
name: string;
config: ChannelConfig;
}

declare namespace channels {
export { Channels, ChannelAlreadyExistsError, ChannelConfig };
}

export = channels;
101 changes: 101 additions & 0 deletions types/modules/connection.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import amqp = require('amqplib');
import channels = require('./channels');
import { Logger } from './logger';
import { ConnectionHooks } from './hooks/connection_hooks';

interface ConnectionConfig {
/**
* amqp connection string
* @default 'amqp://localhost'
*/
host?: string;

/**
* number of fetched messages at once on the channel
* @default 5
*/
prefetch?: number;

/**
* requeue put back message into the broker if consumer crashes/trigger exception
* @default true
*/
requeue?: boolean;

/**
* time between two reconnect (ms)
* @default 1000
*/
timeout?: number;

/**
* the maximum number of retries when trying to send a message before throwing error when failing. If set to '0' will not retry. If set to less then '0', will retry indefinitely.
* @default -1
*/
producerMaxRetries?: number;

/**
* default timeout for RPC calls. If set to '0' there will be none
* @default 15000
*/
rpcTimeout?: number;

/**
* Suffix all queues names. Defaults to empty string.
* For example, a queue service-something with suffix :ci becomes service-something:ci etc.
* @default ''
*/
consumerSuffix?: string;

/** generate a hostname so we can track this connection on the broker (rabbitmq management plugin) - defaults to host from environment or random uuid */
hostname?: string;

/** A logger object with a log function for each of the log levels ("debug", "info", "warn", or "error"). */
logger?: Logger;
}

declare class Connection {
constructor(config: ConnectionConfig);

private _connectionPromise: Promise<amqp.Connection>;
private _config: ConnectionConfig;
public hooks: ConnectionHooks;

get config(): ConnectionConfig;
set config(value: ConnectionConfig);

getConnection(): Promise<amqp.Connection>;
getChannel(queue: string, config: channels.ChannelConfig): Promise<amqp.Channel>;
getDefaultChannel(): Promise<amqp.Channel>;
/**
* Register an event on the default amqp.node channel
* @param on the channel event name to be bound with
* @param func the callback function to execute when the event is called
*/
addListener(on: string, func: Function): Promise<void>;

private _connect(): Promise<amqp.Connection>;
}

declare function connection(config: ConnectionConfig): Connection;

declare namespace connection {
export interface Connection {
getConnection(): Promise<amqp.Connection>;
getChannel(queue: string, config: channels.ChannelConfig): Promise<amqp.Channel>;
getDefaultChannel(): Promise<amqp.Channel>;
/**
* Register an event on the default amqp.node channel
* @param on the channel event name to be bound with
* @param func the callback function to execute when the event is called
*/
addListener(on: string, func: Function): Promise<void>;

get config(): ConnectionConfig;
set config(value: ConnectionConfig);
}

export { ConnectionConfig };
}

export = connection;
63 changes: 63 additions & 0 deletions types/modules/consumer.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { ChannelConfig } from './channels';
import { Connection } from './connection';
import { ConsumerHooks } from './hooks';
import type amqp = require('amqplib');

type ConsumeOptions = amqp.Options.AssertQueue & {
channel: ChannelConfig;
};
type ConsumeCallback = (body: unknown, properties: amqp.MessageProperties) => Promise<unknown> | unknown;

declare class Consumer {
constructor(connection: Connection);
hooks: ConsumerHooks;
private set connection(value: Connection);
get connection(): Connection;
/**
* Sends the RPC reply to the response queue according to the message properties when required.
* @param messageProperties An amqp.node message properties object, containing the rpc settings
* @param queue The initial queue on which the handler received the message
* @param reply the received message to reply the rpc if needed:
* @return The message properties if it is not an rpc request, or a boolean indicating the produce result when an rpc response was produced.
*/
private checkRpc(
messageProperties: amqp.MessageProperties,
queue: string,
reply: unknown,
): Promise<boolean | amqp.MessageProperties>;
/**
* Create a durable queue on RabbitMQ and consumes messages from it - executing a callback function.
* Automatically answers with the callback response (can be a Promise)
* @param queue The RabbitMQ queue name
* @param options (Optional) Options for the queue (durable, persistent, etc.) and channel (with prefetch, `{ channel: { prefetch: 100 } }`)
* @param callback Callback function executed when a message is received on the queue name, can return a promise
* @return A promise that resolves when connection is established and consumer is ready
*/
consume(queue: string, options: ConsumeOptions, callback: ConsumeCallback): Promise<true>;
/**
* Create a durable queue on RabbitMQ and consumes messages from it - executing a callback function.
* Automatically answers with the callback response (can be a Promise)
* @param queue The RabbitMQ queue name
* @param callback Callback function executed when a message is received on the queue name, can return a promise
* @return A promise that resolves when connection is established and consumer is ready
*/
consume(queue: string, callback: ConsumeCallback): Promise<true>;

/** @see Consumer.consume */
subscribe(queue: string, options: ConsumeOptions, callback: ConsumeCallback): Promise<true>;
/** @see Consumer.consume */
subscribe(queue: string, callback: ConsumeCallback): Promise<true>;

private _initializeChannel(queue: string, options: ConsumeOptions, callback): Promise<amqp.Channel>;
private _consumeQueue(channel: amqp.Channel, queue: string, callback: ConsumeCallback): Promise<void>;
private _rejectMessageAfterProcess(
channel: amqp.Channel,
queue: string,
msg: amqp.Message,
parsedBody: unknown,
requeue: boolean,
error: Error,
): Promise<void>;
}

export = Consumer;
47 changes: 47 additions & 0 deletions types/modules/hooks/base_hooks.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
declare class BaseHooks {
/**
* A map between an event name to a set of callbacks registered for it.
* Function shape varies between different events.
*/
private _events: Map<string, Set<Function>>;
/**
* Registers a callback or array of callbacks to an event.
* Callback function shape may vary according to the event type.
* Upon a hook trigger, the callbacks for it will be invoked one by one, but without a particular order.
* The user who registers the callback has the responsibility to handle any error inside of it. Throwing an error inside a callback will propagate it outside to the top level, aborting the process that triggered it.
* @param event The event name to register.
* @param callback A callback or array of callbacks to register for the event.
*/
protected _on(event: string, callback: Function | Function[]): void;
/**
* Registers a number of callbacks for an event.
* @param event The event name to register.
* @param callbacks A callback array register for the event.
*/
private _manyOn(event: string, callbacks: Function | Function[]): void;
/**
* Unregister a callback or array of callbacks from an event.
* Callbacks must be a reference to the same callbacks that registered.
* @param event The event to unregister.
* @param callback A callback or array of callbacks to unregistered from the event.
*/
protected _off(event: string, callback: Function | Function[]): void;
/**
* Unregister a number of callbacks from an event.
* @param event The event to unregister.
* @param callbacks A callback array to unregistered from the event.
*/
private _manyOff(event: string, callbacks: Function[]): void;
/**
* Trigger an event, calling all callbacks registered to it with the given payload.
* @param source The class/object that triggered the event. Will be bound as the 'this' argument of the callbacks.
* @param eventName The name of the event to trigger.
* @param payload The event to pass to the registered callbacks as an argument.
* @public
*/
public trigger(source: unknown, eventName: string, payload: unknown): Promise<void>;

private _getCallbacks(events: string): Set<Function>;
}

export = BaseHooks;
Loading
Loading