@alteriom/mqtt-schema 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +174 -0
  2. package/dist/cjs/generated/types.d.ts +101 -0
  3. package/dist/cjs/generated/types.js +104 -0
  4. package/dist/cjs/index.d.ts +2 -0
  5. package/dist/cjs/index.js +23 -0
  6. package/dist/cjs/schema_data.d.ts +319 -0
  7. package/dist/cjs/schema_data.js +402 -0
  8. package/dist/cjs/schemas/control_response.schema.json +15 -0
  9. package/dist/cjs/schemas/envelope.schema.json +16 -0
  10. package/dist/cjs/schemas/firmware_status.schema.json +17 -0
  11. package/dist/cjs/schemas/gateway_info.schema.json +21 -0
  12. package/dist/cjs/schemas/gateway_metrics.schema.json +26 -0
  13. package/dist/cjs/schemas/mqtt_v1_bundle.json +14 -0
  14. package/dist/cjs/schemas/sensor_data.schema.json +33 -0
  15. package/dist/cjs/schemas/sensor_heartbeat.schema.json +14 -0
  16. package/dist/cjs/schemas/sensor_status.schema.json +14 -0
  17. package/dist/cjs/schemas/validation_rules.md +44 -0
  18. package/dist/cjs/types.d.ts +1 -0
  19. package/dist/cjs/types.js +23 -0
  20. package/dist/cjs/validators.d.ts +23 -0
  21. package/dist/cjs/validators.js +84 -0
  22. package/dist/esm/generated/types.js +92 -0
  23. package/dist/esm/index.js +7 -0
  24. package/dist/esm/package.json +3 -0
  25. package/dist/esm/schema_data.js +399 -0
  26. package/dist/esm/schemas/control_response.schema.json +15 -0
  27. package/dist/esm/schemas/envelope.schema.json +16 -0
  28. package/dist/esm/schemas/firmware_status.schema.json +17 -0
  29. package/dist/esm/schemas/gateway_info.schema.json +21 -0
  30. package/dist/esm/schemas/gateway_metrics.schema.json +26 -0
  31. package/dist/esm/schemas/mqtt_v1_bundle.json +14 -0
  32. package/dist/esm/schemas/sensor_data.schema.json +33 -0
  33. package/dist/esm/schemas/sensor_heartbeat.schema.json +14 -0
  34. package/dist/esm/schemas/sensor_status.schema.json +14 -0
  35. package/dist/esm/schemas/validation_rules.md +44 -0
  36. package/dist/esm/types.js +7 -0
  37. package/dist/esm/validators.js +76 -0
  38. package/package.json +91 -0
