The bridge subscribed to <root>/#, so the broker sent back every discover_r, alexaResponce and changeReport
the bridge published, and the backend's <endpointId>/alexaDirective_e. It now subscribes to <root>/discover
and <root>/+/alexaDirective, both named in src/topics.ts; a directive topic is recognised by the root and the
last segment instead of by counting three segments.
The test reads the subscriptions from the broker, publishes seven other topics under the root and one
directive for each of two endpoints: the bridge receives the two directives and the discovery request,
nothing else. Tests: 148 -> 150 (the count in the body of 6789a1a, 108 -> 113, should read 143 -> 148).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
src/transport.ts has the Publisher interface, MqttPublisher (the client the bridge has at the time) and
MemoryPublisher (tests without a broker); src/topics.ts names the four topics the library publishes to.
Device, AlexaStatusMessage, AlexaErrorResponse and sendSceneResponse shared three copies of the
publish-and-report code: they now call one send() that resolves the topic or "" and never rejects.
registerDevice() and addDevice() work before connect(); a send() without a connection resolves "" and the
"error" event says to call connect(). unregisterDevice() and clearDevices() take the publisher from the device.
Device.setMqttClient() is gone, the first constructor argument of Device is ignored, and the message classes
take a Publisher where they took the client. Tests: 108 -> 113.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
src/messages builds Response, StateReport, DeferredResponse, ErrorResponse, ChangeReport and the scene, doorbell
and simple events from plain values; messageId and times are parameters, so a test compares whole objects.
StateBuilder collects properties checked by the descriptors, AlexaError and AlexaErrors carry the payload fields
of an error type. AlexaStatusMessage and AlexaErrorResponse move to src/compat and are written on the builders.
On the wire, against 1.5.2: a DeferredResponse has no context key, a ChangeReport has no correlationToken, the
namespace of an ErrorResponse follows its type, and a ChangeReport without a changed property is not published:
send() resolves "" and the bridge reports an error that names the endpoint.
143 tests pass (108 before).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The three generic controllers are described in full and leave the stub table (62 stubs remain). A declaration
takes an instance name, friendly names from text() and asset(), and the options of the interface: range, unit and
presets for a range; supportedModes and ordered for a mode; semantics() for all three, with actions mapped to
directives and states mapped to a value or a range of values.
Refused at device.add(), each with the endpoint, interface and instance in the message: no instance name or no
friendly name; an instance declared twice; a first friendly name or a phrase used by another capability of the
endpoint; a reserved word, an asset id that is not in the catalog; range.max not above range.min; a preset off the
precision grid or outside the range; fewer than two modes or a mode listed twice; a mapping to a directive the
interface does not have, to a payload that does not fit, to a mode that is not listed, to AdjustMode on modes
that are not ordered; SetEcoOn, SetEcoOff, EcoOn and EcoOff on anything but a toggle.
For 1.x callers: Alexa.RangeController lists rangeValue (1.5.2 sent supported: []); addSupportedModes() announces
a mode given as a string as { value } and notes it, and takes { ordered: false } as a second argument, the default
stays true as 1.5.2 sent it (device.add() defaults to false). Declared the 1.x way, the zoo fan and curtain give
the same JSON as before.
Tests: the zoo blind, whose range, unit and semantics were written by hand into the JSON of 1.5.2, is declared
with device.add() and equals that JSON plus the Alexa capability; five capability objects of Amazon's examples
are reproduced the same way. 27 examples of four pages added. npm test: 108 pass (was 85) in 10.5-11.3 s, also on
Node 18.20.8 and 20.20.2.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A capability is a descriptor plus what the endpoint declares (device/Capability.ts), and its discovery object is
generated from the two. The new API is bridge.addDevice({ endpointId, name, categories, ... }) and
device.add(PowerController, options): both throw a DeclarationError that names the endpoint, the interface and
the instance (device/validate.ts), and leave the bridge and the device as they were. AlexaInterface is the same
Capability with the 1.x methods on it; it, ActionMapping and the enums moved to src/compat/, Device to src/device/.
What a 1.x caller can observe:
- every endpoint ends with { type: "AlexaInterface", interface: "Alexa", version: "3" } (alexa-interface.html);
new Alex2MQTT(..., { alexaInterface: false }) leaves it out
- the fields of a capability object come in the order of Amazon's examples; their content is unchanged
- addCapability() with a name that is not an interface throws (1.5.2 announced it with the version "UNKNOWN")
- ActionMapping takes the payload as an object; a JSON string is parsed (1.5.2 sent the string), any other throws
- what Alexa would reject in a 1.x declaration is not refused: device.check() lists it and the bridge logs each
line once, as "warning: ..." through the log hook, when it answers a discovery
- a device whose JSON cannot be built is left out of the answer and reported as an error event
- PowerController and EndpointHealth are the descriptors and keep ON/OFF and OK/UNREACHABLE; PowerState is new
Tests: six zoo devices declared the 1.x way give the JSON that Alexa accepted from 1.5.2 on 2026-09-28, plus the
Alexa capability. npm test: 85 pass (was 57) in 10-12 s, also on Node 18.20.8 and 20.20.2.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
src/registry/ holds what the library knows about an interface: namespace, version, the page it was read from,
properties with their value schemas and directives with their payload schemas. Five interfaces are described
(Alexa, PowerController, BrightnessController, TemperatureSensor, EndpointHealth); the other 65 names of
AlexaInterfaceType are stubs with the version and property names of 1.5.2. schema.ts is the run-time check behind
it (241 lines, no new dependency), catalog.ts the vocabularies of the pages: 103 assets (23 units), 6 actions,
9 states, 56 display categories, 22 reserved words, 73 error types under 11 namespaces.
AlexaInterface.getVersion() and getProps() read the registry; the two switch statements are gone (-167 lines).
On the wire: Alexa.EndpointHealth is announced at 3.1 (was 3.3; the page is titled 3.1 and no page mentions 3.3),
and TimeHoldController and Camera.LiveViewController at 3 and 1.7 (1.5.2 sent the string "UNKNOWN").
DisplayCategory gains VACUUM. New exports: registry, DeclarationError, SchemaError, Assets, Units, Actions,
States, DisplayCategories and the descriptor types.
Tests: 20 JSON examples of the five pages under test/fixtures/alexa-docs; every directive payload and property
value in them parses with its descriptor. npm test: 57 pass (was 30) in 10.8 s, also on Node 18.20.8 and 20.20.2.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
"exports" gains "import" and "types" next to "require": import "alex2node" failed with
ERR_PACKAGE_PATH_NOT_EXPORTED. scripts/build.mjs compiles src/ to dist/cjs, dist/esm and dist/types in a staging
directory and swaps them in only when both compiler runs passed. "main" and "types" follow for resolvers that do
not read "exports". Target ES6 -> ES2020, "engines": node >= 18, relative imports carry ".js" for Node's ESM loader.
uuid gives way to crypto.randomUUID(); mqtt is the only runtime dependency left.
Tests load the package by name. New: both entry points, the modules the builds import, dist/ equal to a fresh
build, a round trip on the ES module build, and two compiled type fixtures in place of the .d.ts regexes.
22 tests pass in 5.7 s. dist/: 16 files, 79,711 B -> 25 files, 132,291 B; the npm tarball is 25,125 B.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>