API Reference

Application programming interface documentation for the @imqueue packages. Packaging follows nesting, so importing @imqueue/rpc re-exports everything from @imqueue/core.

Full API Reference

Browse the complete generated reference for the latest release — every class, interface, decorator and function, with signatures and types. These pages always live at /latest/, so a bookmark or link keeps working across releases.

Looking a symbol up by name rather than browsing? /api/search-index.json lists every exported symbol of the current majors as {name, kind, package, url, summary}, with deprecated: true on obsolete members — one fetch instead of a crawl. llms.txt has advertised it for a while; this page had never mentioned it, which meant the mirror the MCP server's get_doc returns for /api/ did not either.

Older versions
@imqueue/rpc 2.1.0 1.17.1
@imqueue/core 2.0.26 1.15.0

Data & events

Observability

API composition

Hardening & validation

Background work

Introduction

core and rpc are the framework spine — the runtime API documented below, and the two packages every @imqueue service is built on. Capability packages such as pg-pubsub or async-logger layer on top of them, each publishing its own generated reference under /api/. cli is different again: a rapid-development command-line tool installed globally, documented by a handwritten manual rather than a generated reference.

rpc re-exports the entire @imqueue/core surface, so a single import is enough whether you use only the core features or the RPC features as well. For example, these two imports are equivalent:

import { profile, IMQMode } from '@imqueue/core';
import { profile, IMQMode } from '@imqueue/rpc';

Both work because profile and IMQMode are defined in core, which is a dependency of rpc.

There is exactly one exception: export * never forwards a default export, so core's default-exported IMQ factory is not available from rpcimport IMQ from '@imqueue/rpc' yields undefined. Import it from @imqueue/core directly.

This pairing is specific to core and rpc. The capability packages do not re-export their @imqueue dependencies: @imqueue/pg-cache and the rest export only their own surface, so a service using one still imports @imqueue/rpc for the framework types.

Using v3? @imqueue 3.x ships as native ES modules and requires Node.js 22.12 or newer. If you're upgrading from 2.x, see the Migration from 2.x to 3.x section below.

RPC API

The RPC API is a high-level framework for building client–server communication between services using the Remote Procedure Call pattern. If you want the full set of @imqueue features, this is the API to build on.

Configuration

Configuration options are usually passed to a Service or Client constructor. In a real-world setup, the cleanest approach is to map option values from environment variables into the object at instantiation, giving you full flexibility at deployment time. Every service scaffolded from the boilerplate includes a config.ts file where these options can be set, for example:

export const serviceOptions: Partial<IMQServiceOptions> = {
    cluster: JSON.parse(process.env['REDIS_CLUSTER_CONFIG']),
    safeDelivery: !!process.env['MQ_SAFE_DELIVERY'],
    // etc...
};

This lets a service be reconfigured by the environment it runs in. For local deployment you can define environment variables in your shell (global configuration) or in a .env file in the service's root directory (per-service configuration). On platforms such as AWS you can pass variables in from Parameter Store, and so on.