package/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # @alteriom/mqtt-schema
2
+
3
+ Alteriom MQTT v1 JSON Schemas, TypeScript types, and production‑ready validation helpers for integrating firmware MQTT payloads into web or backend services.
4
+
5
+ ## Why this exists
6
+ Firmware emits structured MQTT payloads that must remain tightly aligned with web, analytics, and gateway logic. This package is the single source of truth for:
7
+
8
+ - Canonical, versioned JSON Schemas (`schema_version: 1` series)
9
+ - Embedded (tree‑shakeable) schema objects — no runtime file I/O
10
+ - Precompiled Ajv validators (fast + consistent across CJS / ESM)
11
+ - TypeScript interfaces & type guards generated from the same source schema set
12
+ - Message classification helper that infers the schema kind heuristically
13
+ - Forward‑compatible design: unknown extra properties are ignored unless explicitly constrained
14
+
15
+ ## Features
16
+
17
+ - Dual build: CommonJS + ESM (Node 16+ / bundlers)
18
+ - Zero configuration: just import and validate
19
+ - Helpful error paths (JSON Pointer style)
20
+ - Lightweight (Ajv peer dependency, schemas embedded)
21
+ - Ships original schema JSON files (optional consumption)
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install @alteriom/mqtt-schema ajv ajv-formats
27
+ ```
28
+
29
+ ## Quick Start
30
+
31
+ Validate a JSON payload (object already parsed):
32
+
33
+ ```ts
34
+ import { validators } from '@alteriom/mqtt-schema';
35
+
36
+ const payload = JSON.parse(rawMqttString);
37
+ const result = validators.sensorData(payload);
38
+ if (!result.valid) {
39
+ console.error('Invalid sensor data', result.errors);
40
+ }
41
+ ```
42
+
43
+ Classify and validate automatically:
44
+
45
+ ```ts
46
+ import { classifyAndValidate } from '@alteriom/mqtt-schema';
47
+
48
+ const { kind, result } = classifyAndValidate(payload);
49
+ if (result.valid) {
50
+ console.log('Message kind:', kind);
51
+ } else {
52
+ console.warn('Validation errors', result.errors);
53
+ }
54
+ ```
55
+
56
+ Use strong TypeScript types after classification:
57
+
58
+ ```ts
59
+ import { classifyAndValidate, isSensorDataMessage, SensorDataMessage } from '@alteriom/mqtt-schema';
60
+
61
+ const { result } = classifyAndValidate(payload);
62
+ if (result.valid && isSensorDataMessage(payload)) {
63
+ const data: SensorDataMessage = payload;
64
+ Object.entries(data.sensors).forEach(([name, s]) => {
65
+ console.log(name, s.value, s.unit);
66
+ });
67
+ }
68
+ ```
69
+
70
+ Access raw schema JSON (if you need to introspect or power form generation):
71
+
72
+ ```ts
73
+ import envelopeSchema from '@alteriom/mqtt-schema/schemas/envelope.schema.json';
74
+ ```
75
+
76
+ ## API Surface
77
+
78
+ ```ts
79
+ import { validators, validateMessage, classifyAndValidate } from '@alteriom/mqtt-schema';
80
+
81
+ // 1. Direct validator by message kind
82
+ validators.sensorData(payload); // => { valid: boolean; errors?: string[] }
83
+
84
+ // 2. Generic function
85
+ validateMessage('sensorData', payload);
86
+
87
+ // 3. Classify unknown payload then validate
88
+ const { kind, result } = classifyAndValidate(payload);
89
+
90
+ // 4. Type guards (when available)
91
+ // if (isSensorDataMessage(payload)) { ... }
92
+ ```
93
+
94
+ Validation result format:
95
+
96
+ ```ts
97
+ interface ValidationResult {
98
+ valid: boolean;
99
+ errors?: string[]; // Each item contains instancePath + message
100
+ }
101
+ ```
102
+
103
+ ### Performance Notes
104
+ All Ajv validator functions are compiled once at module load. For typical web usage (tens to hundreds of validations per page/session) this is faster and simpler than on‑demand compilation. If you need custom Ajv options (e.g., different formats), open an issue—an override hook can be added without breaking changes.
105
+
106
+ ### Embedded Schemas
107
+ `schema_data.ts` is auto‑generated during build. This avoids dynamic `require()` / `import` of JSON and works cleanly in both Node ESM and bundlers without JSON import assertions. The original JSON files are still published under `schemas/` for tooling or documentation pipelines.
108
+
109
+ ## Provided Schemas (v1)
110
+
111
+ | File | Purpose |
112
+ |------|---------|
113
+ | envelope.schema.json | Base required envelope fields |
114
+ | sensor_data.schema.json | Telemetry payload with sensors map |
115
+ | sensor_heartbeat.schema.json | Lightweight heartbeat (firmware_version may be omitted) |
116
+ | sensor_status.schema.json | Sensor status / presence updates |
117
+ | gateway_info.schema.json | Gateway identity & capabilities |
118
+ | gateway_metrics.schema.json | Gateway performance metrics |
119
+ | firmware_status.schema.json | Firmware update lifecycle events |
120
+ | control_response.schema.json | Command/control response messages |
121
+
122
+ ## Exports
123
+
124
+ | Export | Type | Description |
125
+ |--------|------|-------------|
126
+ | `validators` | object | Precompiled validators per message type |
127
+ | `validateMessage(kind,data)` | fn | Run a specific validator by key |
128
+ | `classifyAndValidate(data)` | fn | Heuristic classification + validation |
129
+ | `SensorDataMessage` etc. | TS interfaces | Strongly typed shapes |
130
+ | `isSensorDataMessage` etc. | type guards | Runtime narrowing helpers |
131
+ | `schemas/*.json` | JSON | Original schema assets (optional) |
132
+
133
+ ### Validator Keys
134
+ `sensorData`, `sensorHeartbeat`, `sensorStatus`, `gatewayInfo`, `gatewayMetrics`, `firmwareStatus`, `controlResponse`
135
+
136
+ ### Classification Heuristics (Simplified)
137
+ - `metrics` → `gatewayMetrics`
138
+ - `sensors` → `sensorData`
139
+ - `progress_pct` or OTA status keywords → `firmwareStatus`
140
+ - `status` + `device_type: sensor` → `sensorStatus`
141
+ - `status: ok|error` (no other match) → `controlResponse`
142
+ - `device_type: gateway` → `gatewayInfo`
143
+ - fallback → `sensorHeartbeat`
144
+
145
+ ## Versioning Policy
146
+
147
+ Schema stability is paramount. We track two related versions:
148
+
149
+ | Concept | Meaning |
150
+ |---------|---------|
151
+ | `schema_version` (in payload) | Wire format major. Only increments for breaking changes. |
152
+ | npm package version | Follows semver; patch/minor may add optional properties or tooling, major aligns with `schema_version` bump. |
153
+
154
+ Backward‑compatible additions: new optional properties or enums, documented in CHANGELOG. Breaking: new required fields, structural changes, or removed properties (triggers parallel `v2` schema path & coordinated firmware rollout).
155
+
156
+ ## TypeScript / Bundler Notes
157
+ - Works in TS >= 5, Node >= 16, Vite / Webpack / ESBuild.
158
+ - Tree shaking: unused validators pruned when using ESM builds.
159
+ - No side effects besides Ajv instance creation.
160
+
161
+ ## Roadmap
162
+ - Optional custom Ajv injection hook
163
+ - JSON Schema → Zod conversion example
164
+ - Runtime metrics helper (count validation categories)
165
+
166
+ ## Contributing
167
+ Issues & PRs welcome. Ensure firmware repo schemas remain the authoritative source—do not manually edit generated `schema_data.ts`.
168
+
169
+ ## Security
170
+ Schemas are static. No network access. Supply-chain risk minimized by keeping dependencies minimal (Ajv + formats only).
171
+
172
+ ## License
173
+
174
+ MIT
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Auto-generated TypeScript types for Alteriom MQTT Schema v1
3
+ * Source: docs/mqtt_schema/*.schema.json
4
+ * Generation Date: 2025-09-20
5
+ * NOTE: This file is maintained in firmware repo for UI alignment. Changes require coordinated review.
6
+ */
7
+ export interface BaseEnvelope {
8
+ schema_version: 1;
9
+ device_id: string;
10
+ device_type: 'sensor' | 'gateway';
11
+ timestamp: string;
12
+ firmware_version?: string;
13
+ hardware_version?: string;
14
+ [k: string]: unknown;
15
+ }
16
+ export interface SensorEntry {
17
+ value: number;
18
+ unit?: string;
19
+ raw_value?: number;
20
+ calibrated_value?: number;
21
+ quality_score?: number;
22
+ name?: string;
23
+ location?: string;
24
+ additional_data?: Record<string, unknown>;
25
+ [k: string]: unknown;
26
+ }
27
+ export interface SensorDataMessage extends BaseEnvelope {
28
+ device_type: 'sensor';
29
+ firmware_version: string;
30
+ sensors: Record<string, SensorEntry>;
31
+ battery_level?: number;
32
+ signal_strength?: number;
33
+ additional?: Record<string, unknown>;
34
+ }
35
+ export interface SensorHeartbeatMessage extends Omit<BaseEnvelope, 'firmware_version'> {
36
+ device_type: 'sensor' | 'gateway';
37
+ firmware_version?: string;
38
+ }
39
+ export interface SensorStatusMessage extends BaseEnvelope {
40
+ device_type: 'sensor';
41
+ firmware_version: string;
42
+ status: 'online' | 'offline' | 'updating' | 'error';
43
+ battery_level?: number;
44
+ signal_strength?: number;
45
+ }
46
+ export interface GatewayInfoMessage extends BaseEnvelope {
47
+ device_type: 'gateway';
48
+ firmware_version: string;
49
+ mac_address?: string;
50
+ ip_address?: string;
51
+ capabilities?: {
52
+ max_nodes?: number;
53
+ supports_mesh?: boolean;
54
+ firmware_update?: boolean;
55
+ [k: string]: unknown;
56
+ };
57
+ }
58
+ export interface GatewayMetricsMessage extends BaseEnvelope {
59
+ device_type: 'gateway';
60
+ firmware_version: string;
61
+ metrics: {
62
+ uptime_s: number;
63
+ cpu_usage_pct?: number;
64
+ memory_usage_pct?: number;
65
+ temperature_c?: number;
66
+ connected_devices?: number;
67
+ mesh_nodes?: number;
68
+ packet_loss_pct?: number;
69
+ data_throughput_kbps?: number;
70
+ [k: string]: unknown;
71
+ };
72
+ }
73
+ export interface FirmwareStatusMessage extends BaseEnvelope {
74
+ status: 'pending' | 'downloading' | 'flashing' | 'verifying' | 'rebooting' | 'completed' | 'failed';
75
+ event?: string;
76
+ from_version?: string;
77
+ to_version?: string;
78
+ progress_pct?: number;
79
+ error?: string | null;
80
+ }
81
+ export interface ControlResponseMessage extends BaseEnvelope {
82
+ status: 'ok' | 'error';
83
+ command?: string;
84
+ message?: string;
85
+ result?: unknown;
86
+ }
87
+ export type AnyMqttV1Message = SensorDataMessage | SensorHeartbeatMessage | SensorStatusMessage | GatewayInfoMessage | GatewayMetricsMessage | FirmwareStatusMessage | ControlResponseMessage;
88
+ export declare function isSensorDataMessage(msg: any): msg is SensorDataMessage;
89
+ export declare function isSensorHeartbeatMessage(msg: any): msg is SensorHeartbeatMessage;
90
+ export declare function isSensorStatusMessage(msg: any): msg is SensorStatusMessage;
91
+ export declare function isGatewayInfoMessage(msg: any): msg is GatewayInfoMessage;
92
+ export declare function isGatewayMetricsMessage(msg: any): msg is GatewayMetricsMessage;
93
+ export declare function isFirmwareStatusMessage(msg: any): msg is FirmwareStatusMessage;
94
+ export declare function isControlResponseMessage(msg: any): msg is ControlResponseMessage;
95
+ export declare function classifyMessage(msg: any): AnyMqttV1Message | null;
96
+ export interface BasicValidationIssue {
97
+ field?: string;
98
+ reason: string;
99
+ }
100
+ export declare function basicValidate(msg: AnyMqttV1Message): BasicValidationIssue[];
101
+ export declare function parseMessage(json: string): AnyMqttV1Message | null;
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ /**
3
+ * Auto-generated TypeScript types for Alteriom MQTT Schema v1
4
+ * Source: docs/mqtt_schema/*.schema.json
5
+ * Generation Date: 2025-09-20
6
+ * NOTE: This file is maintained in firmware repo for UI alignment. Changes require coordinated review.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.isSensorDataMessage = isSensorDataMessage;
10
+ exports.isSensorHeartbeatMessage = isSensorHeartbeatMessage;
11
+ exports.isSensorStatusMessage = isSensorStatusMessage;
12
+ exports.isGatewayInfoMessage = isGatewayInfoMessage;
13
+ exports.isGatewayMetricsMessage = isGatewayMetricsMessage;
14
+ exports.isFirmwareStatusMessage = isFirmwareStatusMessage;
15
+ exports.isControlResponseMessage = isControlResponseMessage;
16
+ exports.classifyMessage = classifyMessage;
17
+ exports.basicValidate = basicValidate;
18
+ exports.parseMessage = parseMessage;
19
+ // Type Guards ------------------------------------------------
20
+ function isSensorDataMessage(msg) {
21
+ return msg && msg.schema_version === 1 && msg.device_type === 'sensor' && typeof msg.sensors === 'object';
22
+ }
23
+ function isSensorHeartbeatMessage(msg) {
24
+ return msg && msg.schema_version === 1 && !!msg.device_type && !!msg.timestamp && !('sensors' in msg) && !('metrics' in msg) && !('status' in msg || (msg.status && ['online', 'offline', 'updating', 'error'].includes(msg.status)));
25
+ }
26
+ function isSensorStatusMessage(msg) {
27
+ return msg && msg.schema_version === 1 && msg.device_type === 'sensor' && typeof msg.status === 'string' && ['online', 'offline', 'updating', 'error'].includes(msg.status);
28
+ }
29
+ function isGatewayInfoMessage(msg) {
30
+ return msg && msg.schema_version === 1 && msg.device_type === 'gateway' && (!msg.metrics) && (!msg.status) && (!msg.progress_pct);
31
+ }
32
+ function isGatewayMetricsMessage(msg) {
33
+ return msg && msg.schema_version === 1 && msg.device_type === 'gateway' && typeof msg.metrics === 'object';
34
+ }
35
+ function isFirmwareStatusMessage(msg) {
36
+ return msg && msg.schema_version === 1 && typeof msg.status === 'string' && ['pending', 'downloading', 'flashing', 'verifying', 'rebooting', 'completed', 'failed'].includes(msg.status) && (msg.progress_pct === undefined || (typeof msg.progress_pct === 'number' && msg.progress_pct >= 0 && msg.progress_pct <= 100));
37
+ }
38
+ function isControlResponseMessage(msg) {
39
+ return msg && msg.schema_version === 1 && (msg.status === 'ok' || msg.status === 'error') && 'timestamp' in msg;
40
+ }
41
+ function classifyMessage(msg) {
42
+ if (isSensorDataMessage(msg))
43
+ return msg;
44
+ if (isGatewayMetricsMessage(msg))
45
+ return msg;
46
+ if (isSensorStatusMessage(msg))
47
+ return msg;
48
+ if (isGatewayInfoMessage(msg))
49
+ return msg;
50
+ if (isFirmwareStatusMessage(msg))
51
+ return msg;
52
+ if (isControlResponseMessage(msg))
53
+ return msg;
54
+ if (isSensorHeartbeatMessage(msg))
55
+ return msg;
56
+ return null;
57
+ }
58
+ function basicValidate(msg) {
59
+ const issues = [];
60
+ if (msg.schema_version !== 1)
61
+ issues.push({ reason: 'unsupported_schema' });
62
+ if (!msg.device_id)
63
+ issues.push({ field: 'device_id', reason: 'missing' });
64
+ if (!msg.device_type)
65
+ issues.push({ field: 'device_type', reason: 'missing' });
66
+ if (!msg.timestamp)
67
+ issues.push({ field: 'timestamp', reason: 'missing' });
68
+ if ('firmware_version' in msg) {
69
+ if (msg.firmware_version === '')
70
+ issues.push({ field: 'firmware_version', reason: 'empty' });
71
+ }
72
+ else if (!isSensorHeartbeatMessage(msg)) {
73
+ issues.push({ field: 'firmware_version', reason: 'missing' });
74
+ }
75
+ if (isSensorDataMessage(msg)) {
76
+ if (!Object.keys(msg.sensors).length)
77
+ issues.push({ field: 'sensors', reason: 'empty' });
78
+ for (const [k, v] of Object.entries(msg.sensors)) {
79
+ if (typeof v.value !== 'number')
80
+ issues.push({ field: `sensors.${k}.value`, reason: 'invalid' });
81
+ if (v.quality_score !== undefined && (v.quality_score < 0 || v.quality_score > 1))
82
+ issues.push({ field: `sensors.${k}.quality_score`, reason: 'out_of_range' });
83
+ }
84
+ }
85
+ if (isGatewayMetricsMessage(msg)) {
86
+ if (typeof msg.metrics.uptime_s !== 'number')
87
+ issues.push({ field: 'metrics.uptime_s', reason: 'missing' });
88
+ }
89
+ if (isFirmwareStatusMessage(msg)) {
90
+ if (msg.progress_pct !== undefined && (msg.progress_pct < 0 || msg.progress_pct > 100))
91
+ issues.push({ field: 'progress_pct', reason: 'out_of_range' });
92
+ }
93
+ return issues;
94
+ }
95
+ // Example parse wrapper
96
+ function parseMessage(json) {
97
+ try {
98
+ const obj = JSON.parse(json);
99
+ return classifyMessage(obj);
100
+ }
101
+ catch {
102
+ return null;
103
+ }
104
+ }
@@ -0,0 +1,2 @@
1
+ export * from './validators.js';
2
+ export * from './types.js';
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ // Public export surface for @alteriom/mqtt-schema
3
+ // NOTE: This initial iteration re-exports pre-generated types produced in the firmware repo.
4
+ // In future, generation could move into this package build, but we intentionally depend on
5
+ // the firmware repo as the single source of truth to avoid divergence.
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
18
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
19
+ };
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ // Use explicit .js extensions so ESM build (with dist/esm/package.json type=module) treats them as ESM.
22
+ __exportStar(require("./validators.js"), exports);
23
+ __exportStar(require("./types.js"), exports);