# @imqueue — Full documentation > RPC over an inter-communication messaging queue for service-oriented Node.js & TypeScript back-ends. This file concatenates the framework documentation as a single markdown document for AI ingestion. Canonical HTML: https://imqueue.org/. Commercial licensing: https://imqueue.com/. Generated API reference (not included here): https://imqueue.org/api/. --- # Clients & Versioning Source: https://imqueue.org/cli/clients-and-versioning/ Generate strongly-typed RPC clients from running services, and bump versions across many services to trigger CI. ## Generating typed RPC clients `imq client generate` produces a strongly-typed RPC client from a **running** service — the service must be up (and Redis reachable) so its interface can be introspected; otherwise generation fails. ```bash imq client generate [path] ``` | Flag | Meaning | |---|---| | (positional) `name` | service name to generate a client for (required) | | (positional) `path` | directory to place the client file (default: cwd) | | `-o, --overwrite` | overwrite an existing client without prompting | ```bash # from within a project, service "billing" running locally imq client generate billing ./src/clients -o ``` Typical flow during development: ```bash imq ctl start -s billing -c # bring the service up and wait for readiness imq client generate billing ./src/clients imq ctl stop -s billing ``` ## Bumping versions across many services `imq service update-version` releases a new version across one or many services under a directory and pushes, triggering CI builds. ```bash imq service update-version [branch] ``` | Flag | Meaning | |---|---| | (positional) `path` | directory containing the services (or a single service) | | (positional) `branch` / `-b` | branch to checkout/use (default `master`) | | `-n, --npm-version`, `--bump` | bump type: `major\|minor\|patch\|prerelease` (default `prerelease`). Unlike `imq up`, `update-version` does not constrain the keyword — any value is passed through to `npm version`. | For each detected service it runs, stopping that service on the first failing step: ``` git checkout → git pull → npm version → git push --follow-tags ``` Detection here is by **loading the built module** and checking whether any export derives from `IMQService` (by walking the prototype chain — the service class need not be named `*Service`). Compare with `imq up`/`imq ctl`, which detect by scanning source. Use `update-version` for a release action against built, committed services; use `imq up` for dependency maintenance. ```bash # patch-release every service under ./services on the main branch imq service update-version ./services main -n patch ``` ## `update-version` vs `up` {#update-version-vs-up} | | `imq service update-version` | `imq up` | |---|---|---| | Purpose | release/version bump | dependency maintenance | | Detects services by | module load (prototype chain) | source scan (`extends IMQService/IMQClient`) | | Touches deps? | no | yes (`ncu -u` + reinstall) | | Git flow | checkout → pull → version → push | (optionally) commit → version → push | | Branch control | `-b/[branch]` | uses current branch | --- # Configuration Source: https://imqueue.org/cli/configuration/ How the CLI resolves options across config layers, the structured v4 schema, secrets, git transport, and every environment variable. The CLI reads configuration from three layers, merged with a strict precedence so that scripted runs are deterministic: ``` CLI flag → per-service .imqrc.json → global ~/.imq/config.json → interactive prompt (TTY only) → built-in default ``` A prompt is only shown when the process is attached to a TTY and no earlier layer supplied a value; otherwise the default is used, or — for a required value with no default (author, email, and a VCS namespace/token on a real create) — the command fails fast with a clear error instead of hanging. This is what lets `imq service create … --dry-run` and CI pipelines run without blocking on input. ## Quick start ```bash imq config init ``` An interactive wizard walks you through the four axes and stores your answers globally. Do this once after installation to make later commands short. When you enable a VCS host, the wizard **auto-detects the git transport**: it defaults to `ssh` if you have SSH keys in `~/.ssh` (overridable with `IMQ_SSH_DIR`) and `https` otherwise, reports which it picked and why, and lets you change it — see [Git transport](#git-transport-for-the-initial-push-https-vs-ssh). ## Managing config values ```bash imq config get # print every set option as "key = value" imq config get --json # print the whole config as JSON (-j for short) imq config get ci.provider # print a single value imq config set ci.provider circleci imq config set vcs.namespace my-org imq config set packages opentelemetry,pg-cache # comma list OR a JSON array imq config set vcs.provider giturb # rejected: prints the valid list imq config check # exit 0 if initialized, 1 otherwise (for scripts) ``` `get`/`set` accept **dot-paths** into the structured config (e.g. `registry.region`, `vcs.auth.token`). The file is written with `0600` permissions because it may hold secrets (VCS token, registry password). Setting a structured key (`vcs.*`, `ci.*`, `registry.*`, `packages`, `templatesRef`) also updates the mapped legacy keys, so a config written by v4 still works if the CLI is downgraded to v3. ## The structured (v4) schema `~/.imq/config.json` holds a structured view built from these groups: ### `vcs` — version control host | Key | Meaning | |---|---| | `vcs.provider` | `github` \| `gitlab` \| `bitbucket` | | `vcs.namespace` | user / organization / workspace that owns new repos | | `vcs.private` | create repositories as private (`true`/`false`) | | `vcs.protocol` | git transport for the create-time push: `https` (default) \| `ssh` | | `vcs.auth.token` | API/personal-access token for repo creation & secrets | ### `ci` — continuous integration | Key | Meaning | |---|---| | `ci.provider` | `github-actions` \| `circleci` \| `travis` | | `ci.auth.token` | token used to enable the repo / set CI secrets (CircleCI, Travis) | ### `registry` — container registry | Key | Meaning | |---|---| | `registry.provider` | `dockerhub` \| `google` \| `aws-ecr` \| `azure-acr` | | `registry.namespace` | image namespace / repository / ACR name | | `registry.region` | region (Google Artifact Registry, AWS ECR) | | `registry.project` | GCP project id (Google) | | `registry.accountId` | AWS account id (ECR) | | `registry.auth.user` / `registry.auth.password` | registry credentials (DockerHub) | ### Top-level | Key | Meaning | |---|---| | `packages` | default addon packages added to new services (array) | | `templatesRef` | git ref of the templates repo to use (default `master`) | Example `~/.imq/config.json`: ```json { "vcs": { "provider": "github", "namespace": "my-org", "private": true }, "ci": { "provider": "github-actions" }, "registry": { "provider": "google", "project": "my-gcp-proj", "region": "europe-west1" }, "packages": ["opentelemetry", "pg-cache"], "templatesRef": "master" } ``` ## Per-service overrides: `.imqrc.json` {#per-service-overrides-imqrcjson} When a service is created, its resolved providers and packages are written to a committed `.imqrc.json` at the service root. Later commands (and re-creations) read it, so a service always rebuilds with the tools it was born with — even if your global defaults have since changed. A `.imqrc.json` value overrides the global config but is still overridden by an explicit CLI flag. ```json { "vcs": { "provider": "gitlab", "namespace": "team-x" }, "ci": { "provider": "circleci" }, "packages": ["sequelize", "tag-cache"] } ``` ## Secrets and tokens Tokens can be provided by (in order of preference): 1. A CLI flag for one-off use: `-T, --github-token ` (used for any VCS host, not only GitHub). 2. The config: `vcs.auth.token`, `ci.auth.token`, `registry.auth.password`. 3. An interactive prompt. Because the config file may contain these, it is always written `0600`. Prefer per-invocation flags or environment injection in shared CI environments. ## Git transport for the initial push (HTTPS vs SSH) {#git-transport-for-the-initial-push-https-vs-ssh} When `imq service create` commits and pushes the new repository, it uses one of two transports, selected by `vcs.protocol` (or the `--git-protocol` flag): | `vcs.protocol` | Push behavior | |---|---| | `https` (**default**) | Push over `https://…` **authenticated with the access token** that created the repo. The token is used **only for that push** (via an ephemeral `http.extraHeader`) and is never written into the repository's `.git/config`, which keeps a clean, token-free remote URL. | | `ssh` | Push over the host's `git@…:…` SSH URL using **your own SSH keys/agent**. No token is injected — you need working SSH access to the namespace. | Precedence is the usual one: `--git-protocol` flag → `.imqrc.json` → global config → the `https` default. **Why HTTPS is the default.** The access token that just created the repo is guaranteed to have write access, so the push succeeds even for a private organization repo where your SSH key — or a *different* "active" git/gh account — has no access (the classic misleading `Repository not found` on push). Choose `ssh` when you specifically rely on SSH keys (e.g. org policy, hardware-key signing, or an SSH-only host): ```bash imq config set vcs.protocol ssh # make ssh the default for new services imq service create my-svc ./my-svc --git-protocol https # or override per run ``` > `IMQ_GIT_REMOTE_BASE` still overrides the push target entirely (custom / > self-hosted git or integration testing) and takes precedence over > `vcs.protocol`; no token is injected in that mode. ## Backward compatibility {#backward-compatibility} A configuration written by 3.x uses legacy keys (`gitBaseUrl`, `gitHubAuthToken`, `gitRepoPrivate`, `useGit`, `useDocker`, `dockerHubNamespace`, `dockerHubUser`, `dockerHubPassword`). The CLI: - **reads** them transparently and derives an equivalent structured view (github + travis + dockerhub, namespace parsed from `gitBaseUrl`); - **writes** both the structured keys *and* their legacy equivalents, so a config remains usable if you downgrade the CLI. You do not need to migrate anything by hand. ## Environment variable reference {#environment-variable-reference} | Variable | Effect | |---|---| | `IMQ_CLI_HOME` | Base for `~/.imq` (sandboxing / CI). | | `IMQ_NO_UPDATE_CHECK` | Skip the npm self-update check. | | `IMQ_TEMPLATES_REPO` | Override the templates git URL (fork or SSH). | | `IMQ_GITHUB_API_URL` | GitHub API base — set to a GitHub Enterprise host. | | `IMQ_GITLAB_API_URL` | GitLab API base — self-managed GitLab. | | `IMQ_BITBUCKET_API_URL` | Bitbucket API base — a Bitbucket Cloud 2.0-compatible endpoint. | | `IMQ_CIRCLECI_API_URL` | CircleCI API base. | | `IMQ_TRAVIS_API_URL` | Travis API base. | | `IMQ_GIT_REMOTE_BASE` | Base for the git remote used on commit/push (testing seam). | | `IMQ_SSH_DIR` | SSH directory inspected for keys when auto-detecting the git transport (defaults to `~/.ssh`). | | `CIRCLE_TOKEN` | CircleCI token fallback (used when `ci.auth.token` is unset). | These are also the seams used by the test harness; in production they enable enterprise / self-hosted deployments without any code change. See [Providers](/cli/providers/#enterprise--self-hosted) and [Extensibility](/cli/extensibility/). ### Cloud-registry credentials (read at create time) When a service is dockerized against a cloud registry, `imq service create` reads the following from the invoking environment and provisions them as CI secrets on the new repository. If a variable is unset, **no secret is provisioned** for it — the CLI reports which secrets were and weren't set, and the CI's `docker login` will fail until you add them manually. | Registry | Environment variables | |---|---| | `google` (Artifact Registry) | `GCP_SA_KEY` (service-account JSON key) | | `aws-ecr` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` | | `azure-acr` | `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET` | Docker Hub credentials come from `registry.auth.user`/`registry.auth.password` (config) or an interactive prompt instead. --- # Creating Services Source: https://imqueue.org/cli/creating-services/ imq service create — scaffold a service from a template and, optionally, create the repo, provision CI secrets, commit, push and tag it. `imq service create` scaffolds a new @imqueue service from a template and, optionally, creates the remote repository, provisions CI secrets, commits, pushes and tags it. ```bash imq service create [path] ``` If omitted, `name` defaults to the current directory's name and `path` to `.` (the current directory). ## The four axes Service creation is organized around four independent, pluggable axes, so you can mix the tools you actually use. Each resolves via the standard precedence (flag → `.imqrc.json` → global config → prompt → default). | Axis | Flag | Choices (default **bold**) | |---|---|---| | VCS host | `--vcs` | **github**, gitlab, bitbucket | | CI provider | `--ci` | **github-actions**, circleci, travis | | Container registry | `--registry` | **dockerhub**, google, aws-ecr, azure-acr | | Addon packages | `--packages` | (none) — see [Package Catalog](/cli/package-catalog/) | CI choices are filtered to those compatible with the selected VCS host. See [Providers](/cli/providers/) for the details and tokens each one needs. ## Options ``` imq service create [name] [path] -a, --author Author full name (person or organization) -e, --email Author contact email -g, --use-git Turn on automatic repo creation [boolean] --vcs VCS host: github | gitlab | bitbucket -u, --github-namespace VCS namespace (user, organization, workspace) --ci CI provider: github-actions | circleci | travis --registry Registry: dockerhub | google | aws-ecr | azure-acr --region Registry region (google, aws-ecr) --project GCP project id (google) --account-id AWS account id (aws-ecr) --packages Comma-separated addon packages (--no-packages = none) --no-install Do not run npm install after scaffolding [boolean] -V, --service-version Initial version [default: "1.0.0-0"] -H, --homepage Homepage URL -B, --bugs-url Bug tracker URL -l, --license SPDX id or path to a custom license file -t, --template Template name, git url, or local directory -d, --description Service description -n, --node-versions Node version tags for CI (comma-separated) -D, --dockerize Enable dockerization in CI builds [boolean] -L, --node-docker-tag Base node docker tag -N, --docker-namespace Registry namespace / repository / ACR name -T, --github-token VCS auth token (any host, not only GitHub) -p, --private Create the repository private [boolean] --dry-run Print the resolved plan and exit [boolean] -y, --yes Skip the confirmation prompt [boolean] ``` ## Preview with `--dry-run` Always safe, makes no changes. Prints the fully-resolved plan — providers, repo URL, image reference, packages — exactly as it would execute: ```bash imq service create billing ./billing \ --vcs gitlab --ci circleci --registry google \ --project my-proj --region europe-west1 \ --packages opentelemetry,pg-cache --dry-run -a "My Org" -e dev@my-org.io ``` A dry run makes no network calls, so it does **not** require a VCS namespace or auth token — those are shown as `` in the plan. It does still validate the always-required inputs (author and email). Use it in scripts and CI to preview a given set of flags before committing to a real run. ## What a run does (pipeline) When repo creation is enabled (`-g`/`--use-git` or a configured VCS), a full run performs, in order: 1. **Resolve** the plan from all config layers. 2. **Scaffold** the service from the template (token substitution + addon overlays); generate `src/.ts` and its test; merge addon dependencies. 3. **Write `.imqrc.json`** with the resolved choices (no secrets). 4. **Create** the remote repository on the VCS host. 5. **Provision CI** — enable the repo and set secrets (e.g. GitHub Actions sealed secrets, CircleCI env vars, Travis RSA-encrypted vars). 6. Compile the **CI/docker** tokens. 7. **Install** dependencies (`npm install`) unless `--no-install`. 8. **Initialize git** locally (sets a local commit identity from the author/ email so it works even without a global git identity), **commit**, add the **remote**, **push**, and **tag** the initial version. By default the push is sent over **HTTPS and authenticated with the same access token** that created the repo (injected only for that push, never written into the repository's git config). This is what makes a push to a private org repo succeed even when your SSH key — or a different "active" git/gh account — has no access to it. The remote left in `.git/config` is the clean, token-free HTTPS URL. To push over **SSH** with your own keys instead, pass `--git-protocol ssh` (or set `vcs.protocol: ssh`) — see [Configuration → Git transport](/cli/configuration/#git-transport-for-the-initial-push-https-vs-ssh). 9. **Report** any addon instructions and environment variables you must set. Without repo creation, the remote/CI-secret/commit/push steps (4, 5, 8) are skipped; scaffold, `.imqrc.json`, CI/docker token compilation and install still run. ### Failure & rollback - The target directory is only removed on failure if the CLI **created** it — a pre-existing directory (or the current/home directory) is never deleted. A non-empty target is refused up front unless you pass `--force`. - If the remote repository was already created when a later step fails, you are asked (interactively) whether to **delete** it (full roll back) or **keep** it and fix the problem manually — the prompt spells out both outcomes and **defaults to keeping** it. Non-interactively it is always left in place with a notice, so nothing is destroyed silently. - Enabling CI and provisioning secrets are **non-fatal**: on failure the CLI prints what to do manually and continues. It reports which registry secrets were actually provisioned rather than assuming success. > The generated service targets ESM + TypeScript + the native `node:test` > runner, matching the current default template. Run `npm test` inside it out > of the box. ## Non-interactive / CI usage Provide everything via flags (or config) and add `-y` to skip confirmation. A real (non-dry-run) create with a VCS host needs an auth token — pass `-T`/ `--vcs-token` (or set `vcs.auth.token` in config): ```bash imq service create orders ./orders -y \ -a "My Org" -e dev@my-org.io -l MIT \ --vcs github -u my-org -T "$GITHUB_TOKEN" --ci github-actions \ --registry dockerhub -N myorg --no-install ``` ## Templates `--template` accepts a **name** (a bundled or custom template), a **git URL**, or a **local directory**. With no flag the default template is used, fetched over public HTTPS and pinned to the `templatesRef` from your config (default `v4`). See [Custom Templates](/cli/custom-templates/) to build your own. ## The generated `.imqrc.json` The resolved providers and packages are committed to `.imqrc.json` in the new service so later commands and re-creations reuse them. Edit it to change a single service's tools without touching your global defaults — see [Configuration](/cli/configuration/#per-service-overrides-imqrcjson). --- # Custom Templates Source: https://imqueue.org/cli/custom-templates/ Use the built-in default, a published template, or your own — pointed at by name, git URL or local path — with %TOKEN substitution and fragment overlays. Templates are the boilerplate `imq service create` clones and compiles into a new service. You can use the built-in default, a published template, or your own — pointed at by name, git URL, or local path. ## Selecting a template `--template` (`-t`) accepts: | Form | Example | |---|---| | **name** | `-t default` (bundled or a named custom template in `~/.imq/custom-templates`) | | **git URL** | `-t https://github.com/my-org/imq-template.git` | | **local directory** | `-t ./my-template` | With no flag, the default template is fetched over public **HTTPS** and pinned to the `templatesRef` from your config (default `master`). Override the source repo entirely with `IMQ_TEMPLATES_REPO` (e.g. your fork, or an SSH URL for contributors): ```bash export IMQ_TEMPLATES_REPO=git@github.com:my-org/templates.git imq config set templatesRef main ``` ## Template versions (v1 vs v2) A template is **v2** when it contains an `imq-template.json` manifest; otherwise it is treated as legacy **v1**. New templates should be v2. ### The manifest — `imq-template.json` ```json { "version": 2, "description": "My org's @imqueue service template (ESM, TS, node:test)", "ciFiles": "provider" } ``` | Field | Meaning | |---|---| | `version` | manifest version — `2` for the current format | | `description` | human-readable description | | `ciFiles` | documents that CI files are emitted by the selected CI provider (rather than shipped in the template). This is descriptive: **any** v2 template gets provider-emitted CI files - the field is not a switch | An absent or unreadable manifest → the template is compiled as v1. ## Token substitution Every file under the template is compiled: `%TOKEN` placeholders are replaced. The base tokens available to template files: | Token | Replaced with | |---|---| | `%SERVICE_NAME` | the service name | | `%SERVICE_CLASS_NAME` | the generated service class name | | `%SERVICE_VERSION` | the initial version | | `%SERVICE_DESCRIPTION` | the service description | | `%SERVICE_AUTHOR_NAME` / `%SERVICE_AUTHOR_EMAIL` | author name / `` | | `%SERVICE_REPO` / `%SERVICE_HOMEPAGE` / `%SERVICE_BUGS` | package.json repository / homepage / bugs fragments (from the VCS host) | | `%LICENSE_HEADER` / `%LICENSE_TEXT` / `%LICENSE_NAME` / `%LICENSE_TAG` | the license header block / full text / name / SPDX tag | | `%ADDON_PRELOAD` | addon early-init snippets (empty when no addons) | | `%ADDON_CONFIG` | addon configuration snippets (empty when no addons) | A token value is inserted verbatim, so `$` characters in an author name or license text are safe. Provider/registry/CI composition tokens (filled from the chosen providers) are also available in CI/Docker files — see [Providers](/cli/providers/#how-ci-and-registry-compose): `%IMAGE_REF`, `%REGISTRY_LOGIN`, `%REGISTRY_PUSH`, `%DOCKER_NAMESPACE`, `%DOCKER_SECRETS`, `%GHA_NODE_MATRIX`, `%GHA_SECRETS_ENV`, `%TRAVIS_NODE_TAG`. Package metadata fragments (repository, homepage, bugs) are derived and injected into `package.json` from the resolved VCS host and the `-H`/`-B` flags. ## Addon token points For a template to support the [Package Catalog](/cli/package-catalog/), place the two addon anchors where addon code should land — typically: - `%ADDON_PRELOAD` near the top of the entry file (`index.ts`), before other imports, for things like a tracing bootstrap; - `%ADDON_CONFIG` inside the service setup (`config.ts`), for configuration wiring. When no addons are selected, both compile to empty strings, so a template with these anchors still produces clean output. ## Fragment overlays Beyond whole-file compilation, providers and addons can **overlay file fragments** onto the scaffolded service (writing or replacing specific files by relative path). This is how a CI provider contributes its workflow file and how an addon contributes any extra files it needs — without the base template having to know about every provider or package. ## Writing your own template 1. Start from the default template (clone the templates repo, copy `default/`). 2. Keep or add `imq-template.json` (`version: 2`). 3. Author your files with `%TOKEN` placeholders; put `%ADDON_PRELOAD` / `%ADDON_CONFIG` where addon code should go. 4. Keep CI files out of the template if you want provider-emitted CI (`"ciFiles": "provider"`). 5. Point the CLI at it: ```bash imq service create demo ./demo -t ./path/to/my-template --dry-run ``` Iterate with `--dry-run` and local scaffolding until happy. 6. Publish by hosting the template in a git repo and sharing the URL, or drop it in `~/.imq/custom-templates/` and refer to it by ``. ## Adapting to an existing project You are not limited to greenfield services. Because a template is just a directory of files with tokens, you can encode your organization's conventions — lint config, tsconfig, Dockerfile, CI, license header, base dependencies, even a house `%ADDON_CONFIG` — into a custom template so every new service is born consistent with the rest of your codebase. Combine with a default `packages` list and a configured VCS/CI/registry to make `imq service create -y` produce a fully wired, on-brand service in one command. --- # Extensibility Source: https://imqueue.org/cli/extensibility/ The seams for adapting the tool to your environment — environment overrides, data-driven templates and catalog, and how contributors add new providers. The v4 architecture is built to be extended along its four axes without rewrites. This page explains the seams for adapting the tool to your environment and, for contributors, for adding new providers. ## The provider model Service creation composes four axes through a typed **provider registry**: - **VCS host** — creates the remote repo and stores CI secrets. Split from the **SCM tool** (git), so the commit/push flow is shared and other SCMs could be added later. - **CI provider** — enables the repo and sets secrets; contributes its workflow file as a fragment overlay. - **Container registry** — supplies the login/push/image-reference snippets. - **Package catalog** — data-driven addon libraries. CI and registry combine through generic shell-snippet tokens, so the surface is **M + N**, not **M × N** — see [Providers](/cli/providers/#how-ci-and-registry-compose). ## Adapting without code: environment seams Every network endpoint is overridable, which turns the built-in providers into enterprise/self-hosted ones and makes the whole tool testable: | Variable | Use | |---|---| | `IMQ_GITHUB_API_URL` | GitHub Enterprise Server | | `IMQ_GITLAB_API_URL` | self-managed GitLab | | `IMQ_BITBUCKET_API_URL` | Bitbucket Cloud 2.0-compatible endpoint | | `IMQ_CIRCLECI_API_URL` | CircleCI (or a proxy) | | `IMQ_TRAVIS_API_URL` | Travis (or a proxy) | | `IMQ_GIT_REMOTE_BASE` | base for the git remote on commit/push | | `IMQ_TEMPLATES_REPO` + `templatesRef` | your own template source & ref | ## Adapting without code: data - **Templates** are a git repo of files with `%TOKEN` placeholders and fragment overlays — no CLI release needed to change boilerplate. See [Custom Templates](/cli/custom-templates/). - **The addon catalog** (`catalog.json`) is data: groups, dependencies, injection snippets, extra files and advertised env vars. Publish new addons by editing it in your template source. See [Package Catalog](/cli/package-catalog/#extending-the-catalog). ## For contributors: adding a provider The providers live under `src/providers/` grouped by axis (`vcs/`, `ci/`, `registry/`, `scm/`) with shared types in `src/providers/types.ts` and registration in `src/providers/index.ts` (`registerBuiltinProviders()`). To add, say, a new VCS host: 1. Implement the VCS provider interface in `src/providers/vcs/.ts` (repo creation, secret storage), reading its API base from a new `IMQ__API_URL` env override for testability/enterprise. 2. Register it in `registerBuiltinProviders()`. 3. Add it to the `--vcs` choices and any CI-compatibility filtering. 4. Add unit tests under `test/src/providers/` (mirror the existing `vcs.spec.ts` / `ci.spec.ts` style, driving the provider against a mock API via the env override). The same shape applies to CI providers (implement `enable()`/`setSecrets()`, optional) and registries (supply the `%REGISTRY_LOGIN` / `%REGISTRY_PUSH` / `%IMAGE_REF` snippets). ## For contributors: the codebase in brief | Area | Location | |---|---| | Command entry (yargs) | `index.ts`, `src/*.ts`, `src/**/**.ts` | | Shared library | `lib/*.ts` (config, resolve, template, services, github, travis, …) | | Providers | `src/providers/**` | | Service creation | `src/service/create-*.ts` (plan → scaffold → pipeline) | | Addon catalog engine | `src/catalog/**` + `lib/catalog.json` | | Tests | `test/**` (native `node:test`, module mocks) | See **AGENTS.md** in the repo root for a deeper orientation aimed at contributors and AI coding agents (build/test commands, invariants, gotchas). --- # CLI User Guide Source: https://imqueue.org/cli/ Everything about the imq command — from installation to writing your own templates and adapting the tool to real-world projects. `@imqueue/cli` (the `imq` command) is a Rapid Application Development tool for the [@imqueue](/) framework — a Redis-backed RPC microservice toolkit for Node.js/TypeScript. It scaffolds services from templates, wires them to your VCS host, CI provider and container registry, generates strongly-typed RPC clients, and helps you run and maintain a whole fleet of services locally. This manual covers everything from installation to writing your own templates and adapting the tool to real-world projects. > **New in 4.x** — the old standalone shell tools `imqctl`, `imqlog` and > `imqup` are now native subcommands: `imq ctl`, `imq log`, `imq up`. > Service creation is built on a **four-axis provider model** (VCS host, CI, > registry, addon packages) that is fully backward compatible with 3.x > configs. See [Configuration](/cli/configuration/) and [Providers](/cli/providers/). ## The command surface at a glance | Command | What it does | |---|---| | `imq service create` | Scaffold a new service from a template; optionally create the remote repo, provision CI secrets, commit, push and tag. | | `imq service update-version` | Bump the version of one or many services on a branch and push, triggering CI. | | `imq client generate` | Generate a typed RPC client from a running service. | | `imq config` | `init` / `get` / `set` / `check` the CLI configuration. | | `imq completions` | Install/remove shell completions (bash & zsh). | | `imq ctl` | Start / stop / restart a bulk of local services. | | `imq log` | Tail and combine local service logs. | | `imq up` | Bulk-update service dependencies (and optionally version/commit/push). | ## Where to start 1. **[Installation](/cli/installation/)** — install the CLI and shell completions. 2. **[Configuration](/cli/configuration/)** — run `imq config init` to set your defaults (VCS host, CI, registry, namespaces, tokens). 3. **[Creating Services](/cli/creating-services/)** — scaffold your first service. 4. **[Managing Local Services](/cli/managing-local-services/)** — run many services at once during development. ## Design principles - **Non-interactive by default when it can be.** Every option resolves with a strict precedence (flag → per-service `.imqrc.json` → global config → interactive prompt → default), so CI and scripted runs never hang. - **Backward compatible.** A config written by 3.x keeps working; the new structured keys and the legacy keys are kept in sync. - **Data-driven where possible.** The addon package catalog and the templates live in a separate repo, so they can evolve without a CLI release. - **Testable and portable.** Every network endpoint has an environment-variable override, which also enables GitHub Enterprise, self-managed GitLab and Bitbucket Cloud-compatible endpoints. ## Conventions in this manual - `~/.imq/` is the CLI home; override the base with `IMQ_CLI_HOME`. - Shell snippets assume a POSIX shell. Windows users should use WSL or Git Bash. - Angle brackets `` mark required values; square brackets `[like-this]` mark optional ones. --- # Installation Source: https://imqueue.org/cli/installation/ Install @imqueue/cli, check requirements, upgrade from 3.x, and enable shell completions. ## Requirements - **Node.js ≥ 22.12.0** (the CLI is ESM and uses modern Node APIs). - **git** on your `PATH` (used for repo creation, commits, template fetch). - A running **Redis** if you intend to generate clients from live services or run services locally. - Optional, per feature: - An SSH key **or** nothing special — templates are fetched over public HTTPS by default. - `npm-check-updates` — installed automatically by `imq up` if missing. - Docker — only if you enable service dockerization. ## Install globally ```bash npm i -g @imqueue/cli ``` Verify: ```bash imq --version imq --help ``` Running `imq` with no arguments prints the command list. ## Upgrading ```bash npm i -g @imqueue/cli@latest ``` On every interactive run the CLI checks npm for a newer release and offers to self-update. To disable that check (e.g. in CI or slow networks): ```bash export IMQ_NO_UPDATE_CHECK=1 ``` ### Upgrading from 3.x The three standalone shell tools were folded into the `imq` binary. Update any scripts or aliases: | 3.x | 4.x | |---|---| | `imqctl start …` | `imq ctl start …` | | `imqlog …` | `imq log …` | | `imqup …` | `imq up …` | All options are unchanged. Your existing `~/.imq/config.json` continues to work untouched — see [Configuration](/cli/configuration/#backward-compatibility). ## Shell completions The CLI can install completion scripts for **bash** and **zsh**: ```bash imq completions on # append the completion block to ~/.bashrc or ~/.zshrc imq completions off # remove it ``` Then reload your shell or `source ~/.bashrc` (or `~/.zshrc`). The shell is detected from your login shell, not from transient `ZSH_*` variables, so it behaves correctly inside subshells. ## Files the CLI creates | Path | Purpose | |---|---| | `~/.imq/config.json` | Global configuration (written `0600` — may hold secrets). | | `~/.imq/templates/` | Cached clone of the templates repo. | | `~/.imq/custom-templates/` | Named custom templates you add. | | `~/.imq/var/` | Runtime state for `imq ctl`/`imq log`: `*.log` and `.pids`. | Override the base directory (useful for sandboxing or CI) with: ```bash export IMQ_CLI_HOME=/some/where # ~/.imq becomes /some/where/.imq ``` ## Uninstall ```bash npm r -g @imqueue/cli rm -rf ~/.imq # optional: remove cached templates, config, logs ``` --- # Managing Local Services Source: https://imqueue.org/cli/managing-local-services/ Run a whole fleet of services side by side with imq ctl, imq log and imq up — start, stop, tail logs and bulk-update dependencies. During development it is common to run several @imqueue services on your host at once. The `imq ctl`, `imq log` and `imq up` commands manage a whole fleet of service repositories sitting side-by-side in one directory. > These replace the 3.x shell tools `imqctl`, `imqlog`, `imqup`. Options are > unchanged. ## Service discovery `imq ctl` and `imq up` share the same discovery. When `-s/--services` is **not** given, they scan the target path (`-p`, default: current directory) for immediate sub-directories whose `src/` tree contains a class extending `IMQService` or `IMQClient`. This is a **source-level** scan — it needs neither a build nor a running service, so it works on freshly-cloned, uninstalled repos. Pass `-s alpha,beta` to target specific services and skip the scan. (`imq log` does not scan a path; it works off the `*.log` files already collected under `~/.imq/var`.) Runtime state lives under `~/.imq/var/`: - `~/.imq/var/.log` — captured stdout/stderr per service (truncated each time the service is started) - `~/.imq/var/.pids` — `service:pid` records of running masters, written incrementally as each service starts ## `imq ctl` — start / stop / restart / status ```bash imq ctl [-p path] [-s services] [-u] [-c] [-v] ``` | Flag | Meaning | |---|---| | `-p, --path` | directory containing the service repos (default: cwd) | | `-s, --services` | comma-separated service names (skips discovery) | | `-u, --update` | run `git pull` in each service before starting (a failed pull skips that service) | | `-c, --calm` | wait for each service to become ready before starting the next | | `-v, --verbose` | print total execution time | **Start** launches each service via its `npm run dev` script in its own process group, redirecting output to `~/.imq/var/.log`, and records the master pid. A service that is **already running** (its recorded pid is live) is skipped with a warning — use `restart` to restart it. **Calm mode** (`-c`) polls the log for the readiness marker `reader channel connected` before moving on, so services with startup dependencies come up in order. Because the log is truncated on start, the scan only sees the current run. If a service **exits during startup** it is reported at once (rather than waiting out the bounded timeout). **Stop** terminates each targeted service's entire process group with `SIGTERM` (so child processes die too), waits for it to actually exit, and escalates to `SIGKILL` if it refuses; a process that still won't die keeps its pid entry with a warning. It then runs each service's `npm run stop` script if it has one, and prints a summary. Pids of services you did not target are preserved. If no services are discoverable from the current directory (and no `-s` was given), `stop` falls back to stopping every tracked pid — so it works from anywhere. **Restart** = stop then start; it waits for the old process to fully exit before relaunching, so the two never run concurrently. **Status** lists each tracked service and whether its recorded pid is live or stale (honoring `-s`, and pruning stale entries it reports). A **start** that finds no services exits non-zero. `-s` accepts a comma list, repeated flags (`-s a -s b`), or both. ```bash # start everything under ~/work/services, waiting for each to be ready imq ctl start -p ~/work/services -c # see what is running imq ctl status -p ~/work/services # restart just two services, pulling latest first imq ctl restart -s billing,orders -u # stop everything imq ctl stop -p ~/work/services ``` ## `imq log` — combined logs ```bash imq log [services..] [-c] [-f] [-P] ``` | Flag | Meaning | |---|---| | (positional) | service names to show (default: all available logs) | | `-c, --clean` | delete collected logs and exit (scoped to the named services, or all logs when none are named) | | `-f, --follow` | follow appended data (default **on**; `--no-follow` dumps and exits) | | `--no-prefix` | do not prefix lines with the service name (`-P` for short) | When more than one log is shown, each line is prefixed with a coloured `[service]` tag so interleaved output stays readable. `--no-follow` is handy in scripts to snapshot current logs and return immediately. ```bash imq log # tail & combine every service log imq log billing orders # only these two imq log --no-follow # dump current logs and exit imq log --clean # wipe all collected logs imq log billing --clean # wipe only billing's log ``` ## `imq up` — bulk dependency update ```bash imq up [-p path] [-s services] [-v type] [-c] [-u] ``` | Flag | Meaning | |---|---| | `-p, --path` | directory containing the service repos (default: cwd) | | `-s, --services` | comma-separated service names (skips discovery) | | `-v, --npm-version`, `--bump` | version bump on commit: `major\|minor\|patch\|prerelease` (default `prerelease`) | | `-c, --commit` | commit, version-bump and push the update | | `-u, --skip-update` | skip the dependency update, perform other tasks only | For each service the update runs `git pull` → `ncu -u` ([npm-check-updates](https://www.npmjs.com/package/npm-check-updates), installed globally on first use if missing) → remove `node_modules` + `package-lock.json` → `npm install`. With `-c` it then commits `chore: dependencies update`, runs `npm version ` and `git push --follow-tags` — but **only when the working tree actually changed** (a stray *untracked* file does not count as a change). A step that fails aborts that service **before** any destructive step, is recorded, and the run continues with the next service; the command exits non-zero and prints a summary if any service failed. `-v` only accepts the four bump keywords (anything else is rejected). `imq up --skip-update` without `--commit` is a no-op and is rejected with a helpful message. Make sure services are not in a dirty git state before an update+commit run. ```bash # update deps everywhere, no git changes imq up -p ~/work/services # update, then patch-bump, commit and push each changed service imq up -p ~/work/services -c -v patch # only re-commit/bump (no dep update) — e.g. after a manual edit imq up -s billing --skip-update --commit -v minor ``` See [Real-World Scenarios](/cli/real-world-scenarios/) for end-to-end fleet workflows. --- # Package Catalog Source: https://imqueue.org/cli/package-catalog/ Add secondary @imqueue libraries to a new service with --packages, wired in automatically from a data-driven catalog. `imq service create --packages ` adds secondary @imqueue libraries to a new service and wires them in automatically. The catalog is **data** (`catalog.json`, shipped with the CLI and mirrored in the templates repo), so new addons can appear without a CLI release. ```bash imq service create billing ./billing --packages opentelemetry,pg-cache,tag-cache imq service create billing ./billing --no-packages # explicitly none ``` You can also set a default list globally so every new service gets them: ```bash imq config set packages opentelemetry,pg-cache ``` To see every available package id (grouped, with a one-line description): ```bash imq service packages # human-readable imq service packages --json # machine-readable ``` ## Groups Packages belong to groups. **Exclusive** groups accept at most one member; selecting two members of the same exclusive group is rejected with an error. | Group | Exclusive? | Members | |---|---|---| | **Tracing / APM** | yes | `opentelemetry`, `dd-trace` | | **ORM / database** | yes | `sequelize`, `prisma` | | **Service features** | no | `pg-cache`, `pg-pubsub`, `tag-cache`, `job`, `net`, `http-protect`, `graphql-dependency`, `type-graphql-dependency` | ## What each addon does when selected For every selected package the scaffolder: 1. **Merges its dependencies** (and devDependencies) into the service `package.json`, preserving the versions declared by the template/catalog. 2. **Injects wiring code** at the template's addon token points: - `%ADDON_PRELOAD` — imports / setup that must run early (e.g. tracing bootstrap before other imports). - `%ADDON_CONFIG` — configuration wiring inside the service setup. 3. May add **extra files** the addon needs. 4. **Prints required environment variables** after creation (e.g. tracing endpoints, database URLs), so you know exactly what to configure. ## Choosing addons interactively Run `imq config init` or `imq service create` on a TTY without `--packages` and you will get a multi-select for the feature group and single-selects for the exclusive groups. Non-interactive runs use your config/flags and never prompt. ## Extending the catalog {#extending-the-catalog} Because the catalog is data, you can publish new addons by editing `catalog.json` in your own fork of the templates repo (point the CLI at it via `IMQ_TEMPLATES_REPO` and `templatesRef`). Each entry declares its group, dependencies, the snippets to inject at the addon token points, any extra files, and the environment variables to advertise. See [Custom Templates](/cli/custom-templates/) and [Extensibility](/cli/extensibility/). --- # Providers Source: https://imqueue.org/cli/providers/ The typed provider registry behind the four axes — each VCS host, CI provider and container registry, the credentials it needs, and how CI and registry compose. Service creation composes four axes through a typed provider registry. This page details each provider, the credentials it needs, and how CI and registry providers combine. ## VCS hosts (`--vcs`) {#vcs-hosts---vcs} | Provider | Namespace means | Token | API base override | |---|---|---|---| | **github** (default) | user or organization | Personal Access Token with `repo` scope (and `admin:org` for org repos / Actions secrets) | `IMQ_GITHUB_API_URL` | | **gitlab** | user or group | Personal Access Token with `api` scope | `IMQ_GITLAB_API_URL` | | **bitbucket** | workspace | Bitbucket **Cloud** access token (repo admin), sent as a Bearer token | `IMQ_BITBUCKET_API_URL` | The VCS provider is responsible for **creating the remote repository** and, where applicable, storing CI secrets. The SCM tool (git) is deliberately split from the host, so the same git commit/push flow serves all three (and other SCMs could be added later). Provide the token with `-T/--github-token` (works for any host) or via `vcs.auth.token` in the config. The initial commit/push transport is set by `vcs.protocol` (`--git-protocol`): `https` (default) authenticates the push with the access token — injected only for that push, never persisted to `.git/config` — while `ssh` pushes over the host's SSH URL with your own keys. Each host declares the basic-auth username its token uses for HTTPS (`x-access-token` for GitHub, `oauth2` for GitLab, `x-token-auth` for Bitbucket). See [Configuration → Git transport](/cli/configuration/#git-transport-for-the-initial-push-https-vs-ssh). ## CI providers (`--ci`) | Provider | Secret mechanism | Token | API base override | |---|---|---|---| | **github-actions** (default) | libsodium **sealed-box** secrets via the GitHub API | GitHub token (same as VCS) | `IMQ_GITHUB_API_URL` | | **circleci** | project environment variables via the CircleCI API | CircleCI token | `IMQ_CIRCLECI_API_URL` | | **travis** (legacy) | RSA (PKCS1) **secure** variables | Travis token | `IMQ_TRAVIS_API_URL` | CI choices are filtered to those compatible with the chosen VCS host (e.g. GitHub Actions requires GitHub). `travis` is kept working for existing setups but is not recommended for new services. ## Container registries (`--registry`) | Provider | Extra inputs | Notes | |---|---|---| | **dockerhub** (default) | `registry.auth.user` / `password`, `-N` namespace | classic Docker Hub | | **google** | `--project`, `--region` | **Artifact Registry** (not the retired GCR) | | **aws-ecr** | `--account-id`, `--region` | Amazon ECR | | **azure-acr** | `-N` ACR name | Azure Container Registry | ## How CI and registry compose {#how-ci-and-registry-compose} Rather than hand-writing an M×N matrix of "CI provider × registry" build scripts, the CLI composes them through a small set of **generic shell-snippet tokens** that the CI template fills from the registry provider: - `%REGISTRY_LOGIN` — the login command(s) for the chosen registry - `%REGISTRY_PUSH` — the push command(s) - `%IMAGE_REF` — the fully-qualified image reference - `%DOCKER_NAMESPACE` / `%DOCKER_SECRETS` — namespace and required secret names - `%GHA_NODE_MATRIX` / `%GHA_SECRETS_ENV` / `%TRAVIS_NODE_TAG` — CI-specific rendering of node versions and secrets This keeps the number of moving parts at **M + N** instead of **M × N**: add a registry and every CI provider can push to it; add a CI provider and it can push to every registry. ## Enterprise / self-hosted {#enterprise--self-hosted} Every provider's API base URL is overridable, which turns the built-in providers into enterprise-ready ones with no code change: ```bash # GitHub Enterprise Server export IMQ_GITHUB_API_URL=https://github.mycorp.com/api/v3 # self-managed GitLab export IMQ_GITLAB_API_URL=https://gitlab.mycorp.com/api/v4 # an API-compatible Bitbucket endpoint / proxy export IMQ_BITBUCKET_API_URL=https://bitbucket.mycorp.com/api/2.0 ``` > The override relocates the API **base URL** only; it does not translate > between API dialects. The GitHub and GitLab providers speak the same API > shape as their enterprise/self-managed servers, so those work directly. The > Bitbucket provider speaks the **Bitbucket Cloud 2.0** API; point the override > at a Cloud-2.0-compatible endpoint (Bitbucket Server/Data Center's 1.0 API is > a different dialect and is not supported as-is). Combine with `IMQ_GIT_REMOTE_BASE` if your git remote host differs from the API host. These same variables are how the test suite exercises the providers against mock servers — see [Extensibility](/cli/extensibility/). ## Tokens: where they come from For any provider token the resolution order is: 1. `-T/--github-token` flag (one-off; applies to the active VCS host) 2. config: `vcs.auth.token`, `ci.auth.token`, `registry.auth.password` 3. for CircleCI, the `CIRCLE_TOKEN` environment variable (fallback for `ci.auth.token`) 4. interactive prompt (TTY only) A legacy `gitHubAuthToken` from a v3 config is only reused for the **github** host, never for gitlab/bitbucket. Because the config may store these, `~/.imq/config.json` is always written `0600`. In shared CI, prefer passing tokens per-invocation or via environment injection rather than persisting them. --- # Real-World Scenarios Source: https://imqueue.org/cli/real-world-scenarios/ End-to-end walkthroughs that combine the commands — new services on different stacks, a local fleet, dependency maintenance and coordinated releases. End-to-end walkthroughs that combine the commands. Each assumes you have run `imq config init` once (adjust to taste). ## 1. New service on GitHub + GitHub Actions + Docker Hub The default stack. One command, non-interactive: ```bash imq service create billing ./billing -y \ -a "Acme Inc" -e dev@acme.io -l MIT \ --vcs github -u acme --ci github-actions \ --registry dockerhub -N acme -D ``` This scaffolds the service, creates the `acme/billing` repo, provisions GitHub Actions sealed secrets, commits, pushes and tags `1.0.0-0`, and enables dockerized CI builds pushing to `acme/billing` on Docker Hub. The push goes over HTTPS authenticated with your token by default, so it works even for a private org repo your SSH key can't reach; add `--git-protocol ssh` to push with your own keys instead. ## 2. GitLab + CircleCI + Google Artifact Registry ```bash imq service create orders ./orders -y \ -a "Acme Inc" -e dev@acme.io -l MIT \ --vcs gitlab -u acme-group --ci circleci \ --registry google --project acme-prod --region europe-west1 \ --packages opentelemetry,pg-cache ``` Preview it first without touching anything: ```bash imq service create orders ./orders --dry-run \ --vcs gitlab -u acme-group --ci circleci \ --registry google --project acme-prod --region europe-west1 \ --packages opentelemetry,pg-cache -a Acme -e dev@acme.io ``` ## 3. GitHub Enterprise (self-hosted) Point the GitHub provider at your enterprise API; everything else is the same: ```bash export IMQ_GITHUB_API_URL=https://github.acme-corp.com/api/v3 imq service create payments ./payments -y \ --vcs github -u platform --ci github-actions \ -T "$GHE_TOKEN" -a "Acme Corp" -e platform@acme-corp.com ``` Analogously use `IMQ_GITLAB_API_URL` for self-managed GitLab, or `IMQ_BITBUCKET_API_URL` for a Bitbucket Cloud 2.0-compatible endpoint. See [Providers](/cli/providers/#enterprise--self-hosted). ## 4. A local fleet of services You have a folder `~/work/services` with several service repos side by side. ```bash cd ~/work/services # bring them all up, waiting for each to be ready, pulling latest first imq ctl start -u -c # watch combined, colour-prefixed logs imq log # generate a client for one of them while it runs (in another terminal) imq client generate billing ./billing/src/clients -o # restart a couple after code changes imq ctl restart -s billing,orders # stop everything when done imq ctl stop ``` ## 5. Fleet-wide dependency maintenance ```bash cd ~/work/services # update deps everywhere (rewrites package.json + reinstalls; no git commit) imq up # then patch-bump, commit and push each service that actually changed imq up -c -v patch ``` `imq up` installs `npm-check-updates` on first use if it is missing, and only commits/pushes services whose working tree changed. Make sure trees are clean before an `-c` run. ## 6. Coordinated release across services To cut a release across many services on a branch (triggering their CI): ```bash imq service update-version ~/work/services main -n minor ``` For each service it does `git checkout main → git pull → npm version minor → git push --follow-tags`, stopping a given service on the first failing step. Compare with `imq up` in [Clients & Versioning](/cli/clients-and-versioning/#update-version-vs-up). ## 7. Standardising new services for your org Encode your conventions once in a [custom template](/cli/custom-templates/) and set org defaults: ```bash export IMQ_TEMPLATES_REPO=git@github.com:acme/imq-templates.git imq config set templatesRef main imq config set vcs.provider github imq config set vcs.namespace acme imq config set ci.provider github-actions imq config set registry.provider dockerhub imq config set packages opentelemetry,pg-cache ``` Now every new service is one command and comes out fully wired and on-brand: ```bash imq service create ./ -y -a "Acme Inc" -e dev@acme.io ``` --- # Troubleshooting Source: https://imqueue.org/cli/troubleshooting/ Common issues and their fixes — hanging prompts in CI, template fetch and push failures, discovery problems, and how to reset everything. ## `imq config init` / prompts hang or fail in CI Prompts only appear on a TTY. In CI, provide values via flags or config and add `-y` to `service create`. If a value is missing and there is no TTY, the default is used rather than blocking. ## Template fetch fails / asks for SSH credentials The default template is fetched over public **HTTPS**, so no SSH key is required. If you overrode the source with an SSH URL via `IMQ_TEMPLATES_REPO`, either set up your SSH key or unset the variable to use the HTTPS default. To pin a different ref: `imq config set templatesRef `. ## `git commit` fails: "unable to auto-detect email address" The create pipeline sets a **local** git identity from the service author/email before committing, so this should not occur during `service create`. If you hit it in your own scripts, set a repo-local identity: ```bash git -C config user.name "Your Name" git -C config user.email "you@example.com" ``` ## Repo creation returns 401/403 Check the token and its scopes for the selected VCS host ([Providers](/cli/providers/#vcs-hosts---vcs)): GitHub needs `repo` (and `admin:org` for org repos / Actions secrets), GitLab needs `api`, Bitbucket needs a repository-scoped access token (sent as a Bearer token). Pass it with `-T` or set `vcs.auth.token`. ## Push fails with "Repository not found" (repo *was* created) The remote repository is created, but the final commit/push fails with `ERROR: Repository not found` / `Could not read from remote repository`. This is an SSH access problem: your SSH key (or the currently "active" git/gh account, if you have several) has no push access to the namespace — common with a private organization repo. By default the CLI now pushes over **HTTPS authenticated with the access token that created the repo**, which avoids this entirely. If you have explicitly selected SSH (`vcs.protocol: ssh` or `--git-protocol ssh`), either: - switch that service/config back to HTTPS — `imq config set vcs.protocol https` (or pass `--git-protocol https`); or - fix your SSH access to the namespace (add the key to the right account / add the account to the org), then re-run or push manually. See [Configuration → Git transport](/cli/configuration/#git-transport-for-the-initial-push-https-vs-ssh). ## Nothing happens on an enterprise/self-hosted host Set the matching API base URL and (if the git host differs) the remote base: ```bash export IMQ_GITHUB_API_URL=https://github.mycorp.com/api/v3 export IMQ_GIT_REMOTE_BASE=git@github.mycorp.com: ``` See [Configuration](/cli/configuration/#environment-variable-reference). ## `imq ctl` / `imq log` / `imq up` find no services Discovery scans immediate sub-directories of the path (`-p`, default cwd) for a class extending `IMQService`/`IMQClient` under `src/`. Ensure you point at the **parent** directory that contains the service repos, or pass `-s name1,name2` to target them explicitly. ## A service won't start with `imq ctl` - It must have an `npm run dev` script. Check `~/.imq/var/.log` for the actual error. - In calm mode (`-c`) the CLI waits for the log line `reader channel connected`; if your service never prints it, calm mode will warn and move on after a bounded wait — the service may still be running, just not detected as "ready". ## `imq ctl stop` didn't kill child processes Services are started in their own process group and stopped with a group `SIGTERM`. If a service double-forks outside its group, terminate it manually using the pid in `~/.imq/var/.pids`. ## `imq up` reports "Nothing to perform" `--skip-update` without `--commit` is a no-op. Either drop `--skip-update` (to update deps) or add `--commit` (to re-commit/bump without updating). ## `imq client generate` fails The target service must be **running** and Redis reachable so its interface can be introspected. Start it first (e.g. `imq ctl start -s -c`), then generate. ## Config or secrets leaked into a shared machine `~/.imq/config.json` is written `0600`. Prefer passing tokens per-invocation (`-T`) or via environment injection in shared/CI environments rather than persisting them in the config. ## Resetting everything ```bash rm -rf ~/.imq # config, cached templates, custom templates, logs, pids ``` Or sandbox a run entirely: `IMQ_CLI_HOME=/tmp/imq-sandbox imq …`. --- # Getting Started Source: https://imqueue.org/get-started/ The shortest path from an empty terminal to a running @imqueue service and a generated client. For a deeper, worked example see the Tutorial; the full technical reference lives in the API docs. ## Prerequisites Before you begin, make sure the following are installed and available on your system: - [Node.js](https://nodejs.org/en/) **22.12 or newer** — we recommend installing it through [NVM](https://github.com/nvm-sh/nvm#installing-and-updating). - [Redis](https://redis.io/download) — version 3.2 or newer. @imqueue uses Redis as its message-queue transport. - [Git](https://git-scm.com/downloads) — the command-line client. ## 1. Install the CLI Install the @imqueue command-line tool globally. It scaffolds services and generates clients for you, so you write features instead of boilerplate: ~~~bash npm i -g @imqueue/cli ~~~ On first run the installer offers to collect some initial configuration. You can fill it in now, or press `Ctrl+C` to skip and configure it later (or not at all — it is optional). ## 2. Configure (optional) `@imqueue/cli` works without any configuration. Defining a global configuration once is only worthwhile on larger projects with many services, where it saves you from repeating the same options on every command. To create or re-create the configuration at any time, run: ~~~bash imq config init ~~~ For the full setup details — requirements, upgrading and shell completions — see the [Installation](/cli/installation/) & [Configuration](/cli/configuration/) chapters of the CLI User Guide. ## 3. Enable shell completions Turning on completions for the `imq` command makes the CLI far more pleasant to use. Run: ~~~bash imq completions on ~~~ and follow the prompts. `bash` and `zsh` are supported. ## 4. Everyday usage The CLI exists to remove the boilerplate of building `@imqueue`-based back-end services. It does two main jobs for you: 1. Scaffold services from ready-made templates. 2. Generate client code for calling those services. ### 4.1 Create a service Scaffold a new service into a fresh directory: ~~~bash mkdir user-service cd user-service imq service create ~~~ Then open `src/UserService.ts` and implement the methods your service needs to expose. ### 4.2 Run the service Make sure a Redis server is running on the default port, then start the service in watch mode: ~~~bash npm run dev ~~~ ### 4.3 Generate a client Every @imqueue service is self-describing, so a fully typed client can be generated directly from a running service. With the service running, generate its client: ~~~bash mkdir clients cd clients imq client generate UserService ~~~ You can now import that client and call the service's methods remotely, with full type-checking and IDE auto-completion.