Generic options (Service and Client)

  • host — Redis server host. Default: "localhost".
  • port — Redis server port. Default: 6379.
  • cluster — defines a cluster of Redis servers, used instead of the host/port pair when provided. It's an array of Redis host/port pairs; @imqueue automatically distributes messages across the configured cluster nodes.
  • prefix — the Redis key prefix used for @imqueue key/value pairs. Default: "imq".
  • logger — a reference to a logger implementation, used by the whole library. Default: console. The implementation must satisfy the ILogger interface.
  • safeDelivery — enables or disables safe message delivery. Default: false (off). When on, reading a message moves it atomically out of the queue into a worker-owned key instead of popping it outright, so a worker that dies before it starts on a message leaves that message behind to be re-queued rather than taking it down with the process. The guarantee covers that hand-off, not the processing: the worker key is released as soon as the message reaches the handler, so a worker killed mid-handler loses it exactly as it would with safe delivery off. Delivery is at-least-once in either mode, so handlers should be idempotent, and draining in-flight work before exit is up to the application.
  • safeDeliveryTtl — time to live, in milliseconds, for a message leased by a consumer instance — how long it stays in a worker key before the watcher sweeps it back onto the main queue. Default: 5000 (5 seconds). This is a recovery deadline for an abandoned hand-off, not a processing deadline: a slow handler is neither interrupted nor re-queued for taking too long, so raising this value extends no protection over long-running work. Note it also sets the maintenance sweep interval that drives cleanup, whether or not safe delivery is on.
  • useGzip — enables or disables gzip compression. Default: false (off). @imqueue exchanges messages as plain JSON. If your messages are large, compression can be a sensible way to reduce traffic between consumer instances and Redis nodes. It trades worker CPU for bandwidth: on the published benchmark it cost about 15% of throughput and cut a ~1 KB payload by roughly 70%. Both producer and consumer of a queue must use the same setting — a mismatch makes deserialization fail and the message is dropped permanently, even under safe delivery.

Client-only options

There are several ways to create and instantiate service clients:

  1. Using only pre-generated client source files.
  2. Instantiating clients dynamically, with or without generating source files.
  3. Implementing clients manually, when you need to.

The following options select among these modes:

  • compile — allows the generated client module to be interpreted by JavaScript on the fly. Default: on, so dynamically generated clients work out of the box with no extra configuration. Can be turned off if needed.
  • write — enables or disables persistence of the generated client code. Default: on.
  • path — where the generated client source files are written. Default: "./src/clients".

Service and Client

IMQ is built so that you focus only on service development — the client is generated automatically from the service's description. Every @imqueue service is self-describing, so your only responsibilities are writing good descriptions (correct type definitions and doc-blocks) and, of course, implementing the functionality itself.

This keeps development simple: you write the service, and the framework handles the rest.

If you have special requirements, there's nothing stopping you from implementing clients by hand — but that adds significant work, both to build and, more importantly, to maintain afterwards.

@imqueue provides the IMQService and IMQClient abstract base classes for concrete implementations to extend.

Each service is treated as a package containing at least one service class. A complex service may consist of several classes, though in microservice terms that's usually best avoided.

A minimal, empty service looks like this:

import { IMQService } from '@imqueue/rpc';

export class SomeService extends IMQService {
    // service implementation goes here
}

A service is an ordinary TypeScript class — it can have private, protected and public methods, and any properties you like. A few rules must be followed to make it work correctly:

  • Methods that aren't exposed are not accessible remotely. To make a method remotely callable, you must expose it with the @expose() decorator:
    import { IMQService, expose } from '@imqueue/rpc';
    
    export class SomeService extends IMQService {
      @expose()
      public exposedMethod() { /* implementation... */ }
    
      public unexposedMethod() { /* implementation... */ }
    }
  • Only methods can be exposed. You cannot expose class properties.
  • To run asynchronous setup during service initialization, override the start() method:
    import { IMessageQueue, IMQService, expose } from '@imqueue/rpc';
    
    export class SomeService extends IMQService {
      public async start(): Promise<IMessageQueue | undefined> {
        // async setup here — e.g. open a database connection...
        return super.start();
      }
    }
  • Prefer the injected logger over console for debug, info, warning and error output:
    import { IMQService, expose } from '@imqueue/rpc';
    
    export class SomeService extends IMQService {
      @expose()
      public loggedStuff() {
        this.logger.log('I am a logged string!');
      }
    }
  • Because remote calls send data across the network, all arguments and return values of exposed methods must be JSON-serializable.
  • You cannot use the spread operator for exposed-method arguments — it won't work on the generated client (a known limitation). Pass such arguments as an array instead:
    import { IMQService, expose } from '@imqueue/rpc';
    
    export class SomeService extends IMQService {
      // INCORRECT — the client for this service will not compile
      @expose()
      public incorrectStuff(...args: any[]) {
        args.forEach((arg) => {
          // do something with arg...
        });
      }
    
      // CORRECT
      @expose()
      public correctStuff(args: any[]) {
        args.forEach((arg) => {
          // do something with arg...
        });
      }
    
      // Also fine — this one isn't exposed
      private somePrivateStuff(...args: any[]) {
        args.forEach((arg) => {
          // do something with arg...
        });
      }
    }
    
    (async () => {
      // assuming we have a client for the service:
      const client = new SomeServiceClient();
      await client.start();
    
      // we'd like to write this:
      client.incorrectStuff(1, 2, 3);
      // but we do this instead — hardly a hardship:
      client.correctStuff([1, 2, 3]);
    })();

