feat: add introspect module with commands endpoint#1525
feat: add introspect module with commands endpoint#1525
Conversation
Add a new `introspect` command module that returns the JSON Schema describing all incoming message types. The `introspect.commands` command works before `initialize`, enabling API discovery pre-negotiation. - Create `src/lib/introspect/` module (command, incoming_message, outgoing_message, message_handler) following existing patterns - Register in Instance enum and wire into server.ts - Add `ts-json-schema-generator` for build-time schema generation - Rename interfaces for `$ref` reuse (node, driver, controller, multicast_group incoming messages) - Add introspection section to README with tool recommendations Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds an introspect module to enable API discovery by exposing a build-time generated JSON Schema for all incoming message types, accessible before initialize.
Changes:
- Introduces
src/lib/introspect/*(command, message types, handler) and registers it as a new instance in the server routing. - Extends incoming/outgoing type unions and exports to include introspection and renames several incoming-message interfaces to improve
$refreuse in the generated schema. - Adds
ts-json-schema-generatorwithgenerate:schema/prebuildhooks, updates docs, and git-ignores the generated schema output directory.
Reviewed changes
Copilot reviewed 15 out of 17 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/lib/server.ts | Registers the new introspect instance handler in the per-client instance handler map. |
| src/lib/outgoing_message.ts | Adds IntrospectResultTypes into the global result type union. |
| src/lib/node/incoming_message.ts | Renames several node incoming-message interfaces for improved schema $ref reuse and updates the union. |
| src/lib/multicast_group/incoming_message.ts | Renames the multicast group getDefinedValueIDs incoming-message interface and updates the union. |
| src/lib/introspect/outgoing_message.ts | Defines result types for introspect.commands. |
| src/lib/introspect/message_handler.ts | Implements the handler that returns the generated incoming-message JSON schema. |
| src/lib/introspect/incoming_message.ts | Defines incoming message type(s) for the new introspect module. |
| src/lib/introspect/command.ts | Defines the new IntrospectCommand enum. |
| src/lib/instance.ts | Adds introspect to the Instance enum for routing. |
| src/lib/incoming_message.ts | Adds IncomingMessageIntrospect to the top-level incoming message union. |
| src/lib/driver/incoming_message.ts | Renames driver incoming-message interfaces for schema $ref reuse and updates the union. |
| src/lib/controller/incoming_message.ts | Renames the controller firmware update “in progress” incoming-message interface and updates the union. |
| src/lib/command.ts | Exports IntrospectCommand (and ZnifferCommand) from the central command export module. |
| package.json | Adds schema generation tooling and hooks (generate:schema, prebuild) and runs generation in npm test. |
| package-lock.json | Locks the new ts-json-schema-generator dependency tree. |
| README.md | Documents the introspection capability and usage guidance. |
| .gitignore | Ignores /src/lib/generated/ where the schema is written. |
Comments suppressed due to low confidence (1)
package.json:44
IntrospectMessageHandlerimports../generated/incoming_message_schema.json, but the build pipeline (tsc+esm2cjs) does not copy JSON assets intodist-esm/dist-cjs. Since the published package only includesdist-*(seefiles), this will likely fail at runtime with a missing module. Consider adding a postbuild step to copysrc/lib/generated/incoming_message_schema.jsonintodist-esm/lib/generated/(and the CJS output), or generate the schema directly into the dist folders as part of the build.
"test": "npm run generate:schema && prettier --check src && tsc --noEmit && npm run lint && tsx src/test/integration.ts",
"generate:schema": "ts-json-schema-generator --path src/lib/incoming_message.ts --type IncomingMessage --tsconfig tsconfig.json --out src/lib/generated/incoming_message_schema.json",
"prebuild": "npm run generate:schema",
"build": "tsc -p .",
"postbuild": "esm2cjs --in dist-esm --out dist-cjs -l error -t node20",
"prepare": "npm run build",
"prepublishOnly": "rm -rf dist-* && npm run build"
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -0,0 +1,21 @@ | |||
| import { UnknownCommandError } from "../error.js"; | |||
| import incomingMessageSchema from "../generated/incoming_message_schema.json" with { type: "json" }; | |||
There was a problem hiding this comment.
The JSON import uses the newer import-attributes syntax (with { type: "json" }). Node 20 commonly supports JSON imports via assert { type: "json" }, but may not support the with form depending on the exact runtime version, which could make the server fail to start. Consider switching to the widely-supported assert { type: "json" } syntax for Node 20, or loading the JSON via fs/createRequire to avoid reliance on import attributes syntax evolution.
| import incomingMessageSchema from "../generated/incoming_message_schema.json" with { type: "json" }; | |
| import incomingMessageSchema from "../generated/incoming_message_schema.json" assert { type: "json" }; |
There was a problem hiding this comment.
@raman325 The comment from Copilot here is a valid concern. If we want to use the with syntax (which is the standard way forward), we need to bump to Node.js 22 minimum.
There was a problem hiding this comment.
if we bump to 22 here, we'd probably need to do the same in zwave-js-ui since it creates a server instance in code - is that preferred or should we stick with the deprecated syntax for now?
There was a problem hiding this comment.
AFAIK assert will be a SyntaxError on Node 22+. So I think there's no way around bumping and making a major version.
There was a problem hiding this comment.
Alternatively, can we just use node:fs/promises and await readFile instead?
There was a problem hiding this comment.
Switched to readFile with caching per AlCalzone's suggestion in 1feec11. Also added a build copy step so the generated JSON lands in dist-esm/lib/generated/ (tsc doesn't emit it on its own). The existing createRequire usages in const.ts and server.ts are fine — they load package.json from the project root and user config paths, which are always present at runtime.
There was a problem hiding this comment.
I made it simpler - convert the json to TS and then just import it. We keep the JSON in case it's useful for development workflows
4b700ac to
b74b4cc
Compare
- Add `mkdir -p` to create generated directory before schema generation - Add `--no-type-check` to avoid type errors when JSON doesn't exist yet - Compress introspect responses (schema payload is ~189KB) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
b74b4cc to
0466008
Compare
AlCalzone
left a comment
There was a problem hiding this comment.
Curious, how does this feature take the minimum schema version for some commands into account?
| @@ -0,0 +1,21 @@ | |||
| import { UnknownCommandError } from "../error.js"; | |||
| import incomingMessageSchema from "../generated/incoming_message_schema.json" with { type: "json" }; | |||
There was a problem hiding this comment.
@raman325 The comment from Copilot here is a valid concern. If we want to use the with syntax (which is the standard way forward), we need to bump to Node.js 22 minimum.
right now it doesn't and we haven't really encoded schema versioning into commands. We don't actually gate commands by schema, the only thing we do is add logic to make returns backwards compatible if something changes in the API. Realistically, I am not sure how valuable being able to filter by schema version for commands is as helpful as e.g. seeing events and states for a given schema version. I was planning to add it for states because it's easy enough, we have all the types defined. Will hvae to work through how to do it for events and how it could be implemented for commands |
- Enable compression for introspect responses (large schema payload) - Use cross-platform Node.js script instead of mkdir -p - Add integration test for pre-initialize introspect.commands Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: AlCalzone <dominic.griesel@nabucasa.com>
Avoids Node version compatibility issues with `with { type: "json" }`
(Node 22+) vs `assert { type: "json" }` (deprecated, errors on Node 22+).
Uses readFile with caching for the 189KB schema payload. Adds a copy
step to the build so the generated JSON lands in dist-esm/.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fb4e567 to
1feec11
Compare
Instead of importing JSON with `with { type: "json" }` (Node 22+ only)
or reading it at runtime with readFile, generate a .ts wrapper alongside
the JSON so tsc handles it like any other module. No copy step needed,
no runtime file I/O, no caching logic.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
generate-schema.cjs produces JSON only, wrap-json-module.cjs wraps any JSON file as a TS default export. This supports generating multiple schemas before wrapping the final output as a module. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
introspectcommand module following the existing module pattern (node, controller, etc.)introspect.commandsreturns the full JSON Schema (Draft 7) describing all incoming message typesinitialize, enabling API discovery pre-negotiationts-json-schema-generatorand served at runtime$refreuse in the generated schema (189KB vs 307KB)Changes
src/lib/introspect/— new module with command, incoming_message, outgoing_message, message_handlersrc/lib/instance.ts— registerintrospectinstancesrc/lib/command.ts— exportIntrospectCommandsrc/lib/incoming_message.ts— addIncomingMessageIntrospectto unionsrc/lib/outgoing_message.ts— addIntrospectResultTypesto result typessrc/lib/server.ts— wireIntrospectMessageHandlerinto instance handlers$refreusepackage.json— addts-json-schema-generator,generate:schemascript, prebuild hookREADME.md— add introspection section with tool recommendationsTest plan
npm testpasses (schema generation, prettier, tsc, lint, integration tests){"messageId": "1", "command": "introspect.commands"}beforeinitialize— returns valid JSON Schema🤖 Generated with Claude Code