@alteriom/mqtt-schema 0.2.1 → 0.3.1

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # Changelog (Moved)
2
+
3
+ The canonical changelog is maintained at:
4
+
5
+ `docs/mqtt_schema/CHANGELOG.md`
6
+
7
+ This root file is intentionally minimal to avoid duplication and ensure release integrity scripts target a single source of truth.
package/README.md CHANGED
@@ -1,7 +1,21 @@
1
1
  # @alteriom/mqtt-schema
2
2
 
3
+ ![Metadata Compliance](https://github.com/Alteriom/alteriom-mqtt-schema/actions/workflows/metadata-compliance.yml/badge.svg)
4
+ ![OTA Manifest Validation](https://github.com/Alteriom/alteriom-mqtt-schema/actions/workflows/validate-ota-manifest.yml/badge.svg)
5
+ ![Schema Verify](https://github.com/Alteriom/alteriom-mqtt-schema/actions/workflows/schema-verify.yml/badge.svg)
6
+ ![npm version](https://img.shields.io/npm/v/@alteriom/mqtt-schema.svg)
7
+ ![npm downloads](https://img.shields.io/npm/dm/@alteriom/mqtt-schema.svg)
8
+ ![license](https://img.shields.io/npm/l/@alteriom/mqtt-schema.svg)
9
+ ![npm total downloads](https://img.shields.io/npm/dt/@alteriom/mqtt-schema.svg)
10
+ ![node version](https://img.shields.io/node/v/@alteriom/mqtt-schema.svg)
11
+ ![peer ajv](https://img.shields.io/badge/peer%20ajv-%3E%3D8.0.0-blue.svg)
12
+ ![latest tag](https://img.shields.io/github/v/tag/Alteriom/alteriom-mqtt-schema?label=tag)
13
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/@alteriom/mqtt-schema)](https://bundlephobia.com/package/@alteriom/mqtt-schema)
14
+
15
+
3
16
  Alteriom MQTT v1 JSON Schemas, TypeScript types, and production‑ready validation helpers for integrating firmware MQTT payloads into web or backend services.
4
17
 
18
+
5
19
  ## Why this exists
6
20
  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
21
 
@@ -26,6 +40,8 @@ Firmware emits structured MQTT payloads that must remain tightly aligned with we
26
40
  npm install @alteriom/mqtt-schema ajv ajv-formats
27
41
  ```
28
42
 
43
+ **Support & Compatibility**: Node 18+ tested; Node 20 primary. Dual CJS/ESM builds.
44
+
29
45
  ## Quick Start
30
46
 
31
47
  Validate a JSON payload (object already parsed):
@@ -73,6 +89,43 @@ Access raw schema JSON (if you need to introspect or power form generation):
73
89
  import envelopeSchema from '@alteriom/mqtt-schema/schemas/envelope.schema.json';
74
90
  ```
75
91
 
92
+ ## OTA Firmware Manifest Schema (v0.3.1+)
93
+
94
+ The package includes OTA firmware manifest schema with both rich and minimal formats.
95
+
96
+ **Preferred: Stable alias import** (v0.3.1+):
97
+ ```ts
98
+ import otaManifestSchema from '@alteriom/mqtt-schema/ota-manifest';
99
+ import { OtaManifest, isRichManifest } from '@alteriom/mqtt-schema/types/ota-manifest';
100
+ ```
101
+
102
+ **Legacy: Deep path import** (still supported):
103
+ ```ts
104
+ import otaManifestSchema from '@alteriom/mqtt-schema/schemas/ota/ota-manifest.schema.json';
105
+ ```
106
+
107
+ **Usage example:**
108
+ ```ts
109
+ import Ajv from 'ajv';
110
+ import addFormats from 'ajv-formats';
111
+ import otaManifestSchema from '@alteriom/mqtt-schema/ota-manifest';
112
+ import { OtaManifest } from '@alteriom/mqtt-schema/types/ota-manifest';
113
+
114
+ const ajv = new Ajv({ allErrors: true, strict: false });
115
+ addFormats(ajv);
116
+ const validate = ajv.compile<OtaManifest>(otaManifestSchema as any);
117
+
118
+ const manifest: OtaManifest = JSON.parse(manifestJson);
119
+ if (!validate(manifest)) {
120
+ console.error('Invalid OTA manifest:', validate.errors);
121
+ }
122
+ ```
123
+
124
+ Supported formats:
125
+ - **Rich manifest**: environment + branch + manifests object
126
+ - **Minimal environment map**: environment → channels mapping
127
+ - **Chunk variants**: structured objects or SHA256 array
128
+
76
129
  ## API Surface
77
130
 
78
131
  ```ts
@@ -101,9 +154,11 @@ interface ValidationResult {
101
154
  ```
102
155
 
103
156
  ### Performance Notes
157
+
104
158
  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
159
 
106
160
  ### Embedded Schemas
161
+
107
162
  `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
163
 
109
164
  ## Provided Schemas (v1)
@@ -131,6 +186,7 @@ All Ajv validator functions are compiled once at module load. For typical web us
131
186
  | `schemas/*.json` | JSON | Original schema assets (optional) |
132
187
 
133
188
  ### Validator Keys
189
+
134
190
  `sensorData`, `sensorHeartbeat`, `sensorStatus`, `gatewayInfo`, `gatewayMetrics`, `firmwareStatus`, `controlResponse`
135
191
 
136
192
  ### Classification Heuristics (Simplified)
@@ -170,6 +226,8 @@ Backward‑compatible additions: new optional properties or enums, documented in
170
226
 
171
227
  Issues & PRs welcome. Ensure firmware repo schemas remain the authoritative source—do not manually edit generated `schema_data.ts`.
172
228
 
229
+ **Before opening a PR:** Run `npm run verify` to validate schemas, changelog, and tests.
230
+
173
231
  ## Security
174
232
 
175
233
  Schemas are static. No network access. Supply-chain risk minimized by keeping dependencies minimal (Ajv + formats only).
@@ -189,14 +247,14 @@ This package is published to BOTH:
189
247
 
190
248
  Create or update an `.npmrc` with a scoped registry override (auth token with `read:packages` required):
191
249
 
192
- ```
250
+ ```bash
193
251
  @alteriom:registry=https://npm.pkg.github.com
194
252
  //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
195
253
  ```
196
254
 
197
255
  Then install normally:
198
256
 
199
- ```
257
+ ```bash
200
258
  npm install @alteriom/mqtt-schema ajv ajv-formats
201
259
  ```
202
260
 
@@ -226,3 +284,58 @@ npm view @alteriom/mqtt-schema version --registry=https://npm.pkg.github.com
226
284
  ```
227
285
 
228
286
  Both should return the same version.
287
+
288
+ ## Repository Metadata Compliance
289
+
290
+ This repository integrates the `@alteriom/repository-metadata-manager` tooling to continuously validate and report on repository metadata health (description, topics, documentation signals, etc.) within the Alteriom organization standards.
291
+
292
+ ### Local Usage
293
+
294
+ Run a validation (non‑destructive):
295
+
296
+ ```bash
297
+ npm run metadata:validate
298
+ ```
299
+
300
+ Generate a detailed report:
301
+
302
+ ```bash
303
+ npm run metadata:report
304
+ ```
305
+
306
+ Configuration lives in `metadata-config.json` (organizationTag `alteriom`). Default detection is auto; adjust if repository classification needs overriding.
307
+
308
+ ### CI Workflow
309
+
310
+ Workflow file: `.github/workflows/metadata-compliance.yml`
311
+
312
+ On each push / PR against `main` it will:
313
+
314
+ 1. Install dependencies
315
+ 2. Run `metadata:validate` (fails job on hard non‑compliance)
316
+ 3. Always attempt a best‑effort report (`metadata:report`) for visibility
317
+
318
+ ### Tokens / Permissions
319
+
320
+ The workflow relies only on the default `GITHUB_TOKEN` for read operations. If future auto‑apply operations are desired, a token with elevated repo scope would be needed and the `metadata:apply` script could be wired (currently omitted to keep CI read‑only).
321
+
322
+ ### Extending
323
+
324
+ If you add new categories of tooling or documentation, re‑run the report to see updated recommendations. For cross‑repo analytics or policy generation, use the original project directly.
325
+
326
+ ### Applying Metadata (Manual Workflow)
327
+
328
+ For authorized maintainers you can run adjustments via GitHub Actions:
329
+
330
+ 1. Open the "Repository Metadata Apply" workflow under the Actions tab.
331
+ 2. Choose whether to keep `dryRun` (default) or set to `false` to apply.
332
+ 3. Run the workflow; the log will show proposed or applied changes.
333
+
334
+ Local dry‑run vs apply:
335
+
336
+ ```bash
337
+ npm run metadata:apply:dry # show what would change
338
+ npm run metadata:apply # apply changes (requires proper permissions via GITHUB_TOKEN)
339
+ ```
340
+
341
+ Note: Applying metadata modifies repository settings (description, topics) through the GitHub API; ensure the default token has the necessary repo scopes (in public repositories the workflow GITHUB_TOKEN normally suffices for these fields).
@@ -317,3 +317,184 @@ export declare const mqtt_v1_bundle_json: {
317
317
  readonly control_response: "control_response.schema.json";
318
318
  };
319
319
  };
320
+ export declare const ota_ota_manifest_schema: {
321
+ readonly $id: "https://schemas.alteriom.com/ota/ota-manifest.schema.json";
322
+ readonly title: "Alteriom OTA Firmware Manifest";
323
+ readonly description: "Schema for Alteriom OTA firmware manifests supporting both rich and minimal variants";
324
+ readonly type: "object";
325
+ readonly oneOf: readonly [{
326
+ readonly title: "Rich Manifest";
327
+ readonly description: "Rich manifest format with environment, branch, and manifests object";
328
+ readonly type: "object";
329
+ readonly properties: {
330
+ readonly environment: {
331
+ readonly type: "string";
332
+ readonly description: "Target environment (e.g., universal-sensor)";
333
+ };
334
+ readonly branch: {
335
+ readonly type: "string";
336
+ readonly description: "Source control branch the build originated from";
337
+ };
338
+ readonly manifests: {
339
+ readonly type: "object";
340
+ readonly description: "Build variants keyed by type (dev, prod, etc.)";
341
+ readonly patternProperties: {
342
+ readonly "^[a-z][a-z0-9-]*$": {
343
+ readonly $ref: "#/$defs/richEntry";
344
+ };
345
+ };
346
+ readonly additionalProperties: false;
347
+ readonly minProperties: 1;
348
+ };
349
+ };
350
+ readonly required: readonly ["environment", "branch", "manifests"];
351
+ readonly additionalProperties: true;
352
+ }, {
353
+ readonly title: "Minimal Environment Map";
354
+ readonly description: "Minimal manifest format as environment -> channels mapping";
355
+ readonly type: "object";
356
+ readonly patternProperties: {
357
+ readonly "^[a-z][a-z0-9-]*$": {
358
+ readonly type: "object";
359
+ readonly description: "Environment entry with channel mappings";
360
+ readonly patternProperties: {
361
+ readonly "^[a-z][a-z0-9-]*$": {
362
+ readonly $ref: "#/$defs/minimalChannel";
363
+ };
364
+ };
365
+ readonly additionalProperties: false;
366
+ readonly minProperties: 1;
367
+ };
368
+ };
369
+ readonly additionalProperties: false;
370
+ readonly minProperties: 1;
371
+ }];
372
+ readonly $defs: {
373
+ readonly richEntry: {
374
+ readonly title: "Rich Build Entry";
375
+ readonly description: "Rich manifest build entry (dev or prod)";
376
+ readonly type: "object";
377
+ readonly properties: {
378
+ readonly build_type: {
379
+ readonly type: "string";
380
+ readonly enum: readonly ["dev", "prod"];
381
+ readonly description: "Build type identifier";
382
+ };
383
+ readonly file: {
384
+ readonly type: "string";
385
+ readonly description: "Firmware binary filename";
386
+ };
387
+ readonly size: {
388
+ readonly type: "integer";
389
+ readonly minimum: 1;
390
+ readonly description: "Total firmware size in bytes";
391
+ };
392
+ readonly sha256: {
393
+ readonly type: "string";
394
+ readonly pattern: "^[a-f0-9]{64}$";
395
+ readonly description: "SHA256 hash of the full firmware binary (lowercase hex)";
396
+ };
397
+ readonly firmware_version: {
398
+ readonly type: "string";
399
+ readonly description: "Semantic or build version string";
400
+ };
401
+ readonly built: {
402
+ readonly type: "string";
403
+ readonly format: "date-time";
404
+ readonly description: "ISO8601 timestamp when built";
405
+ };
406
+ readonly ota_url: {
407
+ readonly type: "string";
408
+ readonly format: "uri";
409
+ readonly description: "Absolute or relative URL to fetch the firmware";
410
+ };
411
+ readonly chunk_size: {
412
+ readonly type: "integer";
413
+ readonly minimum: 1;
414
+ readonly description: "Size of each chunk except possibly the last";
415
+ };
416
+ readonly chunks: {
417
+ readonly description: "Either structured chunk objects or array of SHA256 strings";
418
+ readonly oneOf: readonly [{
419
+ readonly type: "array";
420
+ readonly items: {
421
+ readonly $ref: "#/$defs/chunkObject";
422
+ };
423
+ readonly description: "Array of structured chunk objects with metadata";
424
+ }, {
425
+ readonly type: "array";
426
+ readonly items: {
427
+ readonly type: "string";
428
+ readonly pattern: "^[a-f0-9]{64}$";
429
+ readonly description: "SHA256 hash of chunk (lowercase hex)";
430
+ };
431
+ readonly description: "Array of SHA256 strings for chunks";
432
+ }];
433
+ };
434
+ };
435
+ readonly required: readonly ["build_type", "file", "size", "sha256", "firmware_version", "built", "ota_url"];
436
+ readonly additionalProperties: true;
437
+ };
438
+ readonly chunkObject: {
439
+ readonly title: "OTA Chunk Object";
440
+ readonly description: "Structured chunk metadata with offset and size";
441
+ readonly type: "object";
442
+ readonly properties: {
443
+ readonly index: {
444
+ readonly type: "integer";
445
+ readonly minimum: 0;
446
+ readonly description: "0-based sequential chunk index";
447
+ };
448
+ readonly offset: {
449
+ readonly type: "integer";
450
+ readonly minimum: 0;
451
+ readonly description: "Byte offset within firmware binary";
452
+ };
453
+ readonly size: {
454
+ readonly type: "integer";
455
+ readonly minimum: 1;
456
+ readonly description: "Chunk size in bytes";
457
+ };
458
+ readonly sha256: {
459
+ readonly type: "string";
460
+ readonly pattern: "^[a-f0-9]{64}$";
461
+ readonly description: "SHA256 hash of the chunk (lowercase hex)";
462
+ };
463
+ };
464
+ readonly required: readonly ["index", "offset", "size", "sha256"];
465
+ readonly additionalProperties: true;
466
+ };
467
+ readonly minimalChannel: {
468
+ readonly title: "Minimal Channel Entry";
469
+ readonly description: "Minimal manifest channel entry";
470
+ readonly type: "object";
471
+ readonly properties: {
472
+ readonly file: {
473
+ readonly type: "string";
474
+ readonly description: "Firmware binary filename";
475
+ };
476
+ readonly size: {
477
+ readonly type: "integer";
478
+ readonly minimum: 1;
479
+ readonly description: "Total firmware size in bytes";
480
+ };
481
+ readonly sha256: {
482
+ readonly type: "string";
483
+ readonly pattern: "^[a-f0-9]{64}$";
484
+ readonly description: "SHA256 hash of the firmware binary (lowercase hex)";
485
+ };
486
+ readonly version: {
487
+ readonly type: "string";
488
+ readonly description: "Firmware version string";
489
+ };
490
+ readonly timestamp: {
491
+ readonly type: "string";
492
+ readonly format: "date-time";
493
+ readonly description: "ISO8601 timestamp";
494
+ };
495
+ };
496
+ readonly required: readonly ["file", "size", "sha256", "version", "timestamp"];
497
+ readonly additionalProperties: true;
498
+ };
499
+ };
500
+ };
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.mqtt_v1_bundle_json = exports.control_response_schema = exports.firmware_status_schema = exports.gateway_metrics_schema = exports.gateway_info_schema = exports.sensor_status_schema = exports.sensor_heartbeat_schema = exports.sensor_data_schema = exports.envelope_schema = void 0;
3
+ exports.ota_ota_manifest_schema = exports.mqtt_v1_bundle_json = exports.control_response_schema = exports.firmware_status_schema = exports.gateway_metrics_schema = exports.gateway_info_schema = exports.sensor_status_schema = exports.sensor_heartbeat_schema = exports.sensor_data_schema = exports.envelope_schema = void 0;
4
4
  // AUTO-GENERATED by copy-schemas.cjs. Do not edit manually.
5
5
  /* eslint-disable */
6
6
  exports.envelope_schema = {
@@ -400,3 +400,216 @@ exports.mqtt_v1_bundle_json = {
400
400
  "control_response": "control_response.schema.json"
401
401
  }
402
402
  };
403
+ exports.ota_ota_manifest_schema = {
404
+ "$id": "https://schemas.alteriom.com/ota/ota-manifest.schema.json",
405
+ "title": "Alteriom OTA Firmware Manifest",
406
+ "description": "Schema for Alteriom OTA firmware manifests supporting both rich and minimal variants",
407
+ "type": "object",
408
+ "oneOf": [
409
+ {
410
+ "title": "Rich Manifest",
411
+ "description": "Rich manifest format with environment, branch, and manifests object",
412
+ "type": "object",
413
+ "properties": {
414
+ "environment": {
415
+ "type": "string",
416
+ "description": "Target environment (e.g., universal-sensor)"
417
+ },
418
+ "branch": {
419
+ "type": "string",
420
+ "description": "Source control branch the build originated from"
421
+ },
422
+ "manifests": {
423
+ "type": "object",
424
+ "description": "Build variants keyed by type (dev, prod, etc.)",
425
+ "patternProperties": {
426
+ "^[a-z][a-z0-9-]*$": {
427
+ "$ref": "#/$defs/richEntry"
428
+ }
429
+ },
430
+ "additionalProperties": false,
431
+ "minProperties": 1
432
+ }
433
+ },
434
+ "required": [
435
+ "environment",
436
+ "branch",
437
+ "manifests"
438
+ ],
439
+ "additionalProperties": true
440
+ },
441
+ {
442
+ "title": "Minimal Environment Map",
443
+ "description": "Minimal manifest format as environment -> channels mapping",
444
+ "type": "object",
445
+ "patternProperties": {
446
+ "^[a-z][a-z0-9-]*$": {
447
+ "type": "object",
448
+ "description": "Environment entry with channel mappings",
449
+ "patternProperties": {
450
+ "^[a-z][a-z0-9-]*$": {
451
+ "$ref": "#/$defs/minimalChannel"
452
+ }
453
+ },
454
+ "additionalProperties": false,
455
+ "minProperties": 1
456
+ }
457
+ },
458
+ "additionalProperties": false,
459
+ "minProperties": 1
460
+ }
461
+ ],
462
+ "$defs": {
463
+ "richEntry": {
464
+ "title": "Rich Build Entry",
465
+ "description": "Rich manifest build entry (dev or prod)",
466
+ "type": "object",
467
+ "properties": {
468
+ "build_type": {
469
+ "type": "string",
470
+ "enum": [
471
+ "dev",
472
+ "prod"
473
+ ],
474
+ "description": "Build type identifier"
475
+ },
476
+ "file": {
477
+ "type": "string",
478
+ "description": "Firmware binary filename"
479
+ },
480
+ "size": {
481
+ "type": "integer",
482
+ "minimum": 1,
483
+ "description": "Total firmware size in bytes"
484
+ },
485
+ "sha256": {
486
+ "type": "string",
487
+ "pattern": "^[a-f0-9]{64}$",
488
+ "description": "SHA256 hash of the full firmware binary (lowercase hex)"
489
+ },
490
+ "firmware_version": {
491
+ "type": "string",
492
+ "description": "Semantic or build version string"
493
+ },
494
+ "built": {
495
+ "type": "string",
496
+ "format": "date-time",
497
+ "description": "ISO8601 timestamp when built"
498
+ },
499
+ "ota_url": {
500
+ "type": "string",
501
+ "format": "uri",
502
+ "description": "Absolute or relative URL to fetch the firmware"
503
+ },
504
+ "chunk_size": {
505
+ "type": "integer",
506
+ "minimum": 1,
507
+ "description": "Size of each chunk except possibly the last"
508
+ },
509
+ "chunks": {
510
+ "description": "Either structured chunk objects or array of SHA256 strings",
511
+ "oneOf": [
512
+ {
513
+ "type": "array",
514
+ "items": {
515
+ "$ref": "#/$defs/chunkObject"
516
+ },
517
+ "description": "Array of structured chunk objects with metadata"
518
+ },
519
+ {
520
+ "type": "array",
521
+ "items": {
522
+ "type": "string",
523
+ "pattern": "^[a-f0-9]{64}$",
524
+ "description": "SHA256 hash of chunk (lowercase hex)"
525
+ },
526
+ "description": "Array of SHA256 strings for chunks"
527
+ }
528
+ ]
529
+ }
530
+ },
531
+ "required": [
532
+ "build_type",
533
+ "file",
534
+ "size",
535
+ "sha256",
536
+ "firmware_version",
537
+ "built",
538
+ "ota_url"
539
+ ],
540
+ "additionalProperties": true
541
+ },
542
+ "chunkObject": {
543
+ "title": "OTA Chunk Object",
544
+ "description": "Structured chunk metadata with offset and size",
545
+ "type": "object",
546
+ "properties": {
547
+ "index": {
548
+ "type": "integer",
549
+ "minimum": 0,
550
+ "description": "0-based sequential chunk index"
551
+ },
552
+ "offset": {
553
+ "type": "integer",
554
+ "minimum": 0,
555
+ "description": "Byte offset within firmware binary"
556
+ },
557
+ "size": {
558
+ "type": "integer",
559
+ "minimum": 1,
560
+ "description": "Chunk size in bytes"
561
+ },
562
+ "sha256": {
563
+ "type": "string",
564
+ "pattern": "^[a-f0-9]{64}$",
565
+ "description": "SHA256 hash of the chunk (lowercase hex)"
566
+ }
567
+ },
568
+ "required": [
569
+ "index",
570
+ "offset",
571
+ "size",
572
+ "sha256"
573
+ ],
574
+ "additionalProperties": true
575
+ },
576
+ "minimalChannel": {
577
+ "title": "Minimal Channel Entry",
578
+ "description": "Minimal manifest channel entry",
579
+ "type": "object",
580
+ "properties": {
581
+ "file": {
582
+ "type": "string",
583
+ "description": "Firmware binary filename"
584
+ },
585
+ "size": {
586
+ "type": "integer",
587
+ "minimum": 1,
588
+ "description": "Total firmware size in bytes"
589
+ },
590
+ "sha256": {
591
+ "type": "string",
592
+ "pattern": "^[a-f0-9]{64}$",
593
+ "description": "SHA256 hash of the firmware binary (lowercase hex)"
594
+ },
595
+ "version": {
596
+ "type": "string",
597
+ "description": "Firmware version string"
598
+ },
599
+ "timestamp": {
600
+ "type": "string",
601
+ "format": "date-time",
602
+ "description": "ISO8601 timestamp"
603
+ }
604
+ },
605
+ "required": [
606
+ "file",
607
+ "size",
608
+ "sha256",
609
+ "version",
610
+ "timestamp"
611
+ ],
612
+ "additionalProperties": true
613
+ }
614
+ }
615
+ };