The importance of doc-blocks

JavaScript and TypeScript offer little reflection tooling, so there's no easy way to recover argument and return-value types at runtime. @imqueue works around this by reading the doc-blocks attached to your class methods to build its service description.

This is good practice from every angle: well-written doc-blocks make code more readable and self-documenting, and let you auto-generate API docs. In @imqueue's case they're also mandatory — the framework needs them to describe a service and to generate a correctly working client.

Here's what you need to know about writing doc-blocks for @imqueue services:

  • Always use @param and @return tags with a proper type for every input argument and return value:

    import { IMQService, expose } from '@imqueue/rpc';
    
    export class SomeService extends IMQService {
      /**
       * Some method description goes here
       *
       * @param {string} argOne - description of the first argument
       * @param {boolean} argTwo - description of the second argument
       * @param {{name: string, value: number}} [argThree] - description of the third, optional argument
       * @return {{x: number, y: number}} - description of the return value
       */
      @expose()
      public someMethod(
        argOne: string, argTwo: boolean, argThree?: { name: string, value: number },
      ): { x: number, y: number } {
        // do something with the args...
        return { x: 5, y: 7 };
      }
    }
  • Wrap optional argument names in [] in the doc-block — this is what marks them as optional on the generated client.

  • Use TypeScript type notation in doc-blocks for arguments and return values; it's carried through into the generated client code.

  • The documented @param list is also what the service's argument-count check validates, so it must match the method's real arity — a mismatch rejects calls with IMQ_RPC_INVALID_ARGS_COUNT. An undocumented type falls back to any rather than failing.

  • Compile the service project with removeComments: false. The doc-blocks are read from the emitted sources, so stripping comments leaves the generator nothing to work from.

Complex types

@imqueue services support complex types in the data exchanged between a client and a service. A few rules ensure that both sides understand and use them correctly.

Define a complex type's interface on the service side using a class, not a TypeScript interface. This is because a class exists in the compiled JavaScript, whereas an interface exists only in TypeScript and is gone at runtime. There's a second wrinkle: JavaScript classes don't support bare properties (only getters and setters), so while TypeScript is happy with a plain property definition, @imqueue needs extra metadata to see it at the JavaScript level. That metadata is supplied by the @property() decorator:

function property(type: string | Thunk | any, isOptional?: boolean): any

The type is normally a type-definition string, as in the examples below ('string', 'Address', 'Address[]'). It can also be a constructor, whose name is used, or an anonymous Thunk returning either — which is what a self- or forward-referencing type needs, since a thunk isn't invoked until the description is first read. The thunk must be anonymous: a named function is taken for a constructor and resolves to its own name.

Note that isOptional is not inferred from TypeScript's ? modifier — pass true explicitly, as the examples below do.

Since v3.x, each complex-type class must also be annotated with the @classType() class decorator. @imqueue v3 uses standard (TC39) decorators, under which @property() only collects field metadata — the class-level @classType() then finalizes and registers that metadata as a named type, so that both the service and the generated client recognise it:

function classType(): any

Omitting it produces no error — the type is simply missing from the RPC type description, and generated clients then reference an undeclared type. @indexed() performs the same flush in addition to recording an index signature, so a class carrying @indexed() does not also need @classType().

Putting it together:

// service-side definition of the type:
import { classType, property } from '@imqueue/rpc';

@classType()
class UserObject {
    @property('string')
    firstName: string;

