@alteriom/mqtt-schema 0.3.0 → 0.3.2

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/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # @alteriom/mqtt-schema
2
2
 
3
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)
4
6
  ![npm version](https://img.shields.io/npm/v/@alteriom/mqtt-schema.svg)
5
7
  ![npm downloads](https://img.shields.io/npm/dm/@alteriom/mqtt-schema.svg)
6
8
  ![license](https://img.shields.io/npm/l/@alteriom/mqtt-schema.svg)
@@ -10,62 +12,10 @@
10
12
  ![latest tag](https://img.shields.io/github/v/tag/Alteriom/alteriom-mqtt-schema?label=tag)
11
13
  [![bundle size](https://img.shields.io/bundlephobia/minzip/@alteriom/mqtt-schema)](https://bundlephobia.com/package/@alteriom/mqtt-schema)
12
14
 
13
- Alteriom MQTT v1 JSON Schemas, TypeScript types, and production‑ready validation helpers for integrating firmware MQTT payloads into web or backend services.
14
-
15
- > NOTE: OTA manifest CI validation workflow file could not be added via automated API due to path constraints; it can be added manually post-merge if still absent.
16
-
17
- See also: `docs/SCHEMA_MAP.md` for complete schema listing.
18
-
19
- ## OTA Firmware Manifest Schema (NEW in 0.3.0)
20
-
21
- The package now includes the OTA firmware manifest schema used by build + deployment tooling.
22
-
23
- Import the schema JSON directly:
24
- ```ts
25
- import otaManifestSchema from '@alteriom/mqtt-schema/schemas/ota/ota-manifest.schema.json';
26
- ```
27
-
28
- Types:
29
- ```ts
30
- import { OtaManifest, isRichManifest } from '@alteriom/mqtt-schema/types/ota-manifest';
31
- ```
32
-
33
- Shapes supported (oneOf):
34
- - Rich manifest:
35
- ```json
36
- {
37
- "environment": "universal-sensor",
38
- "branch": "main",
39
- "manifests": { "dev": { /* richEntry */ }, "prod": { /* richEntry */ } }
40
- }
41
- ```
42
- - Minimal environment map:
43
- ```json
44
- {
45
- "universal-sensor": { "dev": { /* minimalChannel */ } }
46
- }
47
- ```
48
-
49
- Chunk hashing variants (mutually exclusive):
50
- 1. Structured objects (offset + size + sha256)
51
- 2. Array of lowercase sha256 strings
52
-
53
- Validation example:
54
- ```ts
55
- import Ajv from 'ajv';
56
- import schema from '@alteriom/mqtt-schema/schemas/ota/ota-manifest.schema.json';
57
- import { OtaManifest } from '@alteriom/mqtt-schema/types/ota-manifest';
58
15
 
59
- const ajv = new Ajv({ allErrors: true });
60
- const validate = ajv.compile<OtaManifest>(schema as any);
61
- const manifest: OtaManifest = JSON.parse(raw);
62
- if (!validate(manifest)) {
63
- console.error(validate.errors);
64
- }
65
- ```
66
-
67
- ---
16
+ Alteriom MQTT v1 JSON Schemas, TypeScript types, and production‑ready validation helpers for integrating firmware MQTT payloads into web or backend services.
68
17
 
18
+
69
19
  ## Why this exists
70
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:
71
21
 
@@ -90,6 +40,8 @@ Firmware emits structured MQTT payloads that must remain tightly aligned with we
90
40
  npm install @alteriom/mqtt-schema ajv ajv-formats
91
41
  ```
92
42
 
43
+ **Support & Compatibility**: Node 18+ tested; Node 20 primary. Dual CJS/ESM builds.
44
+
93
45
  ## Quick Start
94
46
 
95
47
  Validate a JSON payload (object already parsed):
@@ -137,6 +89,43 @@ Access raw schema JSON (if you need to introspect or power form generation):
137
89
  import envelopeSchema from '@alteriom/mqtt-schema/schemas/envelope.schema.json';
138
90
  ```
139
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
+
140
129
  ## API Surface
141
130
 
142
131
  ```ts
@@ -195,8 +184,6 @@ All Ajv validator functions are compiled once at module load. For typical web us
195
184
  | `SensorDataMessage` etc. | TS interfaces | Strongly typed shapes |
196
185
  | `isSensorDataMessage` etc. | type guards | Runtime narrowing helpers |
197
186
  | `schemas/*.json` | JSON | Original schema assets (optional) |
198
- | `schemas/ota/ota-manifest.schema.json` | JSON | OTA firmware manifest schema (rich + minimal) |
199
- | `types/ota-manifest` | TS types | OtaManifest union + helpers |
200
187
 
201
188
  ### Validator Keys
202
189
 
@@ -223,8 +210,6 @@ Schema stability is paramount. We track two related versions:
223
210
 
224
211
  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).
225
212
 
226
- Backward-compatible schema additions to OTA manifest WILL use minor bumps.
227
-
228
213
  ## TypeScript / Bundler Notes
229
214
 
230
215
  - Works in TS >= 5, Node >= 16, Vite / Webpack / ESBuild.
@@ -236,13 +221,44 @@ Backward-compatible schema additions to OTA manifest WILL use minor bumps.
236
221
  - Optional custom Ajv injection hook
237
222
  - JSON Schema → Zod conversion example
238
223
  - Runtime metrics helper (count validation categories)
239
- - Signed OTA manifest extension
240
- - Delta / compressed OTA metadata fields
241
224
 
242
225
  ## Contributing
243
226
 
244
227
  Issues & PRs welcome. Ensure firmware repo schemas remain the authoritative source—do not manually edit generated `schema_data.ts`.
245
228
 
229
+ ### Development Workflow
230
+
231
+ **Before opening a PR:** Run the comprehensive validation suite:
232
+
233
+ ```bash
234
+ npm run verify # Schema sync + changelog + fixtures
235
+ npm run verify:all # Additional schema compilation + OTA manifest validation
236
+ npm test # Enhanced fixture validation with detailed reporting
237
+ ```
238
+
239
+ **Available validation scripts:**
240
+ - `npm run test` - Cross-platform fixture validation (CJS/ESM) with enhanced error reporting
241
+ - `npm run test:ota` - OTA manifest fixture validation
242
+ - `npm run verify:schemas` - Ensure schemas are synced from source directory
243
+ - `npm run verify:release` - Check changelog contains current version
244
+ - `npm run verify:all` - Comprehensive schema compilation and fixture validation
245
+
246
+ ### Script Organization
247
+
248
+ Scripts are organized by category in `package.json`:
249
+ - **Build Scripts**: `build`, `build:cjs`, `build:esm`, `clean`, etc.
250
+ - **Testing & Validation**: `test`, `verify:*`, etc.
251
+ - **Release Management**: `release:prepare`, `wiki:generate`
252
+ - **Repository Metadata**: `metadata:*` (organization compliance)
253
+
254
+ ### Release Process
255
+
256
+ See `PUBLISH_CHECKLIST.md` for detailed release procedures. Quick summary:
257
+ 1. Update schemas in `docs/mqtt_schema/`
258
+ 2. Run `npm run verify` to ensure everything is valid
259
+ 3. Use `npm run release:prepare -- patch|minor|major` for version bumping
260
+ 4. Follow checklist for tagging and publishing
261
+
246
262
  ## Security
247
263
 
248
264
  Schemas are static. No network access. Supply-chain risk minimized by keeping dependencies minimal (Ajv + formats only).
@@ -250,3 +266,107 @@ Schemas are static. No network access. Supply-chain risk minimized by keeping de
250
266
  ## License
251
267
 
252
268
  MIT
269
+
270
+ ## Registry Mirrors
271
+
272
+ This package is published to BOTH:
273
+
274
+ - Public npm registry: `https://registry.npmjs.org` (primary)
275
+ - GitHub Packages registry: `https://npm.pkg.github.com` (mirror for visibility in repo Packages tab)
276
+
277
+ ### Installing from GitHub Packages (optional)
278
+
279
+ Create or update an `.npmrc` with a scoped registry override (auth token with `read:packages` required):
280
+
281
+ ```bash
282
+ @alteriom:registry=https://npm.pkg.github.com
283
+ //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
284
+ ```
285
+
286
+ Then install normally:
287
+
288
+ ```bash
289
+ npm install @alteriom/mqtt-schema ajv ajv-formats
290
+ ```
291
+
292
+ If you omit the override, npmjs.org is used (recommended for most consumers).
293
+
294
+ ### Why dual publish?
295
+
296
+ - GitHub Packages listing provides provenance + quick visibility in the repo UI.
297
+ - npm remains the canonical public distribution source (faster, anonymous installs allowed).
298
+
299
+ ### Operational Notes
300
+
301
+ | Aspect | Behavior |
302
+ |--------|----------|
303
+ | Build artifact | Built once, same tarball published to both registries. |
304
+ | Version uniqueness | Same version must not be republished; bump if any change needed. |
305
+ | Auth (GitHub) | Always required for install from GitHub Packages, even for public repos. |
306
+ | Tarball parity | Do not rebuild between publishes; workflows ensure single build. |
307
+ | Fallback strategy | If mirror publish fails (e.g., transient), primary npm publish still stands. |
308
+ | Provenance flag | Applied for npm (GitHub ignores currently). |
309
+
310
+ ### Verifying a Release
311
+
312
+ ```bash
313
+ npm view @alteriom/mqtt-schema version
314
+ npm view @alteriom/mqtt-schema version --registry=https://npm.pkg.github.com
315
+ ```
316
+
317
+ Both should return the same version.
318
+
319
+ ## Repository Metadata Compliance
320
+
321
+ 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.
322
+
323
+ ### Local Usage
324
+
325
+ Run a validation (non‑destructive):
326
+
327
+ ```bash
328
+ npm run metadata:validate
329
+ ```
330
+
331
+ Generate a detailed report:
332
+
333
+ ```bash
334
+ npm run metadata:report
335
+ ```
336
+
337
+ Configuration lives in `metadata-config.json` (organizationTag `alteriom`). Default detection is auto; adjust if repository classification needs overriding.
338
+
339
+ ### CI Workflow
340
+
341
+ Workflow file: `.github/workflows/metadata-compliance.yml`
342
+
343
+ On each push / PR against `main` it will:
344
+
345
+ 1. Install dependencies
346
+ 2. Run `metadata:validate` (fails job on hard non‑compliance)
347
+ 3. Always attempt a best‑effort report (`metadata:report`) for visibility
348
+
349
+ ### Tokens / Permissions
350
+
351
+ 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).
352
+
353
+ ### Extending
354
+
355
+ 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.
356
+
357
+ ### Applying Metadata (Manual Workflow)
358
+
359
+ For authorized maintainers you can run adjustments via GitHub Actions:
360
+
361
+ 1. Open the "Repository Metadata Apply" workflow under the Actions tab.
362
+ 2. Choose whether to keep `dryRun` (default) or set to `false` to apply.
363
+ 3. Run the workflow; the log will show proposed or applied changes.
364
+
365
+ Local dry‑run vs apply:
366
+
367
+ ```bash
368
+ npm run metadata:apply:dry # show what would change
369
+ npm run metadata:apply # apply changes (requires proper permissions via GITHUB_TOKEN)
370
+ ```
371
+
372
+ 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
+ };