"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SemanticsBuilder = void 0; exports.semantics = semantics; exports.semanticsOf = semanticsOf; // Semantics of a generic controller: the phrases "open", "close", "raise", "lower" mapped to directives, and states // like "open" and "closed" mapped to values of the property (alexa-discovery-objects.html, "Semantics object"). const catalog_js_1 = require("./catalog.js"); const schema_js_1 = require("./schema.js"); const listed = (given) => (Array.isArray(given) ? [...given] : [given]); const withPrefix = (prefix, name) => (name.startsWith(prefix) ? name : `${prefix}${name}`); /** * Collects the mappings of one capability: * * semantics() * .action("Close", "SetRangeValue", { rangeValue: 0 }) * .action(["Open", "Raise"], "SetRangeValue", { rangeValue: 100 }) * .state("Closed", 0) * .stateRange("Open", 1, 100) * * What is collected is checked when the capability is declared, against the directives and the property of its * interface. */ class SemanticsBuilder { constructor() { this.actionMappings = []; this.stateMappings = []; } /** The phrases send the directive. payload is its payload; leave it out for a directive that has none. */ action(actions, directive, payload) { this.actionMappings.push({ "@type": "ActionsToDirective", actions: listed(actions).map((name) => withPrefix("Alexa.Actions.", name)), directive: payload === undefined ? { name: directive } : { name: directive, payload }, }); return this; } /** The property at this value is in these states: "is the garage door open?" */ state(states, value) { this.stateMappings.push({ "@type": "StatesToValue", states: listed(states).map((name) => withPrefix("Alexa.States.", name)), value }); return this; } /** The property between the two values, both included, is in these states. For an interface with a numeric property. */ stateRange(states, minimumValue, maximumValue) { this.stateMappings.push({ "@type": "StatesToRange", states: listed(states).map((name) => withPrefix("Alexa.States.", name)), range: { minimumValue, maximumValue }, }); return this; } } exports.SemanticsBuilder = SemanticsBuilder; function semantics() { return new SemanticsBuilder(); } const isHunch = (id) => /\.(SetEcoOn|SetEcoOff|EcoOn|EcoOff)$/.test(id); /** The schema of the semantics option of one interface. It takes a semantics() builder or the object as discovery has it. */ function semanticsOf({ directives, stateValue, ranges = false, hunches = false }) { const allowed = (ids, what) => { const choice = ids.filter((id) => hunches || !isHunch(id)); return schema_js_1.s.array(schema_js_1.s.oneOf(choice, `${what}: ${choice.map((id) => id.split(".")[2]).join(", ")}`), { min: 1 }); }; const names = Object.keys(directives); const lists = schema_js_1.s.object({ actionMappings: schema_js_1.s.optional(schema_js_1.s.array(schema_js_1.s.unknown())), stateMappings: schema_js_1.s.optional(schema_js_1.s.array(schema_js_1.s.unknown())) }); const action = schema_js_1.s.object({ "@type": schema_js_1.s.literal("ActionsToDirective"), actions: allowed(catalog_js_1.ACTIONS, "a phrase of Alexa.Actions"), directive: schema_js_1.s.object({ name: schema_js_1.s.oneOf(names, `a directive of the interface: ${names.join(", ")}`) }), }); const state = schema_js_1.s.object({ "@type": ranges ? schema_js_1.s.enum("StatesToValue", "StatesToRange") : schema_js_1.s.enum("StatesToValue"), states: allowed(catalog_js_1.STATES, "a state of Alexa.States"), }); const range = schema_js_1.s.object({ minimumValue: schema_js_1.s.number(), maximumValue: schema_js_1.s.number() }, { unknownKeys: "reject" }); function actionMapping(input, path) { const mapping = action.parse(input, path); const { name, payload } = mapping.directive; // Checked as the directive will arrive; a mapping without a payload stays without one in discovery const checked = directives[name].payload.parse(payload ?? {}, (0, schema_js_1.at)(path, "directive.payload")); return { "@type": mapping["@type"], actions: mapping.actions, directive: payload === undefined ? { name } : { name, payload: checked } }; } function stateMapping(input, path) { const mapping = state.parse(input, path); if (mapping["@type"] === "StatesToValue") { return { "@type": mapping["@type"], states: mapping.states, value: stateValue.parse(mapping.value, (0, schema_js_1.at)(path, "value")) }; } const { minimumValue, maximumValue } = range.parse(mapping.range, (0, schema_js_1.at)(path, "range")); if (minimumValue > maximumValue) throw new schema_js_1.SchemaError((0, schema_js_1.at)(path, "range"), `the minimumValue ${minimumValue} is above the maximumValue ${maximumValue}`); return { "@type": mapping["@type"], states: mapping.states, range: { minimumValue, maximumValue } }; } const expects = "semantics() with at least one action or state"; return { expects, parse(input, path = "") { if (typeof input !== "object" || input === null) throw (0, schema_js_1.mismatch)(path, expects, input); const { actionMappings = [], stateMappings = [] } = lists.parse(input, path); if (actionMappings.length + stateMappings.length === 0) throw (0, schema_js_1.mismatch)(path, expects, {}); const parsed = {}; if (actionMappings.length > 0) { parsed.actionMappings = actionMappings.map((mapping, i) => actionMapping(mapping, `${(0, schema_js_1.at)(path, "actionMappings")}[${i}]`)); } if (stateMappings.length > 0) { parsed.stateMappings = stateMappings.map((mapping, i) => stateMapping(mapping, `${(0, schema_js_1.at)(path, "stateMappings")}[${i}]`)); } return parsed; }, }; }