    @property('string')
    lastName: string;

    @property('string')
    email: string;

    @property('string', true)
    phoneNumber?: string;
}

This compiles, on the client side, to a matching TypeScript interface, so the type can be used for correct type-checking on both sides:

// generated client-side definition of the type
interface UserObject {
    firstName: string;
    lastName: string;
    email: string;
    phoneNumber?: string;
}

The type can then be used in service methods (assuming the definition lives in its own file):

import { IMQService, expose } from '@imqueue/rpc';
import { UserObject } from './types/UserObject';

class UserService extends IMQService {
    /**
     * Updates a user record
     *
     * @param {UserObject} data - user data fields
     * @return {Promise<UserObject | null>} - the saved user, or null on failure
     */
    @expose()
    public async update(data: UserObject): Promise<UserObject | null> {
        // do logic...
        return data;
    }
}

This guarantees correct type-checking at the client level.

Complex types can nest other complex types. Suppose we extend the user model to be associated with one or more address objects:

import { classType, property } from '@imqueue/rpc';

@classType()
class AddressObject {
    @property('string')
    country: string;

    @property('string')
    city: string;

    @property('string')
    address: string;

    @property('string', true)
    phoneNumber?: string;
}

@classType()
class UserObject {
    @property('string')
    firstName: string;

    @property('string')
    lastName: string;

    @property('string')
    email: string;

    @property('AddressObject[]', true)
    addresses?: AddressObject[];
}

Working with the service description

If you ever need to access a service's description metadata, just call describe() on a service client:

// assuming this runs in an async context:
const client = new UserClient();
await client.start();
console.log(await client.describe());

This prints all the metadata about the service's classes, methods and complex types.

Delayed messaging

Delayed messaging with @imqueue/rpc is easy: any exposed service method can be called with a delay. This is handy for building scheduling queues. The generator appends two optional trailing parameters to every generated client method — call metadata (of type IMQMetadata) first, then the delay (of type IMQDelay):

import { IMQDelay, IMQMetadata } from '@imqueue/rpc';
import { UserObject, UserClient } from './clients';

const client = new UserClient();
const data: UserObject = {
    firstName: 'John',
    lastName: 'Doe',
    email: 'john@doe.com',
};

await client.start();
// run the scheduled work in 1 hour, and handle the result asynchronously
// once it completes, without blocking:
client
    .doScheduledStuff(
        data,
        undefined,            // metadata slot — skipped
        new IMQDelay(1, 'h'), // delay is always last
    )
    .then((result: any) => client.logger.log(result));

Both trailing parameters are optional, and an ordinary call passes neither. When all you want is a delay, skip the metadata slot with undefined as above: from 3.4.0 a trailing undefined on a delayed call is a placeholder and is never delivered to the service. Pass a real IMQMetadata bag there when you have one — tracing context, an audit reason — and the delay still goes last.

The one form to avoid is the delay dropped straight into the metadata slot. The client strips these two parameters by identity, not by position, so it works at runtime — but it does not type-check without a cast, and the cast is what makes the placeholder rules below observable. Keep the delay last instead.

On older versions the placeholder behaves differently. 3.3.1 dropped only one trailing undefined, and only when no metadata was passed, so skipping an optional declared argument as well still delivered it — as null, which means a defaulted parameter did not fall back to its default. 3.3.0 and earlier delivered the placeholder itself, and a method whose declared parameters are all required rejected the call with IMQ_RPC_INVALID_ARGS_COUNT. An undelayed call drops nothing, on every version.

For the patterns this enables — one-shot reminders, sweepers, backoff — see delayed and scheduled work without a job system.

Reading call metadata

Whatever a caller puts in the metadata bag travels with the request, and the service reads it without threading it through its own method signatures. IMQService binds every in-flight request to the async execution context, so currentMetadata() works inside any exposed method — and inside anything that method awaits:

import { IMQService, expose, currentMetadata } from '@imqueue/rpc';