That's it — you've built and called your first @imqueue service.

Ready for more? Work through the Tutorial for a complete example application, or dive into the API reference.

--- # API Integration Source: https://imqueue.org/tutorial/api-service/ Put a GraphQL API in front of your services — @imqueue works beautifully with GraphQL. This is one of the most interesting parts of the system. The API service exposes an external, HTTP-based interface and orchestrates access to the back-end services we've already built. GraphQL is an ideal fit for this role — it acts as an orchestrator over the underlying services — which is why we've chosen it here. This tutorial won't teach GraphQL itself; we'll focus on the @imqueue integration. If you're new to GraphQL, learn it from the [official](https://graphql.org/learn/) [resources](https://www.graphql.com/tutorials/) first, or skip this part and refer to the [finished source code](https://github.com/imqueue-sandbox/api) of the API service we built for you on GitHub. ### Initializing the service Although the API service differs in structure from a typical @imqueue service, we can still use @imqueue/cli to scaffold it: ~~~bash cd ~/my-tutorial-app imq service create api ./api ~~~ This installs everything needed to work with @imqueue. Since we want a GraphQL server over HTTP rather than a classic @imqueue service, we'll add a few more dependencies and rework the start script: ~~~bash npm i --save express express-graphql graphql graphql-relay \ graph-fields-list graphql-validity \ body-parser compression helmet core-js npm i --save-dev @types/body-parser @types/compression \ @types/core-js @types/express @types/express-graphql \ @types/graphql @types/graphql-relay @types/helmet ~~~ > **NOTE.** The exact technology stack here isn't important. You could use Apollo > Server instead, or any other solution you prefer. We've picked Facebook's > reference stack over Express with a hand-selected set of add-ons, but this is > in no way mandatory. Now remove `./api/src/Api.ts` (we don't need it) and empty out `./api/index.ts`. We keep all the npm scripts from the @imqueue boilerplate — they work fine for us — but we change what the service does: instead of a classic service, it now starts an HTTP server via Express, with an endpoint that serves GraphQL requests. We won't walk through that setup in detail — implement it yourself if you're comfortable, or refer to the [source code](https://github.com/imqueue-sandbox/api) on GitHub. Take a closer look at [`index.ts`](https://github.com/imqueue-sandbox/api/blob/master/index.ts) and [`src/Application.ts`](https://github.com/imqueue-sandbox/api/blob/master/src/Application.ts). The heart of the @imqueue integration lives in the `bootstrapContext()` method of the `Application` class. There we instantiate all the @imqueue/rpc clients used to orchestrate requests to the underlying services, and start them as part of the API service's start-up. The resulting execution context is then handed to the GraphQL layer, which passes it down to every resolver in the schema — so any resolver can reach our services whenever it needs to. ~~~typescript import { clientOptions } from '../config.js'; import { user, auth, car, timeTable } from './clients/index.js'; class Application { // ... /** * Initializes the runtime context for the GraphQL application * * @return {Promise} - the initialized context */ private static async bootstrapContext(): Promise { const context: any = { user: new user.UserClient(clientOptions), auth: new auth.AuthClient(clientOptions), car: new car.CarClient(clientOptions), timeTable: new timeTable.TimeTableClient(clientOptions), }; await context.user.start(); await context.auth.start(); await context.car.start(); await context.timeTable.start(); return context; } // ... } ~~~ As we saw in [chapter 3](/tutorial/auth-service#the-service-client), clients are a core part of @imqueue/rpc — they provide the RPC mechanism for calling remote services. There we used a dynamically built client; here we'll build client code statically. ### Building the clients Generating static client code is a good way to work with @imqueue services, because it offers several concrete advantages: - You don't have to worry about service start-up order — you don't need a service running to build its client. - You get pre-built code that your IDE can read, so you can explore each service's interface during development, with auto-completion. - You can version your client code and manage compatibility between versions. There's one more benefit: with all clients kept in a single place, there's no duplicated client code to maintain. (In larger systems the same client might be generated and used at several network locations, which then requires managing client updates — but our case is simple, so we'll leave that problem aside.) Generating client code takes a single command per service. Before running it, make sure the target service is up and running: ~~~bash imq client generate User ./api/src/clients imq client generate Auth ./api/src/clients imq client generate Car ./api/src/clients imq client generate TimeTable ./api/src/clients ~~~ You may want to wrap these in an npm script so you can regenerate all clients with a single command, for example: ~~~bash npm run rebuild-clients ~~~ ### Querying the services Finally, when building the GraphQL schema, we're ready to query our services. For example, to fetch the list of car brands: ~~~typescript import { GraphQLResolveInfo, GraphQLList, GraphQLSchema, GraphQLObjectType, GraphQLString, } from 'graphql'; import { user, car, timeTable, auth } from '../clients/index.js'; interface Context { user: user.UserClient; car: car.CarClient; timeTable: timeTable.TimeTableClient; auth: auth.AuthClient; } export const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', fields: { brands: { description: 'Fetches the list of car brands', type: new GraphQLList(GraphQLString), async resolve( source: any, args: any, context: Context, info: GraphQLResolveInfo, ): Promise { try { return await context.car.brands(); } catch (err) { console.warn('Fetch brands error:', err); return []; } }, }, }, }), }); ~~~ Because the context is built during the API service's start-up and GraphQL exposes it to every resolver, we can call remote services and fetch whatever data we need, right where we need it. As the example shows, there's very little to do on the client side — just build and use. All the real implementation lives in one place: the service itself. This lets you write "normal" code, treating your services as ordinary objects with methods, and compose complex combinations of service calls imperatively inside your GraphQL resolvers. The full API service implementation is available [here](https://github.com/imqueue-sandbox/api). Next up: [Deployment](/tutorial/deployment). --- # Auth Service Source: https://imqueue.org/tutorial/auth-service/ Add an Auth service that talks to the User service to log users in and verify them — your first inter-service communication. Create a new service named `Auth`, the same way we created the User service in the [previous chapter](/tutorial/user-service#creating-the-service). Now define the Auth service's interface: ~~~typescript /** * Logs a user in * * @param {string} email - user e-mail address * @param {string} password - user password hash * @return {Promise} - the issued auth token, or null if authentication failed * @throws {Error} - "Password mismatch" or "Blocked" */ @profile() @expose() public async login(email: string, password: string): Promise { // TODO: implement... return null; } /** * Logs a user out * * @param {string} token - the user's JWT auth token * @param {string} [verifyEmail] - e-mail to verify against the token (if provided, must match) * @return {Promise} - operation result */ @profile() @expose() public async logout(token: string, verifyEmail?: string): Promise { // TODO: implement... return true; } /** * Verifies whether a token is valid and, if so, returns the associated user * * @param {string} token - the user's auth token * @return {Promise} - the associated user, or null */ @profile() @expose() public async verify(token: string): Promise { // TODO: implement... return null; } ~~~ So the service does three things: - sign users in - sign users out - verify whether a given auth token is valid To do this, the Auth service needs to talk to the User service: `login()` has to check the supplied credentials, and `verify()` returns the verified user object. > **A word of caution.** > Think twice before letting one service call another directly. In a system > where many services talk to each other point-to-point, the overall data flow > can quickly become hard to reason about. Look for a more predictable way to > organise communication first, and reach for direct calls only when they're > genuinely warranted. @imqueue doesn't dictate how you structure service > communication — that architectural decision is yours to make wisely. We use a > direct call here specifically so you can get hands-on with the feature and see > how it works. ### The service client To call a remote service in the RPC pattern, you need a client. Because every @imqueue service is self-describing, there are three ways to build one: - **Dynamically, at runtime.** The client is built while your program runs. The remote service must be up, because @imqueue asks it for its interface description and generates the client on the fly. This is convenient — you don't have to track interface changes yourself — but it has trade-offs: you lose version control over your clients, along with compile-time type-checking and IDE auto-completion during development. You also have to start services in the right order, since a client can't be built while its service is unavailable. - **Statically, ahead of time.** The @imqueue/cli tool generates client source code from a running service. You'll need to regenerate whenever the service's interface changes, but in return you get type-checking, IDE auto-completion, and the ability to detect and version interface changes. Keeping all generated clients in one place makes this the best choice for most applications — and you no longer need to worry about service start-up order, since clients can be instantiated without their services running. - **Manually.** For special cases you can write client code yourself, adding whatever custom behaviour you need. @imqueue provides the `IMQClient` base class to extend. This is advanced usage and outside the scope of this tutorial. For this chapter, let's integrate the Auth service with the User service using a **dynamically generated** client for User. That means we'll initialise a User client asynchronously during the Auth service's start-up — much as we established the database connection in the previous chapter. Override the Auth service's `start()` method: ~~~typescript class Auth extends IMQService { private user: any; // a dynamic client gives us no compile-time types! /** * Performs the required async preparations on service initialization */ public async start() { this.user = new (await IMQClient.create('User', { write: false })).UserClient(); await this.user.start(); return super.start(); } // ... rest of the service implementation } ~~~ With the client in place, calling the remote service is straightforward. Here's how `verify()` might look: ~~~typescript import * as jwt from 'jsonwebtoken'; // ... /** * Verifies whether a token is valid and, if so, returns the associated user * * @param {string} token - the user's auth token * @return {Promise} - the associated user, or null */ @profile() @expose() public async verify(token: string): Promise { let jwtData: any; try { jwtData = jwt.verify(token, 'some_secret_seed'); } catch (err) { return null; } return this.user.fetch(jwtData.email); } // ... ~~~ Try implementing the rest of the Auth service's methods as homework, or take a look at [its source code on GitHub](https://github.com/imqueue-sandbox/auth). Next up: [Domain Services](/tutorial/other-services). --- # Deployment Source: https://imqueue.org/tutorial/deployment/ Ship your services: per-service Docker images, environment-based configuration, and horizontal scaling for any load. This chapter covers the various aspects of deploying @imqueue-based services. There are many ways to deploy, depending on your needs. The most immediate is development deployment; another is shipping production packages. The deployment scheme also varies by environment — you might want to use every physical core of a single server, or spread many small containerised images across a network. @imqueue-based services are ready to meet any of these needs, but it's up to your developers and DevOps to decide how. The @imqueue/cli default template also provides some ready-to-use deployment functionality out of the box. **For services to scale in any direction, they must either be stateless or provide a mechanism to synchronise state across processes and network instances. Keep this in mind throughout implementation.** What does that mean in practice? Imagine a service that keeps some state in memory — say, the list of authenticated users: ~~~typescript import { IMQService, expose } from '@imqueue/rpc'; class MyService extends IMQService { private usersList: any[] = []; @expose() public addUser(data: any) { this.usersList.push(data); } } ~~~ Each time a remote client calls `addUser()`, the service mutates its in-memory state. With a single instance running, this works fine. But launch several copies and you're in trouble: after a few `addUser()` calls, each copy holds a different internal state — and the divergence is unpredictable. That's undesirable, so you must either implement a mechanism to share state between copies, or store and mutate the state in an external tool such as a database. Rolling your own state-sharing is usually non-trivial and prone to side-effects, so unless you're confident in how it works, we recommend designing your services to be stateless. Stateless services behave predictably across every deployment scenario. For example, this tutorial suggests implementing the [Car service](https://github.com/imqueue-sandbox/car) with an in-memory car database, which is stateful by design. But that database is largely static data: we refresh it roughly once every 24 hours, all running copies do so at about the same time, and we otherwise only read from it. There are minor side-effects, but they're insignificant for this system. It's neither good nor bad — you just need to understand what you're doing and what the consequences are. ### Scaling options Services are designed to run in multi-process environments. Since JavaScript on Node.js is single-threaded by nature, one process uses the power of only one core. On a multi-core machine you'll usually want to use all available cores, which you can do with a couple of configuration options. These are service (or client) options, and can be set in `config.ts`: ~~~typescript export const serviceOptions: Partial = { multiProcess: true, // default: false — turned off childrenPerCore: 2, // default: 1 }; ~~~ Set `multiProcess: true` to enable multi-process mode (or manage the value through environment variables). By default this forks one worker per available core. In real-world runs you may find that using all available capacity requires more than one process per core: increasing `childrenPerCore` adds more context switching per core but can still yield an overall performance gain. Treat it as a tuning knob — experiment to find the value that works best for your workload. If your deployment is based on small single-core containers, you probably don't need to touch the multi-process options at all. Either way, the right settings come from testing and experimentation. ### Building containers The @imqueue/cli default template can be configured to build Docker images for your services. You can build locally with the service's npm scripts, drive it through Travis CI-based continuous integration, or both. These Docker-related scripts are available in a service created by @imqueue/cli: ~~~bash npm run docker:build npm run docker:run npm run docker:stop npm run docker:ssh ~~~ Local builds require, of course, a Docker engine installed on your machine. Continuous-integration builds are enabled when a service is created with the `--dockerize` option and given a valid DockerHub namespace via `--docker-namespace`. Both can be pre-set as part of the imq command-line tool's global configuration. Travis CI is configured to build a Docker image on every commit, to verify the build stays green. An image is published to the configured DockerHub namespace when a version tag is pushed to the GitHub repository. Dev versions — those matching the `X.X.X-X` semver format — get the Docker `dev` tag. Release images are built from a dedicated `release` branch. Pre-built Docker images can be pulled and deployed across many cloud environments — AWS, Azure, Google Cloud Platform and others. From there it's a matter of configuring your cloud environment: enabling auto-scaling and anything else you need. One important note about running @imqueue clients in Docker containers: unless you name your clients explicitly, each client generates a unique name based on the operating system's UUID. Since Docker images share the same OS UUID out of the box, you should set a unique value on the first image build — usually in `/etc/machine-id` or `/var/lib/dbus/machine-id`. Consult the documentation for your container's base OS to find the correct location. ### Environment variables Environment variables are a powerful way to separate configuration across environments without maintaining multiple config codebases. On cloud platforms such as AWS you might use Parameter Store to supply configuration, while for local development you can use `.env` files. This requires some setup in the service's `config.ts`. Configure each option to read from an environment variable first (which you define yourself) and fall back to a default value. We covered this in [chapter 2](/tutorial/user-service#configuring-the-service). We strongly recommend following the same approach for any configuration in your real-world services, and documenting the expected environment variables in your README files — so that anyone deploying to a new environment can tune their setup easily. ### Running it locally We've covered the many options available when deploying @imqueue services. As you can see, it's a flexible solution, able to satisfy any load and suitable for horizontal scaling and cloud deployments. For this tutorial we'll focus on the default development environment, so you can run the example services from our [codebase](https://github.com/imqueue-sandbox) and experiment with them. First, clone all the repositories locally. Let's assume a dedicated directory, for example `~/imqueue-sandbox`: ~~~bash mkdir ~/imqueue-sandbox cd ~/imqueue-sandbox git clone git@github.com:imqueue-sandbox/api.git git clone git@github.com:imqueue-sandbox/auth.git git clone git@github.com:imqueue-sandbox/car.git git clone git@github.com:imqueue-sandbox/time-table.git git clone git@github.com:imqueue-sandbox/user.git git clone git@github.com:imqueue-sandbox/web-app.git ~~~ Next, make sure Redis, MongoDB and PostgreSQL are running on your development machine. You'll also need a PostgreSQL database named `tutmq`, owned by a user `tutmq` with the password `tutmq` — or change the time-table service's configuration to use different names. Then run each service in its own terminal window (or use a multiplexer such as `screen` or `tmux` if you prefer): ~~~bash cd ~/imqueue-sandbox/[service_dir] npm run dev ~~~ where `[service_dir]` is one of `user`, `auth`, `car`, `time-table` or `api`. Remember that the user and auth services communicate via a dynamically built runtime client, so start the **user** service before the **auth** service, or you'll hit errors. Once the API service is running, the GraphiQL web interface is available at [http://localhost:8888/](http://localhost:8888/). Finally, start the React-based web interface: ~~~bash cd ~/imqueue-sandbox/web-app npm start ~~~ You can now use the application at [http://localhost:3000/](http://localhost:3000/). Happy hacking! --- # Introduction Source: https://imqueue.org/tutorial/ A step-by-step guide to building back-end services for a car-washing web application with @imqueue — for those who prefer to learn by example. ## A car-wash booking app In this tutorial we build the back-end for a car-wash booking application, one service at a time, covering the fundamentals of the @imqueue framework along the way. Here is what the finished application looks like to its users:
Register screen Login screen Profile details screen Profile garage screen Time table screen
The complete source code for the tutorial application is available on [GitHub](https://github.com/imqueue-sandbox). ## Architecture Let's say we're building the web application on a React/Relay/GraphQL front-end, served by a GraphQL API endpoint that sits in front of a set of @imqueue-based back-end services. While a front-end team builds the user interface, we focus on the back-end. We split it into small, decoupled services that can be developed in parallel by small teams: - **User service** — manages user data. Stack: Node.js/TypeScript, @imqueue over Redis, MongoDB. - **Auth service** — handles authentication. Stack: Node.js/TypeScript, @imqueue over Redis, JSON Web Tokens. - **Car service** — serves car data. Stack: Node.js/TypeScript, @imqueue over Redis, a static data source cached in a custom in-memory store. - **Time-Table service** — manages reservations and reservation events. Stack: Node.js/TypeScript, @imqueue over Redis, PostgreSQL. - **API service** — a GraphQL endpoint that orchestrates access to the services above. Stack: Node.js/TypeScript, @imqueue over Redis, graphql, graphql-relay, express, express-graphql. The high-level architecture looks like this:
Application high-level architecture A React/Relay/GraphQL front-end talks over GraphQL/HTTP to an API Service, which orchestrates four @imqueue back-end services over RPC: User (MongoDB), Auth (Redis/JWT), Car (in-memory) and Time-Table (PostgreSQL). FRONT-END BACK-END GraphQL / HTTP @imqueue · RPC Web App React / Relay / GraphQL API Service GraphQL endpoint User Service Node.js · TS Auth Service Node.js · TS Car Service Node.js · TS Time-Table Service Node.js · TS MongoDB Redis · JWT In-memory PostgreSQL
## Setting up the toolchain The @imqueue command-line tool can wire its scaffolding into third-party services — GitHub, DockerHub and Travis CI. When you create a service with the tool, you can get a ready-made repository, continuous integration and one-command Docker image builds out of the box. So the first step is to install and configure `@imqueue/cli`. ### Prepare the development environment You'll need [Node.js](https://nodejs.org/) 22.12 or newer, ideally installed via [NVM](https://github.com/nvm-sh/nvm#installing-and-updating). You'll also need Redis, MongoDB and PostgreSQL — install them however you prefer, whether via Docker images ([Mongo](https://hub.docker.com/_/mongo/), [Redis](https://hub.docker.com/_/redis/), [PostgreSQL](https://hub.docker.com/_/postgres/)) or directly on your system. ### Install @imqueue/cli The integrations with GitHub, DockerHub and Travis CI are entirely optional. Without them, the tool simply creates local folders and files; you choose during installation whether to enable them. If you do want the integrations, prepare your GitHub and DockerHub namespaces (a personal account or an organisation) and create a GitHub personal access token granting @imqueue/cli permission to create and write to repositories in that namespace. Then install the tool: ~~~bash npm i -g @imqueue/cli ~~~ Installation ends with an interactive configuration wizard — follow the steps to finish configuring `@imqueue/cli`. For the full setup details — requirements, upgrading and shell completions — see the [Installation](/cli/installation/) & [Configuration](/cli/configuration/) chapters of the CLI User Guide. With that in place, we're ready to create our first service. --- # Domain Services Source: https://imqueue.org/tutorial/other-services/ Add the remaining domain services — Car and Time-Table — and choose how their typed clients are generated. By now it should be clear that building @imqueue services is a straightforward process. To make the application fully functional we need two more services: `Car` and `TimeTable`. Building them isn't much different from the `User` and `Auth` services we've already covered, so we suggest tackling them as homework. If you'd rather read the finished code, both are on GitHub — [Car](https://github.com/imqueue-sandbox/car) and [Time-Table](https://github.com/imqueue-sandbox/time-table). Here are the requirements. ### Car service requirements - Source its car data from any available dataset — for example, this [publicly available vehicle database](https://www.fueleconomy.gov/feg/ws/index.shtml). - Cache the data in an in-memory store, refreshed from the remote vehicle database every 24 hours. - Expose a list of car objects (`CarObject`) with the following fields: * unique car identifier (`id: string`) * manufacturer name (`make: string`) * model name (`model: string`) * years of manufacture (`years: number[]`) * type (`type: 'mini' | 'midsize' | 'large'`) Here is the interface the service is expected to implement: ~~~typescript /** * Returns the list of car manufacturers (brands) * * @return {string[]} - the list of known brands */ public brands(): string[]; /** * Returns the car object for a given identifier, or a list of car objects if an * array of identifiers is given. * * @param {string | string[]} id - car identifier(s) * @param {string[]} [selectedFields] - fields to return * @return {Partial | Partial[] | null} - the found object(s), or null */ public fetch( id: string | string[], selectedFields?: string[], ): Partial | Partial[] | null; /** * Returns the list of known cars for a given brand * * @param {string} brand - car manufacturer (brand) name * @param {string[]} [selectedFields] - fields to return * @param {string} [sort] - field to sort by, defaults to 'model' * @param {'asc' | 'desc'} [dir] - sort direction, defaults to 'asc' (ascending) * @return {Partial[]} - the list of matching cars */ public list( brand: string, selectedFields?: string[], sort: string = 'model', dir: 'asc' | 'desc' = 'asc', ): Partial[]; ~~~ For implementation details, refer to the [source code](https://github.com/imqueue-sandbox/car). **Something to think about:** synchronising the in-memory data across multiple running instances of the service. ### Time-Table service requirements This is the central service. Use a relational database as its data store — for example, PostgreSQL (or another of your choice) with the Sequelize ORM on top. Here is the interface expected for this service: ~~~typescript /** * Returns reservations starting from a given time (or from the current time if * omitted) * * @param {string} [date] - date to select reservations for; defaults to the current date * @param {string[]} [fields] - fields to select for each reservation * @return {Promise} - the matching reservations */ public async list(date?: string, fields?: string[]): Promise; /** * Fetches a single reservation by its identifier * * @param {string} id - identifier of the reservation to fetch * @param {string[]} [fields] - fields to select for the reservation * @return {Promise | null>} - the reservation, or null if not found */ public async fetch(id: string, fields?: string[]): Promise | null>; /** * Makes a reservation, or throws if it cannot be made * * @param {Reservation} reservation - the reservation data * @param {string[]} [fields] - fields to select for the updated reservations list * @return {Promise} - the updated reservations list */ public async reserve(reservation: Reservation, fields?: string[]): Promise; /** * Cancels a reservation * * @param {string} id - reservation identifier * @param {string[]} [fields] - fields to select for the updated reservations list * @return {Promise} - the updated reservations list */ public async cancel(id: string, fields?: string[]): Promise; /** * Returns the time-table configuration settings * * @return {Promise} - the time-table options */ public async config(): Promise; ~~~ It also exposes these complex types: `Reservation`: - `id` — reservation record identifier - `carId` — user's car identifier - `userId` — user identifier - `type` — the washing type for this reservation, one of `'fast' | 'std' | 'full'` - `duration` — a range of start and end times `TimeTableOptions`: - `start` — the station's opening time, in `HH:MM` format - `end` — the station's closing time, in `HH:MM` format - `baseTime` — the duration options per washing type, as a list of: ~~~typescript { key: 'fast' | 'std' | 'full', // or whatever else... title: string, // human-readable title for the washing type duration: number, // in minutes } ~~~ **Something to think about:** (a) exposing a Sequelize model as an @imqueue complex type; (b) storing the options as configurable database records. Either way, the [complete source code is on GitHub](https://github.com/imqueue-sandbox/time-table). Next up: [API Service — integration](/tutorial/api-service). --- # User Service — your first service Source: https://imqueue.org/tutorial/user-service/ Create your first @imqueue service — the User service — and expose typed methods that other services can call. We're ready to create our first service. Start by making a project directory to hold all of the tutorial's repositories: ~~~bash mkdir ~/my-tutorial-app cd ~/my-tutorial-app ~~~ ### Creating the service Scaffold the User service with a single command: ~~~bash imq service create user ./user ~~~ If all goes well, you'll have a `./user` directory containing every file the service needs, with all dependencies already installed. ### Configuring the service You can check that it works by running `npm run dev`. This requires Redis running on `localhost` and the default port. If your Redis runs on a different host or port, adjust the configuration first. The configuration file lives at `./user/config.ts`. Redis access can be configured in two ways: a single Redis instance, or a cluster of instances. Which one you choose depends on your scaling needs — configure a cluster for services expected to handle heavy load, and a single instance otherwise. A good practice at this point is to read the configuration from the environment and pass it into the service config, so it can be changed at deployment time without touching code. Here is one way to adapt `config.ts`: ~~~typescript import { IMQServiceOptions, DEFAULT_IMQ_SERVICE_OPTIONS as opts, } from '@imqueue/rpc'; import { config as initEnvironment } from 'dotenv'; initEnvironment(); export const serviceOptions: Partial = { cluster: (process.env['IMQ_REDIS'] || `${opts.host}:${opts.port}`) .split(',').map((instance: string) => { const [host, port] = instance.split(':'); return { host, port: Number(port) }; }), }; ~~~ With that in place, you can put a `.env` file in the service's root directory to set the Redis configuration for your local environment. For example, if Redis is running at `some-redis-special.host:63790`: `.env`: ~~~bash IMQ_REDIS="some-redis-special.host:63790" ~~~ If your Redis runs at `localhost:6379` (the standard default), you can skip this step for now. ### Local environment During development it's convenient to run a service directly in your local environment. Keep in mind, though, that in production the same service will usually run with a different configuration. Reading configuration from environment variables solves this cleanly. Because you may have several projects on your development machine, setting environment variables globally can get awkward — `.env` files avoid that. @imqueue services support `.env` files out of the box: whenever a service needs local configuration, create a `.env` file in its root directory and list the variables you want to read from the environment. These files are never committed, so they stay out of production runs. ### Verifying the service Let's confirm the service is operational: ~~~bash npm run dev ~~~ If everything is fine, you should see output like this: ~~~ User: starting single-worker, pid 27034 Starting clustered redis message queue... User: reader channel connected, host localhost:6379, pid 27034 User: writer channel connected, host localhost:6379, pid 27034 ~~~ That means the service is up and ready. The @imqueue boilerplate always scaffolds a service with one remotely callable method — `hello()` — because a service needs at least one exposed method to start without errors. Once you've implemented your own first method, it's safe to remove the generated `hello()`. For now, we'll use `hello()` to verify the service. Create a `debug.ts` file in the service's root directory with the following content: ~~~typescript import { IMQClient, ILogger } from '@imqueue/rpc'; import { User } from './src/index.js'; import { serviceOptions } from './config.js'; const logger: ILogger = serviceOptions.logger || console; new User(serviceOptions).start().then((service: any) => { IMQClient.create('User', { write: false }).then(async (ns: any) => { let client: any; try { client = new ns.UserClient(serviceOptions); await client.start(); console.log(await client.hello()); } catch (err) { logger.error(err); } await client.destroy(); await service.destroy(); }); }); ~~~ This starts the service and a client, then makes a remote call to the service's `hello()` method. The output should look like: ~~~ User: starting single-worker, pid 32372 User: reader channel connected, host localhost:6379, pid 32372 User: writer channel connected, host localhost:6379, pid 32372 UserClient-6a4e92f40a6e4d7e8a650c6c44d79ab2-2:client: reader channel connected, host localhost:6379, pid 32372 UserClient-6a4e92f40a6e4d7e8a650c6c44d79ab2-2:client: writer channel connected, host localhost:6379, pid 32372 Hello! ~~~ That confirms the service works as expected. In development mode, @imqueue watches for file changes with nodemon, so you can simply run the service and start coding. > **NOTE:** any file or folder whose name matches the `debug*` pattern is > ignored by git, so you can use `debug.ts` freely — or adjust your ignore files > if you prefer different behaviour. ### Adding dependencies Adding dependencies works exactly as it does in any Node.js project — just use `npm install`. We chose MongoDB as the data store for this service, so we'll use the `mongoose` package to work with it: ~~~bash npm i --save mongoose ~~~ ### Implementing the service #### Prepare the data store First, define a Mongoose schema for the service. @imqueue imposes no constraints here — do it the usual way. Create `./user/src/schema.ts` (or any path you prefer) with the following content: ~~~typescript import * as mongoose from 'mongoose'; export const schema = new mongoose.Schema({ email: { type: mongoose.SchemaTypes.String, unique: true, required: true, }, password: { type: mongoose.SchemaTypes.String, required: true, }, isActive: { type: mongoose.SchemaTypes.Boolean, default: true, }, isAdmin: { type: mongoose.SchemaTypes.Boolean, default: false, }, firstName: { type: mongoose.SchemaTypes.String, required: true, }, lastName: { type: mongoose.SchemaTypes.String, required: true, }, cars: { type: [{ carId: { type: mongoose.SchemaTypes.String, required: true, }, regNumber: { type: mongoose.SchemaTypes.String, required: true, }, }], required: false, default: [], }, }); ~~~ By design, we store the following for each user: - identifier - first name - last name - email - password - `isActive` flag — lets us block a user for any reason - `isAdmin` flag — marks users with the admin role - the user's cars ("garage") — a dedicated Car service will manage the cars database, but here we store the fields needed to link a user to a car and to hold user-specific data such as the car's registration number Next we implement the operations on that data that remote clients can call — the service's public methods. Open `./user/src/User.ts`, which contains our service class. #### Prepare the database connection Import the Mongoose schema at the top of the file: ~~~typescript import { schema } from './schema.js'; ~~~ We then need to open the MongoDB connection when the service starts and register the schema so we can use it. Declare these properties on the service class: ~~~typescript private db: mongoose.Connection; private UserModel: mongoose.Model; ~~~ Opening a database connection is asynchronous, so the natural place to do it is by overriding `IMQService.start()`. First, add a private `initDb()` method: ~~~typescript /** * Initializes the MongoDB connection and the user schema * * @return {Promise} */ @profile() private async initDb(): Promise { await mongoose.connect('mongodb://localhost/user'); this.db = mongoose.connection; this.UserModel = this.db.model('User', schema); } ~~~ Now override `start()` to call it: ~~~typescript /** * Overrides start() to establish the MongoDB connection first */ @profile() public async start(): Promise { this.logger.log('Initializing MongoDB connection...'); await this.initDb(); return super.start(); } ~~~ #### A note on logging Notice we used `this.logger` above. By default it's the standard `console`, but you can swap in your own logger through `config.ts`, and every debug, log and error output across the service will go through it. That's useful when you want to route logs to a specific destination — for example, using `winston` with transports that send output to local storage and/or a remote service such as Sentry. Using `this.logger` (rather than `console` directly) keeps all logging manageable and monitorable from one place. With that, we're ready to implement the service's remote interface. #### Exposing the interface Let's start by defining the external interface — the set of methods the service will expose: ~~~typescript /** * Creates or updates an existing user with the given data * * @param {UserObject} data - user data fields * @param {string[]} [fields] - fields to return on success * @return {Promise} - the saved user object */ @profile() @expose() public async update(data: UserObject, fields?: string[]): Promise { // TODO: implement... return null; } /** * Looks up and returns a user by e-mail or by object identifier * * @param {string} criteria - user identifier or e-mail * @param {string[]} [fields] - fields to select and return * @return {Promise} - the matching user, or null */ @profile() @expose() public async fetch(criteria: string, fields?: string[]): Promise { // TODO: implement... return null; } /** * Returns a collection of users matching the given criteria. Records can be * paginated using the skip and limit arguments. * * @param {UserFilters} [filters] - criteria to filter the user list * @param {string[]} [fields] - fields to select for each returned user * @param {number} [skip] - number of records to skip before fetching * @param {number} [limit] - maximum number of records to return * @return {Promise} - the matching users */ @profile() @expose() public async find(filters?: UserFilters, fields?: string[], skip?: number, limit?: number): Promise { // TODO: implement... return []; } /** * Returns the number of users matching the given criteria * * @param {UserFilters} [filters] - criteria to filter by * @return {Promise} - the number of matching users */ @profile() @expose() public async count(filters?: UserFilters): Promise { // TODO: implement... return 0; } /** * Attaches a new car to a user * * @param {string} userId - identifier of the user to attach the car to * @param {string} carId - identifier of the selected car * @param {string} regNumber - car registration number * @param {string[]} [selectedFields] - fields to return for the modified user * @return {Promise} - the modified user */ @profile() @expose() public async addCar(userId: string, carId: string, regNumber: string, selectedFields?: string[]): Promise { // TODO: implement... return null; } /** * Removes a car from a user * * @param {string} carId - identifier of the user's car * @param {string[]} [selectedFields] - fields to return for the modified user * @return {Promise} - the modified user */ @profile() @expose() public async removeCar(carId: string, selectedFields?: string[]): Promise { // TODO: implement... return null; } /** * Returns a given user's car, fetched by identifier * * @param {string} userId - user identifier * @param {string} carId - car identifier * @return {Promise} */ @profile() @expose() public async getCar(userId: string, carId: string): Promise { // TODO: implement... return null; } /** * Returns the number of cars registered for the user with the given id or email * * @param {string} idOrEmail - user identifier or e-mail * @return {Promise} */ @profile() @expose() public async carsCount(idOrEmail: string): Promise { // TODO: implement... return 0; } ~~~ There are a few mandatory rules for defining externally callable methods: 1. To make a method callable remotely, wrap it with the `@expose()` decorator. 2. Every service must have at least one externally callable method. 3. Write a doc-block for each exposed method. Two rules apply: - Describe all argument and return-value types in TypeScript notation. - Mark optional arguments as optional by wrapping the name in `[]`, like this: `@param {string} [name]`. Following these rules guarantees a correct service description and, in turn, correctly generated, working clients. With the interface above in place, the service won't compile yet — we've referenced types TypeScript doesn't know: `UserObject`, `UserCarObject` and `UserFilters`. Let's define them. #### Defining exposable complex types You can describe complex data structures inline in doc-blocks using TypeScript notation, but that quickly leads to duplication. A cleaner approach is to define reusable complex types. Complex types that can be exposed remotely **must be defined as classes**. Each such class must be annotated with the `@classType()` class decorator, and each exposed field with the `@property()` decorator. Under @imqueue v3's standard (TC39) decorators, `@property()` only collects field metadata — the class-level `@classType()` then registers that metadata as a named type, so both the service and the generated client recognise it. Create the first type: ~~~bash mkdir ./user/src/types touch ./user/src/types/UserObject.ts ~~~ Put the following inside: ~~~typescript import { classType, property } from '@imqueue/rpc'; import { UserCarObject } from './UserCarObject.js'; /** * Serializable user type */ @classType() export class UserObject { @property('string', true) _id?: string; @property('string') email: string; @property('string') password: string; @property('boolean') isActive: boolean; @property('boolean') isAdmin: boolean; @property('string') firstName: string; @property('string') lastName: string; @property('UserCarObject[]') cars: UserCarObject[]; } ~~~ A few things to note about `@property()`: - Its first argument is the property type in TypeScript notation. - Pass `true` as the second argument to mark the property as optional. - A property may reference another complex type — here, `cars` references an array of `UserCarObject`. - Types defined this way appear on the client side as TypeScript interfaces, giving you full type-checking across the client and service. - Leaving a property undecorated hides it from the remote interface, which is a handy way to keep service-internal fields private. Now define the remaining types. ~~~bash touch ./user/src/types/UserCarObject.ts ~~~ ~~~typescript import { classType, property } from '@imqueue/rpc'; @classType() export class UserCarObject { @property('string') _id: string; @property('string') carId: string; @property('string') regNumber: string; } ~~~ And `UserFilters`: ~~~bash touch ./user/src/types/UserFilters.ts ~~~ ~~~typescript import { classType, property } from '@imqueue/rpc'; @classType() export class UserFilters { @property('string', true) email?: string; @property('boolean', true) isActive?: boolean; @property('boolean', true) isAdmin?: boolean; @property('string', true) firstName?: string; @property('string', true) lastName?: string; } ~~~ Finally, import the types into the service class module: ~~~typescript import { UserCarObject } from './types/UserCarObject.js'; import { UserFilters } from './types/UserFilters.js'; import { UserObject } from './types/UserObject.js'; ~~~ The service should now compile without errors. All that's left is to implement the logic of the service methods — we'll leave that as homework, but you can always refer to the [source code](https://github.com/imqueue-sandbox/user) on GitHub. Next up: [Auth Service — inter-service communication](/tutorial/auth-service). --- # Using @imqueue with AI assistants Source: https://imqueue.org/using-ai-assistants/ Build @imqueue services faster with Claude, ChatGPT, Cursor, GitHub Copilot and other coding agents. Paste the context block below into your assistant so it generates correct, idiomatic @imqueue code. ## Why this page exists @imqueue is a small, strongly-typed framework, and coding assistants work best when they have accurate context about its packages, decorators and conventions. This page gives you a **paste-ready context block** and points AI agents at the **machine-readable versions** of these docs. ## Paste this into your AI assistant Copy the block below into Claude, ChatGPT, Cursor, Windsurf, GitHub Copilot Chat or any other assistant before asking it to write @imqueue code. It captures the package names, the core APIs and the constraints that most often trip up generated code. ~~~text You are helping me build back-end services with @imqueue, an RPC framework for Node.js and TypeScript that communicates over a Redis-backed message queue. Packages: - @imqueue/rpc — typed RPC: services, clients, decorators. - @imqueue/core — the underlying message queue over Redis. - @imqueue/cli — scaffolding (`imq service create`) and client generation (`imq client generate `). How a service is written: - A service is a class that extends `IMQService` from '@imqueue/rpc'. - Only methods decorated with `@expose()` are callable remotely. - Exposed-method arguments and return values MUST be JSON-serializable. - Do NOT use the spread/rest operator for exposed-method arguments — the generated client won't compile. Pass an array instead: // wrong: public doThing(...args: any[]) // right: public doThing(args: any[]) - Write doc-blocks with accurate @param/@return types — they are part of the service's self-description and drive the generated client's types. Complex types: - Declare data objects as classes decorated with `@classType()`, and each field with `@property('type', optional?)`, e.g. `@property('string')` or `@property('AddressObject[]', true)` for an optional array. Clients: - Clients are GENERATED from a running service (`imq client generate`), not hand-written. Usage: const client = new UserClient(); await client.start(); const user = await client.update({ ... }); - There is no service discovery or load balancer to configure; the queue handles routing. Runtime: - Requires Node.js 22.12+ and Redis 3.2+ (default connection localhost:6379). - Configure host/port/cluster/safeDelivery via IMQServiceOptions or environment. License: the open-source packages are GPL-3.0. Commercial licensing for closed-source products is available at https://imqueue.com. Prefer generating a service class + its typed methods, and let the CLI generate the client. Follow the patterns above exactly. ~~~ ## A minimal service the way @imqueue expects it ~~~typescript 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> { // ...look the user up and return a JSON-serializable value return { id, name: 'Jane Doe' }; } } ~~~ Then generate and use a fully typed client: ~~~bash imq client generate UserService ~~~ ~~~typescript const client = new UserServiceClient(); await client.start(); const user = await client.get('42'); // fully typed, no hand-written client ~~~ ## Endpoints for AI agents If you are building an agent, or your assistant can fetch URLs, these endpoints serve the documentation in machine-friendly form: - **[/llms.txt](/llms.txt)** — a curated, machine-readable index of the docs (following the [llmstxt.org](https://llmstxt.org/) convention). - **[/llms-full.txt](/llms-full.txt)** — the full documentation concatenated into a single markdown file for one-shot ingestion. - **Markdown mirror of any docs page** — append `index.md` to a page URL, e.g. [`/get-started/index.md`](/get-started/index.md) or [`/tutorial/user-service/index.md`](/tutorial/user-service/index.md). - **[/api/](/api/)** — the full generated API reference for `@imqueue/core` and `@imqueue/rpc`. ## Next steps - Work through the [Getting Started](/get-started/) guide. - Follow the [Tutorial](/tutorial/) for a complete example application. - Explore the [CLI User Guide](/cli/) for scaffolding and fleet management.