This file is for AI coding agents (and humans who like density) working on
@imqueue/rpc. It captures how the codebase is built, tested and structured,
plus the invariants that are easy to get wrong. Read it before making changes.
For contribution process/terms see CONTRIBUTING.md; for
end-user docs see the README and https://imqueue.org/.
@imqueue/rpc is the typed RPC layer of the @imqueue framework. It provides the
abstract base classes and decorators to build self-describing services and
the machinery to generate strongly-typed clients from them — all over the
Redis-backed message queue in @imqueue/core.
No service discovery, no load balancer, no hand-written HTTP layer: a service
describes itself and its client is generated from that description.
- ESM only,
"type": "module". Useimport, notrequire(). Import sibling modules with the.jsextension (NodeNext resolves it to the.tssource). - TypeScript,
module/moduleResolution: nodenext,target: es2024,verbatimModuleSyntax: true,isolatedModules: true,strict: true(strictPropertyInitialization: false). Useimport typefor type-only imports. - Standard (TC39) decorators —
experimentalDecoratorsis false and theesnext.decoratorslib is enabled. There is noemitDecoratorMetadata, so there is no runtime type reflection. - Types are derived from JSDoc. Because decorators carry no type metadata,
the RPC layer parses JSDoc
@param/@returnstags (viaacorn) to build a service's description and its generated client's types. Consequences:removeCommentsMUST stayfalse, and every exposed method MUST carry accurate JSDoc — undocumented params fall back toanyin generated clients. - Node ≥ 22.12.
- Runtime deps:
@imqueue/core,acorn(JSDoc/source parsing for client generation),typescript(compiling generated clients on the fly). - Lint/format:
oxlint+oxfmt. Runnpm run formatbefore committing; CI checksnpm run format:check. - Build emits
.js/.d.ts/.js.mapnext to sources; gitignored, not committed.buildrunsbuild:deps(builds../coreif present) thenclean-compiledthentsc.
npm install
npm run build # build ../core (if present) + clean + tsc
npm test # build + node:test over every test/**/*.spec.js
npm run lint # oxlint
npm run format # oxfmt (write) | npm run format:check (verify)
npm run test-coverage # tests + experimental coverageTests use the native node:test runner with
--experimental-test-module-mocks and preload ./test/warmup.mjs; timeout is
15s. A local Redis on localhost:6379 is expected for the integration-style
specs.
| Path | Role |
|---|---|
index.ts |
export * from './src/index.js' and export * from '@imqueue/core' — consumers get core's queue API re-exported. |
src/IMQService.ts |
IMQService abstract base — extend it and decorate methods with @expose(). |
src/IMQClient.ts |
IMQClient base + IMQClient.create() — generates/compiles/loads a client from a running service's description. |
src/decorators/expose.ts |
@expose() — marks a service method remotely callable. |
src/decorators/remote.ts |
@remote() — marks a hand-written client method. |
src/decorators/classType.ts, property.ts, indexed.ts |
Complex-type registration: @classType() on the class, @property('type', optional?) on fields. |
src/decorators/cache.ts, lock.ts, logged.ts, metadata.ts |
Cross-cutting method decorators. |
src/cache/ (ICache, RedisCache) |
Caching layer for RPC results. |
src/helpers/signature.ts |
JSDoc/source signature extraction (acorn). |
src/IMQRPCDescription.ts, IMQRPCRequest.ts, IMQRPCResponse.ts, IMQRPCError.ts, IMQDelay.ts, IMQLock.ts, IMQMetadata.ts, IMQRequestContext.ts |
RPC wire types & context. |
- Only
@expose()-decorated methods are remotely callable. Non-exposed methods stay private to the service. - Arguments and return values must be JSON-serializable.
- No spread/rest params on exposed methods — the generated client will not
compile. Pass an array instead:
foo(args: any[]), notfoo(...args: any[]). - Complex types need
@classType()on the class and@property()on each field, or they will not appear in generated clients.@indexed()registers@propertyfields too. - JSDoc is load-bearing, not documentation-only (see toolchain note). Keep
@param/@returnstypes accurate.
import { IMQService, expose } from '@imqueue/rpc';
export class UserService extends IMQService {
/**
* Returns a user by id
* @param {string} id - user identifier
* @return {Promise<{ id: string; name: string } | null>}
*/
@expose()
public async get(id: string): Promise<{ id: string; name: string } | null> {
return { id, name: 'Jane Doe' };
}
}Generate the client with @imqueue/cli (imq client generate UserService) or
at runtime with IMQClient.create('UserService'), then
const c = new UserServiceClient(); await c.start(); await c.get('42');.
GPL-3.0. Commercial licensing for closed-source products: https://imqueue.com/.