class OrderService extends IMQService {
    /**
     * Ships the order with the given identifier
     *
     * @param {string} orderId - order identifier
     * @return {Promise<void>}
     */
    @expose()
    public async ship(orderId: string): Promise<void> {
        const meta = currentMetadata();

        this.logger.info('shipping', orderId, meta?.reason);
    }
}

The bag is opaque, and it arrives as plain JSON — read its fields directly rather than expecting an IMQMetadata instance. Outside of a request, at start-up for instance, currentMetadata() returns undefined. Don't declare an IMQMetadata parameter on an exposed method to receive it: the generator treats a trailing IMQMetadata or IMQDelay parameter as its own and strips it from the client it builds.

Locking

Locking is a powerful tool in @imqueue/rpc for optimising remote calls. Imagine one of your service processes receives hundreds or thousands of calls to the same method, with the same execution context, in a very short window — and each returns the same result. This happens, for example, when popular content is requested by many users but stays effectively static over that short period. Without help, your back-end runs the same work hundreds or thousands of times a second for no real reason. Locks fix this.

When @lock() wraps a method, the first matching call acquires an asynchronous lock. Until that call resolves, all other calls with the same signature wait; when it resolves, they're all resolved with the same result.

These are not distributed locks. The lock lives in the memory of one Node.js process, so the coalescing described here happens per process. Separate processes, multiProcess cluster workers and service replicas each maintain their own independent locks and will run the guarded code concurrently — four replicas under the same burst do the work four times, not once. That is usually still a large win, but if you need genuine mutual exclusion across replicas, use a Redis- or database-backed lock. See IMQLock for the precise scope.

For example, if a specific blog post is fetched 100 times over 100 milliseconds, and the database fetch itself takes about 100 milliseconds, the actual logic runs just once — and all 100 waiting clients are resolved with the same value:

import { IMQService, expose, lock } from '@imqueue/rpc';
import { BlogPost } from './types';

class BlogService extends IMQService {
    /**
     * Returns the blog post for a given identifier
     *
     * @param {string} id - blog post identifier
     * @return {Promise<BlogPost>} - the blog post data
     */
    @lock()
    @expose()
    public async fetchPost(id: string): Promise<BlogPost> {
        let data: BlogPost;
        // fetch the blog post from the database by id...
        return data;
    }
}

Meanwhile, concurrent calls for different blog post identifiers run under their own contextual locks and resolve their clients with their own values.

This offers two advantages:

  1. Lower back-end load — less logic executed, fewer database calls.
  2. Better average response time — the first caller waits longest, but the last is served almost instantly, so the average across all callers improves.

There is a trade-off. To identify the execution context, the locking mechanism hashes the call's signature, which costs time and CPU. The algorithm is efficient, but its cost grows with the length of the method signature. In some cases you may gain nothing from locking, so weigh it up: for high-load, slow methods it's clearly worth it; when the method is cheaper to run than its signature is to hash, it isn't.

Locking isn't limited to a method decorator. IMQ also provides a general-purpose asynchronous lock class, IMQLock, that you can use wherever you need it across your back-end.

Caching

Caching is another optimisation tool @imqueue provides. It caches a method's results using a caching adapter (Redis is the default, and currently the only built-in one). You can supply your own adapter by implementing the ICache and ICacheConstructor interfaces.

Out of the box, use the @cache() decorator on service methods, or work with the IMQCache registry and the RedisCache engine directly.

Typical usage:

import { IMQService, expose, cache } from '@imqueue/rpc';
import { BlogPost } from './types';

class BlogService extends IMQService {
    /**
     * Returns the blog post for a given identifier
     *
     * @param {string} id - blog post identifier
     * @return {Promise<BlogPost>} - the blog post data
     */
    @cache()
    @expose()
    public async fetchPost(id: string): Promise<BlogPost> {
        let data: BlogPost;
        // do stuff...
        return data;
    }
}

Like @lock(), @cache() works per call signature. All of @imqueue's decorators can be combined on a service method to improve overall performance and stability. Write load tests for your back-end and use them to find the best optimisation strategy for your case.

