"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, };