registry: describe an Alexa interface as data

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>
This commit is contained in:
David 2026-09-28 15:18:22 +00:00
parent 128ca35c2a
commit aa0ffd64ea
93 changed files with 4058 additions and 490 deletions

View file

@ -1,6 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AlexaInterface = exports.AlexaInterfaceType = void 0;
const index_js_1 = require("./registry/index.js");
var AlexaInterfaceType;
(function (AlexaInterfaceType) {
AlexaInterfaceType["APPLICATION_STATE_REPORTER"] = "Alexa.ApplicationStateReporter";
@ -103,170 +104,13 @@ class AlexaInterface {
getTypeString() {
return this.type;
}
/** The version of the interface, from its descriptor. "UNKNOWN" for a name the registry does not have. */
getVersion() {
switch (this.type) {
case AlexaInterfaceType.APPLICATION_STATE_REPORTER:
case AlexaInterfaceType.AUDIO_PLAY_QUEUE:
case AlexaInterfaceType.AUTHORIZATION_CONTROLLER:
case AlexaInterfaceType.AUTOMATION_MANAGEMENT:
case AlexaInterfaceType.AUTOMOTIVE_VEHICLE_DATA:
case AlexaInterfaceType.COMMISSIONABLE:
case AlexaInterfaceType.CONSENT_MANAGEMENT_CONSENT_REQUIRED_REPORTER:
case AlexaInterfaceType.COOKING:
case AlexaInterfaceType.COOKING_FOOD_TEMPERATURE_CONTROLLER:
case AlexaInterfaceType.COOKING_FOOD_TEMPERATURE_SENSOR:
case AlexaInterfaceType.COOKING_PRESET_CONTROLLER:
case AlexaInterfaceType.COOKING_TEMPERATURE_CONTROLLER:
case AlexaInterfaceType.COOKING_TEMPERATURE_SENSOR:
case AlexaInterfaceType.COOKING_TIME_CONTROLLER:
case AlexaInterfaceType.DATA_CONTROLLER:
case AlexaInterfaceType.DEVICE_USAGE_ESTIMATION:
case AlexaInterfaceType.DEVICE_USAGE_METER:
case AlexaInterfaceType.EQUALIZER_CONTROLLER:
case AlexaInterfaceType.INVENTORY_LEVEL_SENSOR:
case AlexaInterfaceType.INVENTORY_LEVEL_USAGE_SENSOR:
case AlexaInterfaceType.INVENTORY_USAGE_SENSOR:
case AlexaInterfaceType.KEYPAD_CONTROLLER:
case AlexaInterfaceType.MEDIA_PLAYBACK:
case AlexaInterfaceType.MEDIA_PLAY_QUEUE:
case AlexaInterfaceType.MEDIA_SEARCH:
case AlexaInterfaceType.PLAYBACK_STATE_REPORTER:
case AlexaInterfaceType.PROACTIVE_NOTIFICATION_SOURCE:
case AlexaInterfaceType.REMOTE_VIDEO_PLAYER:
case AlexaInterfaceType.RTC_SESSION_CONTROLLER:
case AlexaInterfaceType.SECURITY_PANEL_CONTROLLER:
case AlexaInterfaceType.SECURITY_PANEL_CONTROLLER_ALERT:
case AlexaInterfaceType.SIMPLE_EVENT_SOURCE:
case AlexaInterfaceType.SMART_VISION_OBJECT_DETECTION_SENSOR:
case AlexaInterfaceType.SMART_VISION_SNAPSHOT_PROVIDER:
case AlexaInterfaceType.SPEAKER:
case AlexaInterfaceType.STEP_SPEAKER:
case AlexaInterfaceType.THERMOSTAT_CONTROLLER_CONFIGURATION:
case AlexaInterfaceType.THERMOSTAT_CONTROLLER_HVAC_COMPONENTS:
case AlexaInterfaceType.THERMOSTAT_CONTROLLER_SCHEDULE:
case AlexaInterfaceType.UI_CONTROLLER:
case AlexaInterfaceType.USER_PREFERENCE:
case AlexaInterfaceType.VIDEO_RECORDER:
case AlexaInterfaceType.WAKE_ON_LAN_CONTROLLER:
return "1";
case AlexaInterfaceType.BRIGHTNESS_CONTROLLER:
case AlexaInterfaceType.CAMERA_STREAM_CONTROLLER:
case AlexaInterfaceType.CHANNEL_CONTROLLER:
case AlexaInterfaceType.COLOR_CONTROLLER:
case AlexaInterfaceType.COLOR_TEMPERATURE_CONTROLLER:
case AlexaInterfaceType.CONTACT_SENSOR:
case AlexaInterfaceType.DOORBELL_EVENT_SOURCE:
case AlexaInterfaceType.INPUT_CONTROLLER:
case AlexaInterfaceType.LOCK_CONTROLLER:
case AlexaInterfaceType.MODE_CONTROLLER:
case AlexaInterfaceType.MOTION_SENSOR:
case AlexaInterfaceType.PERCENTAGE_CONTROLLER:
case AlexaInterfaceType.PLAYBACK_CONTROLLER:
case AlexaInterfaceType.POWER_CONTROLLER:
case AlexaInterfaceType.POWER_LEVEL_CONTROLLER:
case AlexaInterfaceType.RANGE_CONTROLLER:
case AlexaInterfaceType.RECORD_CONTROLLER:
case AlexaInterfaceType.SCENE_CONTROLLER:
case AlexaInterfaceType.SEEK_CONTROLLER:
case AlexaInterfaceType.TEMPERATURE_SENSOR:
case AlexaInterfaceType.TOGGLE_CONTROLLER:
return "3";
case AlexaInterfaceType.LAUNCHER:
return "1.1";
case AlexaInterfaceType.THERMOSTAT_CONTROLLER:
return "3.2";
case AlexaInterfaceType.ENDPOINT_HEALTH:
return "3.3";
default:
return "UNKNOWN";
}
return index_js_1.registry.has(this.type) ? index_js_1.registry.get(this.type).version : "UNKNOWN";
}
/** The names of the properties the interface reports, from its descriptor. */
getProps() {
switch (this.type) {
case AlexaInterfaceType.AUTOMATION_MANAGEMENT:
return ["automationStatuses"];
case AlexaInterfaceType.BRIGHTNESS_CONTROLLER:
return ["brightness"];
case AlexaInterfaceType.CHANNEL_CONTROLLER:
return ["channel"];
case AlexaInterfaceType.COLOR_CONTROLLER:
return ["color"];
case AlexaInterfaceType.COLOR_TEMPERATURE_CONTROLLER:
return ["colorTemperatureInKelvin"];
case AlexaInterfaceType.CONTACT_SENSOR:
return ["detectionState"];
case AlexaInterfaceType.POWER_CONTROLLER:
return ["powerState"];
case AlexaInterfaceType.ENDPOINT_HEALTH:
return ["connectivity"];
case AlexaInterfaceType.TOGGLE_CONTROLLER:
return ["toggleState"];
case AlexaInterfaceType.MODE_CONTROLLER:
return ["mode"];
case AlexaInterfaceType.TEMPERATURE_SENSOR:
return ["temperature"];
case AlexaInterfaceType.THERMOSTAT_CONTROLLER:
return [
"targetSetpoint",
"lowerSetpoint",
"upperSetpoint",
"thermostatMode",
];
case AlexaInterfaceType.APPLICATION_STATE_REPORTER:
case AlexaInterfaceType.AUDIO_PLAY_QUEUE:
case AlexaInterfaceType.AUTHORIZATION_CONTROLLER:
case AlexaInterfaceType.AUTOMOTIVE_VEHICLE_DATA:
case AlexaInterfaceType.CAMERA_LIVE_VIEW_CONTROLLER:
case AlexaInterfaceType.CAMERA_STREAM_CONTROLLER:
case AlexaInterfaceType.COMMISSIONABLE:
case AlexaInterfaceType.CONSENT_MANAGEMENT_CONSENT_REQUIRED_REPORTER:
case AlexaInterfaceType.COOKING:
case AlexaInterfaceType.DATA_CONTROLLER:
case AlexaInterfaceType.DEVICE_USAGE_ESTIMATION:
case AlexaInterfaceType.DEVICE_USAGE_METER:
case AlexaInterfaceType.DOORBELL_EVENT_SOURCE:
case AlexaInterfaceType.EQUALIZER_CONTROLLER:
case AlexaInterfaceType.INPUT_CONTROLLER:
case AlexaInterfaceType.INVENTORY_LEVEL_SENSOR:
case AlexaInterfaceType.INVENTORY_LEVEL_USAGE_SENSOR:
case AlexaInterfaceType.INVENTORY_USAGE_SENSOR:
case AlexaInterfaceType.KEYPAD_CONTROLLER:
case AlexaInterfaceType.LAUNCHER:
case AlexaInterfaceType.LOCK_CONTROLLER:
case AlexaInterfaceType.MEDIA_PLAYBACK:
case AlexaInterfaceType.MEDIA_SEARCH:
case AlexaInterfaceType.MOTION_SENSOR:
case AlexaInterfaceType.PERCENTAGE_CONTROLLER:
case AlexaInterfaceType.PLAYBACK_CONTROLLER:
case AlexaInterfaceType.PLAYBACK_STATE_REPORTER:
case AlexaInterfaceType.PROACTIVE_NOTIFICATION_SOURCE:
case AlexaInterfaceType.RANGE_CONTROLLER:
case AlexaInterfaceType.RECORD_CONTROLLER:
case AlexaInterfaceType.REMOTE_VIDEO_PLAYER:
case AlexaInterfaceType.RTC_SESSION_CONTROLLER:
case AlexaInterfaceType.SCENE_CONTROLLER:
return []; // scenes have no reportable properties: Activate/Deactivate answer with ActivationStarted (Device.sendSceneResponse)
case AlexaInterfaceType.SECURITY_PANEL_CONTROLLER:
case AlexaInterfaceType.SEEK_CONTROLLER:
case AlexaInterfaceType.SIMPLE_EVENT_SOURCE:
case AlexaInterfaceType.SMART_VISION_OBJECT_DETECTION_SENSOR:
case AlexaInterfaceType.SMART_VISION_SNAPSHOT_PROVIDER:
case AlexaInterfaceType.SPEAKER:
case AlexaInterfaceType.STEP_SPEAKER:
case AlexaInterfaceType.THERMOSTAT_CONTROLLER_CONFIGURATION:
case AlexaInterfaceType.THERMOSTAT_CONTROLLER_HVAC_COMPONENTS:
case AlexaInterfaceType.THERMOSTAT_CONTROLLER_SCHEDULE:
case AlexaInterfaceType.TIME_HOLD_CONTROLLER:
case AlexaInterfaceType.UI_CONTROLLER:
case AlexaInterfaceType.USER_PREFERENCE:
case AlexaInterfaceType.VIDEO_RECORDER:
case AlexaInterfaceType.WAKE_ON_LAN_CONTROLLER:
// no property list known for these: discovery lists the capability with an empty "supported" (the bridge notes
// it through its log hook; 1.5.1 wrote "UNSUPORTED INTERFACE TYPE" to stderr on every discovery)
return [];
default:
return [];
}
return index_js_1.registry.has(this.type) ? Object.keys(index_js_1.registry.get(this.type).properties) : [];
}
getJSON() {
const doc = {

View file

@ -55,6 +55,8 @@ var DisplayCategory;
DisplayCategory["THERMOSTAT"] = "THERMOSTAT";
DisplayCategory["TV"] = "TV";
DisplayCategory["VACUUM_CLEANER"] = "VACUUM_CLEANER";
DisplayCategory["VACUUM"] = "VACUUM";
/** Not on the list of display categories any more; kept for 1.x callers. */
DisplayCategory["VEHICLE"] = "VEHICLE";
DisplayCategory["WASHER"] = "WASHER";
DisplayCategory["WATER_HEATER"] = "WATER_HEATER";

13
dist/cjs/index.js vendored
View file

@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AlexaErrorResponse = exports.AlexaErrorType = exports.ThermostatMode = exports.TemperatureSensorScale = exports.EndpointHealth = exports.PowerController = exports.AlexaStatusMessage = exports.DisplayCategory = exports.AlexaActions = exports.ActionMapping = exports.AlexaInterfaceType = exports.AlexaInterface = exports.Device = exports.DEFAULT_HOST = exports.Alex2MQTT = void 0;
exports.DisplayCategories = exports.States = exports.Actions = exports.Units = exports.Assets = exports.SchemaError = exports.DeclarationError = exports.registry = exports.AlexaErrorResponse = exports.AlexaErrorType = exports.ThermostatMode = exports.TemperatureSensorScale = exports.EndpointHealth = exports.PowerController = exports.AlexaStatusMessage = exports.DisplayCategory = exports.AlexaActions = exports.ActionMapping = exports.AlexaInterfaceType = exports.AlexaInterface = exports.Device = exports.DEFAULT_HOST = exports.Alex2MQTT = void 0;
// Relative specifiers carry ".js": Node's ES module loader resolves no extension, and TypeScript maps it back to the .ts.
var Alex2Node_js_1 = require("./Alex2Node.js");
Object.defineProperty(exports, "Alex2MQTT", { enumerable: true, get: function () { return __importDefault(Alex2Node_js_1).default; } });
@ -27,3 +27,14 @@ Object.defineProperty(exports, "ThermostatMode", { enumerable: true, get: functi
var AlexaErrorResponse_js_1 = require("./AlexaErrorResponse.js");
Object.defineProperty(exports, "AlexaErrorType", { enumerable: true, get: function () { return AlexaErrorResponse_js_1.AlexaErrorType; } });
Object.defineProperty(exports, "AlexaErrorResponse", { enumerable: true, get: function () { return AlexaErrorResponse_js_1.AlexaErrorResponse; } });
// The interface registry and the vocabularies of the Smart Home API
var index_js_1 = require("./registry/index.js");
Object.defineProperty(exports, "registry", { enumerable: true, get: function () { return index_js_1.registry; } });
Object.defineProperty(exports, "DeclarationError", { enumerable: true, get: function () { return index_js_1.DeclarationError; } });
Object.defineProperty(exports, "SchemaError", { enumerable: true, get: function () { return index_js_1.SchemaError; } });
var index_js_2 = require("./registry/index.js");
Object.defineProperty(exports, "Assets", { enumerable: true, get: function () { return index_js_2.ASSETS; } });
Object.defineProperty(exports, "Units", { enumerable: true, get: function () { return index_js_2.UNITS_OF_MEASURE; } });
Object.defineProperty(exports, "Actions", { enumerable: true, get: function () { return index_js_2.ACTIONS; } });
Object.defineProperty(exports, "States", { enumerable: true, get: function () { return index_js_2.STATES; } });
Object.defineProperty(exports, "DisplayCategories", { enumerable: true, get: function () { return index_js_2.DISPLAY_CATEGORIES; } });

132
dist/cjs/registry/catalog.js vendored Normal file
View file

@ -0,0 +1,132 @@
"use strict";
// The fixed vocabularies of the Smart Home API, copied from Amazon's pages as read on 2026-09-28. The pages are under
// https://developer.amazon.com/docs/alexaplus/device-apis/. A value missing here is one Amazon added since.
Object.defineProperty(exports, "__esModule", { value: true });
exports.ERROR_TYPES = exports.LIMITS = exports.RESERVED_WORDS = exports.DISPLAY_CATEGORIES = exports.STATES = exports.ACTIONS = exports.UNITS_OF_MEASURE = exports.ASSETS = void 0;
/**
* The asset ids a friendly name can refer to: 103, the units of measure among them
* (resources-and-assets.html, "Global Alexa catalog").
*/
exports.ASSETS = [
"Alexa.Actions.Charge", "Alexa.Actions.Clean", "Alexa.Actions.Dispense", "Alexa.Actions.Dock",
"Alexa.Actions.Empty", "Alexa.Actions.Mop", "Alexa.Actions.Skip", "Alexa.Actions.Sweep", "Alexa.Actions.Vacuum",
"Alexa.Button.OffButton", "Alexa.Button.OnButton", "Alexa.Button.BrightenButton", "Alexa.Button.DimButton",
"Alexa.Button.MainButton", "Alexa.Button.TopButton", "Alexa.Button.BottomButton", "Alexa.Button.CenterButton",
"Alexa.Button.MiddleButton", "Alexa.Button.UpButton", "Alexa.Button.DownButton", "Alexa.Button.LeftButton",
"Alexa.Button.RightButton", "Alexa.Button.FirstButton", "Alexa.Button.SecondButton", "Alexa.Button.ThirdButton",
"Alexa.Button.FourthButton", "Alexa.Button.FifthButton", "Alexa.Button.SixthButton", "Alexa.Button.SeventhButton",
"Alexa.Button.EighthButton", "Alexa.Button.DoublePress", "Alexa.Button.DoublePush", "Alexa.Button.LongPress",
"Alexa.Button.LongPush", "Alexa.Button.SinglePress", "Alexa.Button.SinglePush",
"Alexa.DeviceName.AirPurifier", "Alexa.DeviceName.Camera", "Alexa.DeviceName.Fan", "Alexa.DeviceName.Router",
"Alexa.DeviceName.Shade", "Alexa.DeviceName.Shower", "Alexa.DeviceName.SpaceHeater", "Alexa.DeviceName.Washer",
"Alexa.Gesture.DoubleClick", "Alexa.Gesture.SingleClick", "Alexa.Gesture.SwipeDown", "Alexa.Gesture.SwipeLeft",
"Alexa.Gesture.SwipeRight", "Alexa.Gesture.SwipeUp", "Alexa.Gesture.Tap",
// "Gestures", plural: the page spells this one id differently from the seven above
"Alexa.Gestures.DoubleTap",
"Alexa.Setting.2GGuestWiFi", "Alexa.Setting.5GGuestWiFi", "Alexa.Setting.Auto", "Alexa.Setting.Direction",
"Alexa.Setting.DryCycle", "Alexa.Setting.FanSpeed", "Alexa.Setting.GuestWiFi", "Alexa.Setting.Heat",
"Alexa.Setting.Mode", "Alexa.Setting.Night", "Alexa.Setting.Opening", "Alexa.Setting.Oscillate",
"Alexa.Setting.Preset", "Alexa.Setting.Quiet", "Alexa.Setting.Temperature", "Alexa.Setting.WashCycle",
"Alexa.Setting.WaterTemperature",
"Alexa.Shower.HandHeld", "Alexa.Shower.RainHead",
"Alexa.Unit.Angle.Degrees", "Alexa.Unit.Angle.Radians", "Alexa.Unit.Distance.Feet", "Alexa.Unit.Distance.Inches",
"Alexa.Unit.Distance.Kilometers", "Alexa.Unit.Distance.Meters", "Alexa.Unit.Distance.Miles",
"Alexa.Unit.Distance.Yards", "Alexa.Unit.Mass.Grams", "Alexa.Unit.Mass.Kilograms", "Alexa.Unit.Percent",
"Alexa.Unit.Temperature.Celsius", "Alexa.Unit.Temperature.Degrees", "Alexa.Unit.Temperature.Fahrenheit",
"Alexa.Unit.Temperature.Kelvin", "Alexa.Unit.Volume.CubicFeet", "Alexa.Unit.Volume.CubicMeters",
"Alexa.Unit.Volume.Gallons", "Alexa.Unit.Volume.Liters", "Alexa.Unit.Volume.Pints", "Alexa.Unit.Volume.Quarts",
"Alexa.Unit.Weight.Ounces", "Alexa.Unit.Weight.Pounds",
"Alexa.Value.Close", "Alexa.Value.Delicate", "Alexa.Value.High", "Alexa.Value.Low", "Alexa.Value.Maximum",
"Alexa.Value.Medium", "Alexa.Value.Minimum", "Alexa.Value.Open", "Alexa.Value.QuickWash",
];
exports.UNITS_OF_MEASURE = exports.ASSETS.filter((id) => id.startsWith("Alexa.Unit."));
/** The phrases an action mapping gives to a directive (alexa-discovery-objects.html, "ActionMappings object"). */
exports.ACTIONS = [
"Alexa.Actions.Open", "Alexa.Actions.Close", "Alexa.Actions.Raise", "Alexa.Actions.Lower",
"Alexa.Actions.SetEcoOn", "Alexa.Actions.SetEcoOff",
];
/** The states a state mapping gives to a property value (alexa-discovery-objects.html, "StateMappings object"). */
exports.STATES = [
"Alexa.States.Open", "Alexa.States.Closed", "Alexa.States.EcoOn", "Alexa.States.EcoOff", "Alexa.States.Low",
"Alexa.States.Empty", "Alexa.States.Full", "Alexa.States.Done", "Alexa.States.Stuck",
];
/**
* The 56 display categories (alexa-discovery.html, "Display categories"). The DisplayCategory enum also keeps
* VEHICLE from 1.x, which the page no longer lists.
*/
exports.DISPLAY_CATEGORIES = [
"ACTIVITY_TRIGGER", "AIR_CONDITIONER", "AIR_FRESHENER", "AIR_PURIFIER", "AIR_QUALITY_MONITOR",
"ALEXA_VOICE_ENABLED", "AUTO_ACCESSORY", "BLUETOOTH_SPEAKER", "CAMERA", "CHRISTMAS_TREE", "COFFEE_MAKER",
"COMPUTER", "CONTACT_SENSOR", "DISHWASHER", "DOOR", "DOORBELL", "DRYER", "EXTERIOR_BLIND", "FAN", "GAME_CONSOLE",
"GARAGE_DOOR", "HEADPHONES", "HUB", "INTERIOR_BLIND", "LAPTOP", "LIGHT", "MICROWAVE", "MOBILE_PHONE",
"MOTION_SENSOR", "MUSIC_SYSTEM", "NETWORK_HARDWARE", "OTHER", "OVEN", "PHONE", "PRINTER", "REMOTE", "ROUTER",
"SCENE_TRIGGER", "SCREEN", "SECURITY_PANEL", "SECURITY_SYSTEM", "SLOW_COOKER", "SMARTLOCK", "SMARTPLUG", "SPEAKER",
"STREAMING_DEVICE", "SWITCH", "TABLET", "TEMPERATURE_SENSOR", "THERMOSTAT", "TV", "VACUUM_CLEANER", "VACUUM",
"WASHER", "WATER_HEATER", "WEARABLE",
];
/** Not to be used as a friendly name (resources-and-assets.html, "Reserved words"). */
exports.RESERVED_WORDS = [
"alarm", "alarms", "all alarms", "away mode", "bass", "camera", "date", "date today", "day", "do not disturb",
"drop in", "music", "night light", "notification", "playing", "sleep sounds", "time", "timer", "today in music",
"treble", "volume", "way f. m.",
];
/**
* What a discovery answer may hold (alexa-discovery.html, "Interface limits"; alexa-discovery-objects.html,
* "Endpoint object details" and "AdditionalAttributes object details"; alexa-scenecontroller.html, "Discovery").
*/
exports.LIMITS = {
endpointsPerCustomer: 300,
capabilitiesPerEndpoint: 100,
endpointIdLength: 256,
friendlyNameLength: 256,
sceneFriendlyNameLength: 128,
manufacturerNameLength: 128,
descriptionLength: 128,
additionalAttributeLength: 256,
cookieBytes: 5000,
};
// alexa-errorresponse.html, "Error type values". The Interface column names <namespace>.ErrorResponse; the event
// header carries the namespace and the name ErrorResponse, as the examples on the ThermostatController,
// SecurityPanelController and Safety error pages show. The other namespaces are as the table spells them.
// INVALID_VALUE is listed under Alexa and under SmartVision.ObjectDetectionSensor, SUBSCRIPTION_REQUIRED under both
// SmartVision interfaces: the first one listed is kept.
const ERROR_TYPES_BY_NAMESPACE = {
"Alexa": [
"ALREADY_IN_OPERATION", "BRIDGE_UNREACHABLE", "CLOUD_CONTROL_DISABLED", "DEVICE_STUCK", "DO_NOT_DISTURB_MODE",
"ENDPOINT_BUSY", "ENDPOINT_CONTROL_UNAVAILABLE", "ENDPOINT_LOW_POWER", "ENDPOINT_UNREACHABLE",
"EXPIRED_AUTHORIZATION_CREDENTIAL", "FIRMWARE_OUT_OF_DATE", "HARDWARE_MALFUNCTION", "INSUFFICIENT_PERMISSIONS",
"INSUFFICIENT_RESOURCE", "INTERNAL_ERROR", "INVALID_AUTHORIZATION_CREDENTIAL", "INVALID_DIRECTIVE",
"INVALID_VALUE", "MAINTENANCE_REQUIRED", "NO_SUCH_ENDPOINT", "NOT_CALIBRATED", "NOT_IN_OPERATION",
"NOT_SUPPORTED_IN_CURRENT_MODE", "NOT_SUPPORTED_WITH_CURRENT_BATTERY_CHARGE_STATE",
"PARTNER_APPLICATION_REDIRECTION", "POWER_LEVEL_NOT_SUPPORTED", "RATE_LIMIT_EXCEEDED",
"TEMPERATURE_VALUE_OUT_OF_RANGE", "TOO_MANY_FAILED_ATTEMPTS", "UNABLE_TO_CHARGE", "VALUE_OUT_OF_RANGE",
],
"Alexa.Commissionable.ReportCommissioningInformation": [
"FAILED_TO_BOOTSTRAP_COMMISSIONING_PROCESS", "MAX_COMMISSIONING_LIMIT_REACHED",
],
"Alexa.Cooking": [
"CHILD_LOCK", "COOK_DURATION_TOO_LONG", "DOOR_CLOSED_TOO_LONG", "DOOR_OPEN", "PREHEAT_REQUIRED",
"PROBE_REQUIRED", "REMOTE_START_NOT_SUPPORTED", "REMOVE_PROBE", "REMOTE_START_DISABLED",
],
"Alexa.DataController": ["DATA_DELETION_NOT_SUPPORTED", "DATA_RETRIEVAL_NOT_SUPPORTED"],
"Alexa.Safety": ["OBSTACLE_DETECTED", "SAFETY_BEAM_BREACHED"],
"Alexa.SecurityPanelController": [
"AUTHORIZATION_REQUIRED", "BYPASS_NEEDED", "NOT_READY", "UNAUTHORIZED", "UNCLEARED_ALARM", "UNCLEARED_TROUBLE",
],
"Alexa.SmartVision.ObjectDetectionSensor": ["SUBSCRIPTION_REQUIRED"],
"Alexa.SmartVision.SnapshotProvider": ["DISABLED_BY_USER"],
"Alexa.ThermostatController": [
"DUAL_SETPOINTS_UNSUPPORTED", "REQUESTED_SETPOINTS_TOO_CLOSE", "THERMOSTAT_IS_OFF",
"TRIPLE_SETPOINTS_UNSUPPORTED", "UNSUPPORTED_THERMOSTAT_MODE", "UNWILLING_TO_SET_SCHEDULE",
"UNWILLING_TO_SET_VALUE",
],
"Alexa.ThermostatController.Configuration": [
"CONFIGURATION_UPDATE_NOT_ALLOWED", "COOLING_LOCKOUT_TEMPERATURE_VALUE_OUT_OF_RANGE",
"COOLING_STAGES_EXCEEDS_LIMIT", "HEATING_LOCKOUT_TEMPERATURE_VALUE_OUT_OF_RANGE", "HEATING_STAGES_EXCEEDS_LIMIT",
"INVALID_AUXILIARY_HEATING_SYSTEM_TYPE", "INVALID_SYSTEM_TYPE", "INVALID_TARGET_STATE",
"INVALID_TEMPERATURE_SCALE", "INVALID_TERMINAL_CONNECTION", "MISSING_SETUP_INFORMATION",
],
"Alexa.ThermostatController.Schedule": ["INSUFFICIENT_SPACE"],
};
/** The 73 error types of the table, each with the namespace its ErrorResponse goes under. */
exports.ERROR_TYPES = Object.fromEntries(Object.entries(ERROR_TYPES_BY_NAMESPACE).flatMap(([namespace, types]) => types.map((type) => [type, namespace])));

68
dist/cjs/registry/index.js vendored Normal file
View file

@ -0,0 +1,68 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SchemaError = exports.s = exports.defineInterface = exports.DeclarationError = exports.TemperatureSensor = exports.PowerController = exports.EndpointHealth = exports.BrightnessController = exports.Alexa = exports.registry = void 0;
// The interfaces the library knows, by namespace.
const Alexa_js_1 = require("./interfaces/Alexa.js");
Object.defineProperty(exports, "Alexa", { enumerable: true, get: function () { return Alexa_js_1.Alexa; } });
const BrightnessController_js_1 = require("./interfaces/BrightnessController.js");
Object.defineProperty(exports, "BrightnessController", { enumerable: true, get: function () { return BrightnessController_js_1.BrightnessController; } });
const EndpointHealth_js_1 = require("./interfaces/EndpointHealth.js");
Object.defineProperty(exports, "EndpointHealth", { enumerable: true, get: function () { return EndpointHealth_js_1.EndpointHealth; } });
const PowerController_js_1 = require("./interfaces/PowerController.js");
Object.defineProperty(exports, "PowerController", { enumerable: true, get: function () { return PowerController_js_1.PowerController; } });
const TemperatureSensor_js_1 = require("./interfaces/TemperatureSensor.js");
Object.defineProperty(exports, "TemperatureSensor", { enumerable: true, get: function () { return TemperatureSensor_js_1.TemperatureSensor; } });
const stubs_js_1 = require("./interfaces/stubs.js");
const types_js_1 = require("./types.js");
const described = [
Alexa_js_1.Alexa,
BrightnessController_js_1.BrightnessController,
EndpointHealth_js_1.EndpointHealth,
PowerController_js_1.PowerController,
TemperatureSensor_js_1.TemperatureSensor,
];
const descriptors = new Map();
for (const descriptor of [...described, ...stubs_js_1.STUBS]) {
// A stub left in the table next to the descriptor that replaces it would win or lose by the order of this list
if (descriptors.has(descriptor.namespace))
throw new Error(`${descriptor.namespace} is described twice`);
descriptors.set(descriptor.namespace, descriptor);
}
exports.registry = {
/** Whether an interface of this name is known. */
has(namespace) {
return descriptors.has(namespace);
},
/** The descriptor of "Alexa.RangeController" or of AlexaInterfaceType.RANGE_CONTROLLER, which is that string. */
get(namespace) {
const descriptor = descriptors.get(namespace);
if (!descriptor)
throw new types_js_1.DeclarationError({}, `${JSON.stringify(namespace)} is not an interface alex2node knows`);
return descriptor;
},
/** Every descriptor, ordered by namespace. */
list() {
return [...descriptors.values()].sort((a, b) => (a.namespace < b.namespace ? -1 : 1));
},
};
var types_js_2 = require("./types.js");
Object.defineProperty(exports, "DeclarationError", { enumerable: true, get: function () { return types_js_2.DeclarationError; } });
Object.defineProperty(exports, "defineInterface", { enumerable: true, get: function () { return types_js_2.defineInterface; } });
var schema_js_1 = require("./schema.js");
Object.defineProperty(exports, "s", { enumerable: true, get: function () { return schema_js_1.s; } });
Object.defineProperty(exports, "SchemaError", { enumerable: true, get: function () { return schema_js_1.SchemaError; } });
__exportStar(require("./catalog.js"), exports);

23
dist/cjs/registry/interfaces/Alexa.js vendored Normal file
View file

@ -0,0 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Alexa = void 0;
const schema_js_1 = require("../schema.js");
const types_js_1 = require("../types.js");
/**
* The base interface: every endpoint lists it (alexa-interface.html, "Support the Alexa interface in all add-ons").
* Its one directive to a device is ReportState, answered with a StateReport.
*/
exports.Alexa = (0, types_js_1.defineInterface)({
namespace: "Alexa",
version: "3",
doc: "https://developer.amazon.com/docs/alexaplus/device-apis/alexa-interface.html",
kind: "base",
tier: 1,
instanced: false,
properties: {},
directives: {
ReportState: { name: "ReportState", payload: schema_js_1.s.object({}) },
},
// { type, interface, version } and nothing else, as in the discovery example of the page
discovery: () => ({ properties: false }),
});

View file

@ -0,0 +1,27 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BrightnessController = void 0;
const schema_js_1 = require("../schema.js");
const types_js_1 = require("../types.js");
/** Both directives turn a light that is off on, at the brightness asked for (alexa-brightnesscontroller.html). */
exports.BrightnessController = (0, types_js_1.defineInterface)({
namespace: "Alexa.BrightnessController",
version: "3",
doc: "https://developer.amazon.com/docs/alexaplus/device-apis/alexa-brightnesscontroller.html",
kind: "controller",
tier: 1,
instanced: false,
properties: {
brightness: { name: "brightness", value: schema_js_1.s.number({ min: 0, max: 100, integer: true }) },
},
directives: {
SetBrightness: {
name: "SetBrightness",
payload: schema_js_1.s.object({ brightness: schema_js_1.s.number({ min: 0, max: 100, integer: true }) }),
},
AdjustBrightness: {
name: "AdjustBrightness",
payload: schema_js_1.s.object({ brightnessDelta: schema_js_1.s.number({ min: -100, max: 100, integer: true }) }),
},
},
});

View file

@ -0,0 +1,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EndpointHealth = exports.CONNECTIVITY_REASONS = void 0;
const schema_js_1 = require("../schema.js");
const types_js_1 = require("../types.js");
exports.CONNECTIVITY_REASONS = [
"WIFI_BAD_PASSWORD", "WIFI_AP_NOT_FOUND", "WIFI_ROUTER_UNREACHABLE", "WIFI_AP_CHANNEL_QUALITY_LOW",
"INTERNET_UNREACHABLE", "CAPTIVE_PORTAL_CHECK_FAILED", "UNKNOWN",
];
/**
* Version 3.1, as the page is titled. It defines connectivity only. Examples on other pages declare 3 and 3.2;
* alex2node 1.x declared 3.3, which no page mentions.
*/
exports.EndpointHealth = (0, types_js_1.defineInterface)({
namespace: "Alexa.EndpointHealth",
version: "3.1",
doc: "https://developer.amazon.com/docs/alexaplus/device-apis/alexa-endpointhealth.html",
kind: "sensor",
tier: 1,
instanced: false,
properties: {
connectivity: {
name: "connectivity",
value: schema_js_1.s.object({
value: schema_js_1.s.enum("OK", "UNREACHABLE"),
reason: schema_js_1.s.optional(schema_js_1.s.enum(...exports.CONNECTIVITY_REASONS)),
}),
note: "in every Response, StateReport and ChangeReport; a change of it reported within three seconds",
},
},
directives: {},
});

View file

@ -0,0 +1,31 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PowerController = void 0;
const schema_js_1 = require("../schema.js");
const types_js_1 = require("../types.js");
exports.PowerController = (0, types_js_1.defineInterface)({
namespace: "Alexa.PowerController",
version: "3",
doc: "https://developer.amazon.com/docs/alexaplus/device-apis/alexa-powercontroller.html",
kind: "controller",
tier: 1,
instanced: false,
properties: {
powerState: { name: "powerState", value: schema_js_1.s.enum("ON", "OFF") },
},
directives: {
TurnOn: { name: "TurnOn", payload: schema_js_1.s.object({}) },
TurnOff: { name: "TurnOff", payload: schema_js_1.s.object({}) },
},
options: schema_js_1.s.object({
// The directives Alexa asks the user to confirm before it sends them. Amazon supports this for devices in Japan
// only (alexa-discovery-objects.html, "VerificationsRequired object").
verificationsRequired: schema_js_1.s.optional(schema_js_1.s.array(schema_js_1.s.enum("TurnOn", "TurnOff"), { min: 1 })),
}, { unknownKeys: "reject" }),
discovery({ options }) {
if (!options.verificationsRequired)
return {};
const confirmed = options.verificationsRequired.map((directive) => ({ directive, methods: [{ "@type": "Confirmation" }] }));
return { topLevel: { verificationsRequired: confirmed } };
},
});

View file

@ -0,0 +1,18 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TemperatureSensor = void 0;
const schema_js_1 = require("../schema.js");
const types_js_1 = require("../types.js");
/** A sensor: an endpoint that has it declares Alexa.EndpointHealth as well (alexa-temperaturesensor.html). */
exports.TemperatureSensor = (0, types_js_1.defineInterface)({
namespace: "Alexa.TemperatureSensor",
version: "3",
doc: "https://developer.amazon.com/docs/alexaplus/device-apis/alexa-temperaturesensor.html",
kind: "sensor",
tier: 1,
instanced: false,
properties: {
temperature: { name: "temperature", value: schema_js_1.s.temperature() },
},
directives: {},
});

102
dist/cjs/registry/interfaces/stubs.js vendored Normal file
View file

@ -0,0 +1,102 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.STUBS = void 0;
// Interfaces without a descriptor of their own. A stub names the interface with the version and the properties
// alex2node 1.5.2 put in discovery, so an endpoint that declares one is announced as before. Nothing about it is
// checked, and the version is the one 1.5.2 sent, which is not always the one on the page of the interface
// (Alexa.Speaker is "3" there). A row leaves this table when a descriptor is written from that page.
const schema_js_1 = require("../schema.js");
const DEVICE_APIS = "https://developer.amazon.com/docs/alexaplus/device-apis";
const LIST_OF_INTERFACES = `${DEVICE_APIS}/list-of-interfaces.html`;
// "As of August 7, 2026, Automotive skills and the associated Alexa.AuthorizationController and
// Alexa.Automotive.VehicleData APIs are no longer available."
const DEPRECATED_FEATURES = "https://developer.amazon.com/en-US/docs/alexa/ask-overviews/deprecated-features.html";
const TABLE = [
["Alexa.ApplicationStateReporter", "1", [], "https://developer.amazon.com/docs/alexaplus/alexa-voice-service/alexa-applicationstatereporter.html"],
["Alexa.Audio.PlayQueue", "1", [], "alexa-audio-playqueue.html"],
["Alexa.AuthorizationController", "1", [], DEPRECATED_FEATURES],
["Alexa.AutomationManagement", "1", ["automationStatuses"], "alexa-automationmanagement.html"],
["Alexa.Automotive.VehicleData", "1", [], DEPRECATED_FEATURES],
// 1.5.2 announced the version "UNKNOWN"; 1.7 is what the list of interfaces gives
["Alexa.Camera.LiveViewController", "1.7", [], LIST_OF_INTERFACES],
["Alexa.CameraStreamController", "3", [], "alexa-camerastreamcontroller.html"],
["Alexa.ChannelController", "3", ["channel"], "alexa-channelcontroller.html"],
["Alexa.ColorController", "3", ["color"], "alexa-colorcontroller.html"],
["Alexa.ColorTemperatureController", "3", ["colorTemperatureInKelvin"], "alexa-colortemperaturecontroller.html"],
["Alexa.Commissionable", "1", [], "alexa-commissionable.html"],
["Alexa.ConsentManagement.ConsentRequiredReporter", "1", [], "alexa-consentrequiredreporter.html"],
["Alexa.ContactSensor", "3", ["detectionState"], "alexa-contactsensor.html"],
["Alexa.Cooking", "1", [], "alexa-cooking.html"],
["Alexa.Cooking.FoodTemperatureController", "1", [], "alexa-cooking-foodtemperaturecontroller.html"],
["Alexa.Cooking.FoodTemperatureSensor", "1", [], "alexa-cooking-foodtemperaturesensor.html"],
["Alexa.Cooking.PresetController", "1", [], "alexa-cooking-presetcontroller.html"],
["Alexa.Cooking.TemperatureController", "1", [], "alexa-cooking-temperaturecontroller.html"],
["Alexa.Cooking.TemperatureSensor", "1", [], "alexa-cooking-temperaturesensor.html"],
["Alexa.Cooking.TimeController", "1", [], "alexa-cooking-timecontroller.html"],
["Alexa.DataController", "1", [], "alexa-datacontroller.html"],
["Alexa.DeviceUsage.Estimation", "1", [], "alexa-deviceusage-estimation.html"],
["Alexa.DeviceUsage.Meter", "1", [], "alexa-deviceusage-meter.html"],
["Alexa.DoorbellEventSource", "3", [], "alexa-doorbelleventsource.html"],
["Alexa.EqualizerController", "1", [], "alexa-equalizercontroller.html"],
["Alexa.InputController", "3", [], "alexa-inputcontroller.html"],
["Alexa.InventoryLevelSensor", "1", [], "alexa-inventorylevelsensor.html"],
["Alexa.InventoryLevelUsageSensor", "1", [], "alexa-inventorylevelusagesensor.html"],
["Alexa.InventoryUsageSensor", "1", [], "alexa-inventoryusagesensor.html"],
["Alexa.KeypadController", "1", [], "alexa-keypadcontroller.html"],
["Alexa.Launcher", "1.1", [], "alexa-launcher.html"],
["Alexa.LockController", "3", [], "alexa-lockcontroller.html"],
["Alexa.Media.PlayQueue", "1", [], "alexa-media-playqueue.html"],
["Alexa.Media.Playback", "1", [], "alexa-media-playback.html"],
["Alexa.Media.Search", "1", [], "alexa-media-search.html"],
["Alexa.ModeController", "3", ["mode"], "alexa-modecontroller.html"],
["Alexa.MotionSensor", "3", [], "alexa-motionsensor.html"],
["Alexa.PercentageController", "3", [], "alexa-percentagecontroller.html"],
["Alexa.PlaybackController", "3", [], "alexa-playbackcontroller.html"],
["Alexa.PlaybackStateReporter", "1", [], "alexa-playbackcontroller.html"],
["Alexa.PowerLevelController", "3", [], "alexa-powerlevelcontroller.html"],
["Alexa.ProactiveNotificationSource", "1", [], "alexa-proactivenotificationsource.html"],
["Alexa.RTCSessionController", "1", [], "alexa-rtcsessioncontroller.html"],
["Alexa.RangeController", "3", [], "alexa-rangecontroller.html"],
["Alexa.RecordController", "3", [], "alexa-recordcontroller.html"],
["Alexa.RemoteVideoPlayer", "1", [], "alexa-remotevideoplayer.html"],
["Alexa.SceneController", "3", [], "alexa-scenecontroller.html"],
["Alexa.SecurityPanelController", "1", [], "alexa-securitypanelcontroller.html"],
["Alexa.SecurityPanelController.Alert", "1", [], "alexa-securitypanelcontroller-alert.html"],
["Alexa.SeekController", "3", [], "alexa-seekcontroller.html"],
["Alexa.SimpleEventSource", "1", [], "alexa-simpleeventsource.html"],
["Alexa.SmartVision.ObjectDetectionSensor", "1", [], "alexa-smartvision-objectdetectionsensor.html"],
["Alexa.SmartVision.SnapshotProvider", "1", [], "alexa-smartvision-snapshotprovider.html"],
["Alexa.Speaker", "1", [], "alexa-speaker.html"],
["Alexa.StepSpeaker", "1", [], "alexa-stepspeaker.html"],
["Alexa.ThermostatController", "3.2", ["targetSetpoint", "lowerSetpoint", "upperSetpoint", "thermostatMode"], "alexa-thermostatcontroller.html"],
["Alexa.ThermostatController.Configuration", "1", [], "alexa-thermostatcontroller-configuration.html"],
["Alexa.ThermostatController.HVAC.Components", "1", [], "alexa-thermostatcontroller-hvac-components.html"],
["Alexa.ThermostatController.Schedule", "1", [], "alexa-thermostatcontroller-schedule.html"],
// "UNKNOWN" in 1.5.2 as well; the page is titled "Interface 3"
["Alexa.TimeHoldController", "3", [], "alexa-timeholdcontroller.html"],
["Alexa.ToggleController", "3", ["toggleState"], "alexa-togglecontroller.html"],
["Alexa.UIController", "1", [], "alexa-uicontroller.html"],
["Alexa.UserPreference", "1", [], "alexa-userpreference.html"],
["Alexa.VideoRecorder", "1", [], "alexa-videorecorder.html"],
["Alexa.WakeOnLANController", "1", [], "alexa-wakeonlancontroller.html"],
];
function kindOf(namespace) {
if (namespace.endsWith("Sensor"))
return "sensor";
if (namespace.endsWith("EventSource"))
return "eventSource";
return "controller";
}
function stub([namespace, version, properties, page]) {
return {
namespace,
version,
doc: page.startsWith("https://") ? page : `${DEVICE_APIS}/${page}`,
kind: kindOf(namespace),
tier: 3,
instanced: false,
properties: Object.fromEntries(properties.map((name) => [name, { name, value: schema_js_1.s.unknown() }])),
directives: {},
};
}
exports.STUBS = TABLE.map(stub);

190
dist/cjs/registry/schema.js vendored Normal file
View file

@ -0,0 +1,190 @@
"use strict";
// Values checked at run time and typed at compile time. A descriptor states its property values, directive payloads
// and declaration options with these; parse() returns the value or throws a SchemaError that names where it went
// wrong. Written here rather than taken from a package: mqtt stays the only runtime dependency.
Object.defineProperty(exports, "__esModule", { value: true });
exports.s = exports.SchemaError = void 0;
/** A value that does not fit its schema: path is where ("payload.targetSetpoint.scale"), problem is what. */
class SchemaError extends Error {
constructor(path, problem) {
super(path ? `${path}: ${problem}` : problem);
this.path = path;
this.problem = problem;
this.name = "SchemaError";
}
}
exports.SchemaError = SchemaError;
// The input as an error message shows it: short, and quoted when it is text.
function shown(input) {
if (input === undefined)
return "nothing";
if (typeof input === "function")
return "a function";
const text = JSON.stringify(input) ?? String(input);
return text.length > 60 ? `${text.slice(0, 57)}...` : text;
}
function mismatch(path, expects, input) {
return new SchemaError(path, `expected ${expects}, got ${shown(input)}`);
}
const at = (path, key) => (path ? `${path}.${key}` : key);
function isRecord(input) {
return typeof input === "object" && input !== null && !Array.isArray(input);
}
function schema(expects, accepts) {
return {
expects,
parse(input, path = "") {
if (!accepts(input))
throw mismatch(path, expects, input);
return input;
},
};
}
function numberText({ min, max, gt, integer }) {
const kind = integer ? "an integer" : "a number";
if (min !== undefined && max !== undefined)
return `${kind} from ${min} to ${max}`;
if (min !== undefined)
return `${kind} of ${min} or more`;
if (max !== undefined)
return `${kind} of ${max} or less`;
if (gt !== undefined)
return `${kind} greater than ${gt}`;
return kind;
}
function number(rules = {}) {
const { min = -Infinity, max = Infinity, gt = -Infinity, integer = false } = rules;
return schema(numberText(rules), (input) => typeof input === "number" && Number.isFinite(input) && input >= min && input <= max && input > gt
&& (!integer || Number.isInteger(input)));
}
function string(rules = {}) {
const { min = 0, max = Infinity, pattern } = rules;
const length = max === Infinity ? (min > 0 ? ` of ${min} or more characters` : "") : ` of ${min} to ${max} characters`;
return schema(rules.expects ?? `a string${length}`, (input) => typeof input === "string" && input.length >= min && input.length <= max && (!pattern || pattern.test(input)));
}
function boolean() {
return schema("true or false", (input) => typeof input === "boolean");
}
function literal(value) {
return schema(JSON.stringify(value), (input) => input === value);
}
function enumeration(...values) {
return { ...schema(values.join(" | "), (input) => values.includes(input)), values };
}
function unknown() {
return schema("any value", () => true);
}
function optional(inner) {
return {
expects: inner.expects,
optional: true,
parse: (input, path = "") => (input === undefined ? undefined : inner.parse(input, path)),
};
}
function nullable(inner) {
const expects = `${inner.expects} or null`;
return {
expects,
parse(input, path = "") {
if (input === null)
return null;
try {
return inner.parse(input, path);
}
catch (err) {
// The value itself is of the wrong kind: null was a choice too. An error further in keeps its own path.
if (err instanceof SchemaError && err.path === path)
throw mismatch(path, expects, input);
throw err;
}
},
};
}
function array(item, rules = {}) {
const { min = 0, max = Infinity } = rules;
const count = max === Infinity ? (min > 0 ? ` with ${min} or more entries` : "") : ` with ${min} to ${max} entries`;
const expects = `a list${count}`;
return {
expects,
parse(input, path = "") {
if (!Array.isArray(input) || input.length < min || input.length > max)
throw mismatch(path, expects, input);
return input.map((entry, i) => item.parse(entry, `${path}[${i}]`));
},
};
}
/**
* An object with the keys of shape. Keys the shape does not name are kept as they are: a field Alexa adds to a
* directive reaches the handler. unknownKeys "reject" is for what a developer writes, where such a key is a typo.
*/
function object(shape, rules = {}) {
const known = Object.keys(shape);
const expects = known.length > 0 ? `an object with ${known.join(", ")}` : "an object";
return {
expects,
parse(input, path = "") {
if (!isRecord(input))
throw mismatch(path, expects, input);
const others = Object.keys(input).filter((key) => !known.includes(key));
if (others.length > 0 && rules.unknownKeys === "reject") {
throw new SchemaError(at(path, others[0]), `unknown key, the known ones are ${known.join(", ") || "none"}`);
}
const parsed = {};
for (const key of known) {
const value = shape[key].parse(input[key], at(path, key));
if (value !== undefined)
parsed[key] = value;
}
for (const key of others)
parsed[key] = input[key];
return parsed;
},
};
}
// alexa-property-schemas.html "Temperature", "Temperature scales"
function temperature() {
return object({ value: number(), scale: enumeration("CELSIUS", "FAHRENHEIT", "KELVIN") });
}
// alexa-property-schemas.html "DateTime": UTC, no offsets. The seconds are optional here because the TimeInterval
// examples on the same page leave them out ("2017-10-04T14:00Z").
const DATE_TIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?Z$/;
function dateTime() {
return schema("a UTC time like 2017-08-30T01:18:21Z", (input) => typeof input === "string" && DATE_TIME.test(input) && !Number.isNaN(Date.parse(input)));
}
// alexa-property-schemas.html "Duration": the time portion of ISO 8601, negative for a delta ("PT-30S")
const DURATION = /^PT(?=.)(-?\d+H)?(-?\d+M)?(-?\d+S)?$/;
function duration() {
return string({ pattern: DURATION, expects: "a duration like PT3M15S" });
}
// alexa-property-schemas.html "TimeInterval": "Specify one or two of the time interval fields. If you specify all
// three fields, an error occurs."
function timeInterval() {
const fields = object({ start: optional(dateTime()), end: optional(dateTime()), duration: optional(duration()) });
const expects = "a time interval with one or two of start, end, duration";
return {
expects,
parse(input, path = "") {
const interval = fields.parse(input, path);
const given = [interval.start, interval.end, interval.duration].filter((field) => field !== undefined).length;
if (given < 1 || given > 2)
throw mismatch(path, expects, input);
return interval;
},
};
}
exports.s = {
string,
number,
boolean,
literal,
enum: enumeration,
unknown,
optional,
nullable,
array,
object,
temperature,
dateTime,
duration,
timeInterval,
};

25
dist/cjs/registry/types.js vendored Normal file
View file

@ -0,0 +1,25 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DeclarationError = void 0;
exports.defineInterface = defineInterface;
/** Types a descriptor from what it is given: the property names, the payload of each directive, the options. */
function defineInterface(descriptor) {
return descriptor;
}
/** A device, a capability or an interface name declared in a way Alexa would reject. Thrown where it is declared. */
class DeclarationError extends Error {
constructor(where, problem) {
const capability = where.namespace && where.instance ? `${where.namespace} "${where.instance}"` : where.namespace;
super([where.endpointId && shortened(where.endpointId), capability, problem].filter(Boolean).join(": "));
this.name = "DeclarationError";
this.endpointId = where.endpointId;
this.namespace = where.namespace;
this.instance = where.instance || undefined;
this.problem = problem;
}
}
exports.DeclarationError = DeclarationError;
// An endpointId may be the very thing that is wrong, 300 characters of it.
function shortened(text) {
return text.length > 64 ? `${text.slice(0, 61)}...` : text;
}