Messaging API

The Messaging API is the low-level API implementing the Message Queue pattern used for inter-service communication. Reach for it when you need only the messaging layer in your code.

This API concerns the messaging-engine adapter, its configuration, logging injection and profiling.

The IMQ factory and adapters

The IMQ factory constructs message-queue instances. Currently IMQ ships with a Redis adapter out of the box. Prefer creating instances through the factory rather than constructing a queue class yourself: it picks the right implementation for the options you pass — supplying cluster or clusterManagers gets you a ClusteredRedisQueue instead of a RedisQueue, with no change at the call site.

IMQ is the default export of @imqueue/core, so import it without braces. Note that export * never forwards a default: it is the one part of the core surface @imqueue/rpc does not re-export, so import it from @imqueue/core directly.

Example:

import IMQ from '@imqueue/core';

const mq = IMQ.create('MyMQ', { vendor: 'Redis' });

You don't need to specify the vendor — 'Redis' is the default, and currently the only supported value; IMQ.create() throws a TypeError for anything else. The factory builds only the adapters the framework ships with, so a queue of your own is instantiated directly rather than through it:

import { MyMQAdapter } from './path/to/MyMQAdapter.js';

const mq = new MyMQAdapter('MyMQ');

Any such adapter must implement the IMessageQueue interface, extending EventEmitter and emitting 'message' and 'error' events.

The factory performs no I/O, so the queue it returns is not connected — call start() on it, or send(), which starts the queue implicitly.

Redis Queue

RedisQueue is the core Redis-based message-queue implementation, providing the engine for a single Redis node.

Clustered Redis Queue

ClusteredRedisQueue extends RedisQueue to work across a cluster of Redis nodes, with automatic round-robin load balancing between them.

Profiling and debugging

Profiling and debugging matter throughout the development and ongoing support of any system.

@imqueue provides a simple, built-in tool for measuring and debugging service method execution: the @profile() decorator. Apply it to the parts of the system you most need to keep an eye on.

Profiled timing is reported in microseconds by default.

Usage:

import { IMQService, expose, profile } from '@imqueue/rpc';

class MonitoredService extends IMQService {
    @profile()
    @expose()
    public exposedStuff() {
        // call some internals:
        this.internalStuff(1, 2, 3);
        // do anything else...
    }

    @profile()
    private internalStuff(...args: any[]) {
        for (let i = 0; i < 100000; i++) {
        }
    }

    @profile({ enableDebugTime: true })
    private forcedTimeProfiling(...args: any[]) {

    }

    @profile({ enableDebugArgs: true })
    private forcedArgsProfiling(...args: any[]) {

    }

    @profile({ enableDebugTime: true, enableDebugArgs: true })
    private forcedFullProfiling(...args: any[]) {

    }
}

Called with no arguments, @profile() follows the environment configuration, which can turn profiling on or off. A ProfileDecoratorOptions object overrides the environment and forces time and/or argument profiling explicitly — but only for fields passed as real booleans; any other value is ignored and the environment default applies.

Whether timing and argument logging are enabled is resolved once, when the class is defined, so changing process.env later has no effect.

We recommend managing profiling state through .env files (per service) or by setting the variables globally (for the whole environment). Those variables are:

  • IMQ_LOG_TIME=1|0 — enables or disables execution-time profiling. Empty is treated as 0, the default.
  • IMQ_LOG_ARGS=1|0 — enables or disables argument debug logging. Empty is treated as 0, the default.
  • IMQ_LOG_TIME_FORMAT="microseconds"|"milliseconds"|"seconds" — sets the time format in the debug output. Empty is treated as "microseconds", the default.

@profile() writes through the logger property of the instance it decorates — any ILogger. Inside a service class that property is already there, so the decorator needs no extra setup. It also works on any class method, not just service classes, but then the logger is yours to provide: an instance with no logger profiles the method and produces no output at all, with no warning. Static methods are never logged, because the logger is looked up on instances only.

