Skip to Content

attachCatRPC (host)

New here? Start with Quick start for a minimal copy-paste server, then return here for every option.

For Gloocan QA web and the cat-demo stack, the supported integration is attachCatRPC from @gloocan/cat-inspector/socket-io: your HTTP server creates a Socket.IO Server, then you attach catalog refresh, RPC invoke, uploads, and optional host-media wiring in one call.

The SDK still runs an internal catalog build (filesystem scan, AST merge, registry) when serving the catalog; you configure that through scanRoots, the nested bootstrap object.

import 'reflect-metadata' import { Server as SocketIOServer } from 'socket.io' import { attachCatRPC } from '@gloocan/cat-inspector/socket-io' const io = new SocketIOServer(/* … */) const cat = attachCatRPC(io, { scanRoots: [new URL('.', import.meta.url).pathname], // …see tables below }) // on shutdown: cat.detach()

attachCatRPC wires catalog:bootstrap, catalog:refresh, rpc:call, upload events, optional host Minio over Socket.IO (v10+ when URL mode + host media + hostMinio are configured), and optional inspector broadcast fan-out. It returns { detach() } to unregister listeners and related policy hooks.

secretApiKey (required for Gloocan)

The TypeScript field is optional so local-only demos can start without a tenant key. For any deployment where QA traffic goes through Gloocan (tenant web verifies the host), treat secretApiKey as mandatory:

  • Pass the host API key your tenant was issued (same concept as GLOOCAN_HOST_API_KEY in examples)—always from environment or a secret manager, never hardcoded in source.
  • When set (non-empty after trim), the SDK merges it into each emitted catalog:bootstrap payload so the QA client can confirm this backend matches the expected tenant.
  • If you omit it, the catalog payload has no secretApiKey; Gloocan-side verification will fail or be unsafe depending on product configuration.

Obtaining the key and wiring attachCatRPC

In your Gloocan Cat-Inspector tenant dashboard, open API Keys in the sidebar, then use + Create API key. Copy the full secret when it is shown—it is only displayed once and cannot be retrieved later from the list (you will only see a prefix on the keys table).

Cat-Inspector dashboard: sidebar with API Keys selected, API Keys page title, note that secrets are shown only once, + Create API key button, and tenant keys table with Name, Prefix, Status, Created, Last used, and Revoke actions

Put that value in your backend environment (for example GLOOCAN_HOST_API_KEY), then pass it into attachCatRPC as the top-level secretApiKey option—never commit the literal key to source control:

attachCatRPC(io, { // …scanRoots, bootstrap, etc. secretApiKey: process.env.GLOOCAN_HOST_API_KEY, })

If the variable may be unset in local tooling, you can mirror the cat-demo pattern and only set the field when the env var is non-empty: ...(process.env.GLOOCAN_HOST_API_KEY ? { secretApiKey: process.env.GLOOCAN_HOST_API_KEY } : {}).

Example

scanRoots, serverId, isDevelopment, secretApiKey, top-level upload, nested bootstrap (AST expansion, file wire, URL fetch policy, host Minio—credentials stay server-side and are not serialized on the wire).

import { attachCatRPC } from '@gloocan/cat-inspector/socket-io' attachCatRPC(io, { scanRoots: [new URL('.', import.meta.url).pathname], serverId: 'cat-demo-backend', isDevelopment: process.env.NODE_ENV !== 'production', secretApiKey: process.env.GLOOCAN_HOST_API_KEY!, // required when using Gloocan QA // When isDevelopment is false, also set e.g. rpcAuth: { token: process.env.QA_AUTH_TOKEN! }, upload: { enabled: true, maxSizeBytes: 50 * 1024 * 1024, idleTimeoutMs: 60_000, }, bootstrap: { expandParamTypes: true, expandParamTypesOptions: { maxDepth: 6, maxProps: 80, maxUnion: 10, maxLen: 10_000, }, qaFileWire: { mode: 'url' }, qaMediaUpload: { target: 'host' }, fileUrl: { allowedHosts: ['localhost'], maxDownloadBytes: Number(process.env.QA_FILE_URL_MAX_BYTES ?? String(50 * 1024 * 1024)), timeoutMs: Number(process.env.QA_FILE_URL_TIMEOUT_MS ?? '30000'), maxRedirects: Number(process.env.QA_FILE_URL_MAX_REDIRECTS ?? '5'), allowHttp: true, // dev only; omit or false in production }, hostMinio: { endpoint: 'localhost:9004', accessKeyId: process.env.MINIO_ACCESS_KEY!, secretAccessKey: process.env.MINIO_SECRET_KEY!, bucket: 'qa-app-media', forcePathStyle: true, }, }, })

With qaFileWire.mode: 'url', qaMediaUpload.target: 'host', and bootstrap.hostMinio set, the SDK may advertise qaHostMediaUploadViaSocket on the catalog payload (v10+): bytes can flow over Socket.IO for host-held media without a separate HTTP upload URL.

Top-level attachCatRPC options

OptionTypeEffect
scanRootsstring[]Required. Directories of .ts source scanned by the TypeScript compiler to enrich already registered catalog entries (params, returns, labels). Does not run cat() or import() your app—handlers must be registered first (see importEntryUrls or normal import in server.ts).
serverIdstring?Shown on the initial status event (server field). Default cat-inspector.
isDevelopmentboolean?Default: NODE_ENV !== 'production'. Playground HTTP-inspector toggles and broadcast bridging from HTTP-sourced runs; when false, set rpcAuth so invokes require socket.handshake.auth.token.
upload{ enabled?, maxSizeBytes?, idleTimeoutMs? }?In-memory store for Files and size/idle limits; defaults align with the internal upload store (e.g. 50 MiB) when enabled.
invokeRateLimit{ maxInvokesPerWindow, windowMs }?Token bucket per Socket.IO socket.id.
invokeTimeoutMsnumber?Passed through for executeRPC (same meaning as catalog-build timeout fields).
rpcSerializationRpcSerializationOptions?Set enabled: true for post-invoke JSON normalization (maxUtf8Bytes). Set validateParamsJsonSchema / validateReturnJsonSchema to validate invoke args and result against manually attached paramsJsonSchema / returnJsonSchema on each catalog entry (cat(), @Cat, registerReturnJsonSchema, etc.). Param checks run before invoke and do not require enabled; return checks run after serialization and need enabled. May also appear under bootstrap.
storageBootstrapStorageOptions?Outbound / handler-owned blobs (exports, screenshots, big RPC results): supply adapter with createPresignedPut and call it inside your handlers; return small JSON (objectKey, URLs)—not raw bytes. artifactThresholdBytes > 0 requires adapter or attach throws QA_STORAGE_NOT_CONFIGURED. Not published on catalog; not for QA uploading test inputs (see qaMediaUploadHostUploadUrl). SDK validates only today; auto spill above threshold is planned. May also live under bootstrap.storage.
qaFileWire{ mode? }?How file parameters travel in RPC args (echoed on catalog + used at materialize). ref (default): QA uploads via Socket.IO upload → invoke sends __qaFileRef. url: QA uploads elsewhere first → invoke sends __qaFileUrl; host fetches bytes with fileUrl (allowlist required). Use url when files land on host/admin storage before the test runs. Merged with bootstrap.qaFileWire.
qaMediaUpload{ target }?Merged with bootstrap.qaMediaUpload (admin | host).
fileUrlFetchFileUrlOptions?SSRF-safe URL fetch for __qaFileUrl; when mode is url, allowlist must be valid (see validation below).
qaMediaUploadHostUploadUrlstring?Inbound test files (QA → host): HTTPS route advertised on catalog:bootstrap when qaMediaUpload.target is host and qaFileWire.mode is url. Gloocan QA POSTs the file here first; invoke then passes a __qaFileUrl the SDK resolves via fileUrl. You implement the route (often backed by hostMinio). Not storage (that is for handler outbound uploads). If hostMinio + URL/host target are set without this URL, catalog may offer qaHostMediaUploadViaSocket (v10) instead.
forwardBroadcastboolean?Default true. Live QA UI updates: forwards inspector events (HTTP playground runs, capture steps, job progress) to the Socket.IO room for the connected inspector socket so the tenant web UI updates without polling rpc:call. Set false only if you register your own fan-out via setBroadcastSink.
emitUnscopedBroadcastsboolean?If true when there is no inspector store, falls back to io.emit—broad fan-out; use sparingly.
createExpressMocksfunction?Express routes over RPC: when QA calls an Express-registered fnKey (api / api_candidate), the SDK builds fake req / res / next from the RPC payload and runs your middleware/handler. Replace the default createExpressPlaygroundMocks factory to add custom Express behavior—e.g. extra headers, auth context, multer-like file / files, or a specialized res.json capture—without changing production Express wiring.
hooksAttachCatRPCHooks?Wrap every rpc:call (plain handlers and Express playground). onBeforeRpc: run your logic first (auth, logging, tweak the incoming message); return an RpcResponse to skip the built-in invoke. onAfterRpc: observe or enrich the response after invoke. onConnection: socket setup. onCatalogError: catalog refresh failures. Use with createExpressMocks when Express-specific setup belongs in mock req/res, and onBeforeRpc when the same checks should apply to all RPC types.

Nested bootstrap object (catalog build)

Nested options.bootstrap is a partial of the catalog-related fields from BootstrapOptions (there is no wsPort here—your Socket.IO server owns the transport).

Registration vs types: catalog build first ensures handlers exist in the registry (runtime import), then scanRoots reads .ts on disk for types. You only need importEntryUrls when QA registration modules are not already loaded from server.ts (routers, import './qa/load.js', etc.).

FieldNotes
importEntryUrlsOptional string[] of absolute paths or file: URLs. SDK import()s each entry before the AST scan so side-effect registration runs (cat(), @Cat, catModule()). Typical use: one barrel file (e.g. qa-wrappers/load.js) that imports the rest of your testable surface. Same job as import './that-file.js' at the top of server.ts—pick one style, not both unless intentional. Does not supply types; keep scanRoots pointed at the src/ tree that contains the matching .ts files.
getAllTsFilesOptionsOptional AST file walk tuning under scanRoots. skipDirNames: extra directory names to skip when collecting .ts files (defaults already skip node_modules, dist, .git, coverage, .vite). Use to exclude generated or vendor trees inside your app root. Only affects which sources are parsed—not registration.
compilerOptionsOptional TypeScript CompilerOptions merged on top of tsconfig.json from the first scanRoots entry (or SDK defaults if none). Controls how scanned files are type-checked (decorators, strict, path aliases, etc.) and therefore the shape strings in the catalog. Rarely needed if your project tsconfig is correct.
logLevel'debug' | 'info' | 'warn' | 'error' for catalog-build log lines (file count, dynamic import paths). With attachCatRPC, internal bootstrap is called at error to keep server logs quiet unless you pass a custom logger.
redactBodiesDefault true. Clears RegistryEntry.body (handler source snippets) from the registry snapshot sent on catalog:bootstrap. Set false only in trusted local tooling when you intentionally want source text on the wire.
expandParamTypes, expandParamTypesOptionsRicher params[].type in catalog (maxDepth, maxProps, maxUnion, maxLen).
hostMinioHostMinioOptions (endpoint, accessKeyId, secretAccessKey, bucket, …): server-only S3/MinIO credentials for tenant test uploads when qaFileWire.mode is url and qaMediaUpload.target is host. Never sent on catalog:bootstrap. Either you implement qaMediaUploadHostUploadUrl (POST → your bucket) using these creds, or omit that URL and the SDK advertises qaHostMediaUploadViaSocket so QA streams bytes over Socket.IO qa:hostMedia:* and the host writes to MinIO. Not storage (that is for handler-owned outbound blobs).
uploadOptional in-object in-memory ref uploads; many apps only set top-level upload on attachCatRPC.

Validation helpers

These apply when you configure storage or URL file wire (including under attachCatRPC):

  • validateBootstrapStorage(storage) — throws QA_STORAGE_NOT_CONFIGURED if artifactThresholdBytes > 0 without an adapter.
  • validateBootstrapFileWire({ qaFileWire, fileUrl }) — when qaFileWire.mode === 'url', fileUrl.allowedHosts must be non-empty.

See also