import { profile } from '@imqueue/core';

class SomeClass {
    // the decorator logs through this property only;
    // without a logger nothing is ever written
    public logger = console;

    @profile()
    protected someProtectedMethod() {
    }
}

Note that enabling profiling can slightly reduce overall back-end performance — but it's invaluable for diagnosing and eliminating bottlenecks and slow code paths.

Migration from 2.x to 3.x

Version 3.x is a modernization release of @imqueue/core and @imqueue/rpc. It moves the packages to native ES modules and standard TypeScript decorators, built with TypeScript 7. The public runtime API is largely the same, but the following changes require attention when upgrading from 2.x.

ES modules and Node

Both packages are now published as native ES modules ("type": "module") and require Node.js 22.12 or newer. In practice this means:

  • Import @imqueue/core / @imqueue/rpc from ESM code; require() of these packages is no longer supported.
  • In your own project, use ESM as well and add the .js extension to relative import specifiers (Node's nodenext resolution), for example import { UserObject } from './types/UserObject.js';.

Standard decorators and tsconfig

@imqueue 3.x uses standard (TC39) decorators instead of the legacy experimental implementation. Update your tsconfig.json accordingly:

{
  "compilerOptions": {
    // remove these — legacy decorators are no longer used:
    // "experimentalDecorators": true,
    // "emitDecoratorMetadata": true,

    // use a modern target and the standard-decorators metadata lib:
    "target": "es2024",
    "lib": ["es2024", "esnext.decorators"],
    "module": "nodenext",
    "moduleResolution": "nodenext",

    // keep this: doc-blocks are the only type source the client
    // generator reads, so stripping comments leaves it nothing
    "removeComments": false
  }
}

@classType() is now required on complex types

Under standard decorators, @property() only collects field metadata — it no longer registers the class itself. Every complex type must now be annotated with the new @classType() class decorator (see Complex Types). Add it to each @property()-decorated class:

// 2.x
import { property } from '@imqueue/rpc';

class UserObject {
    @property('string')
    firstName: string;
}

// 3.x
import { classType, property } from '@imqueue/rpc';

@classType()
class UserObject {
    @property('string')
    firstName: string;
}

Removed helpers

A number of general-purpose utilities that these packages exported alongside their real API have been removed. Most had a standard-library equivalent by the time 3.x was cut; the rest were internals that were never meant to be public.

Removed from @imqueue/core (and therefore from @imqueue/rpc, which re-exports it):

Removed export Replacement
uuid() randomUUID() from node:crypto
promisify() promisify from node:util
sha1() createHash('sha1') from node:crypto
IJson JsonObjectIJson was only ever an alias for it
intrand() no equivalent — inline your own random-integer helper
propertiesOf() no equivalent — walk the prototype chain yourself if you need it
pack() / unpack() internal message codec — removed; useGzip covers compression on the wire
buildOptions() internal helper — removed, inline your own option merge
copyEventEmitter() internal helper — removed

Removed from @imqueue/rpc:

Removed export Replacement
fileExists() / mkdir() / writeFile() node:fs/promises
osUuid() no equivalent — it returned a machine UUID; use randomUUID() if a per-process id will do
signature() internal — the call-signature hash behind @lock() and @cache()
pid() / forgetPid() / IMQ_PID_DIR / IMQ_TMP_DIR internal PID-file bookkeeping — removed
SIGNALS internal — see IMQOptions.handleSignals

For example:

// 2.x
import { uuid } from '@imqueue/core';
const id = uuid();

// 3.x
import { randomUUID } from 'node:crypto';
const id = randomUUID();

The rest of the runtime API — IMQ, RedisQueue, ClusteredRedisQueue, profile, IMQService, IMQClient, and the @expose() / @lock() / @cache() / @property() decorators — is unchanged, @classType() above being the one addition you must make.

Last updated

Read this page as plain markdown — no HTML, no navigation. For pasting into an LLM, or for an agent to fetch.