signalk-forecast-dataset-provider 0.1.0 → 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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0 — 2026-10-31
4
+
5
+ - Require Node.js 22 or later, dropping Node.js 20 support. Align Node.js
6
+ development types with version 22 and test on Node.js 22/24.
7
+ - Upgrade Express to 5 and validate named route parameters explicitly. Cover
8
+ the HTTP lifecycle against both Express 4 hosts and Express 5 hosts.
9
+ - Upgrade to TypeScript 7 with Node16 module resolution, preserving CommonJS
10
+ output for Signal K plugin loading.
11
+ - Update Signal K development API types to 2.35 and the OpenAPI validator to 13.
12
+ Development checks require Node.js 22.19+; installed runtime code supports 22+.
13
+ - Separate major dependency migrations from routine Dependabot updates; keep
14
+ Express and its types together and Node.js types on the supported baseline.
15
+ - Keep storage, settings, dataset identities and HTTP contract 0.1 unchanged.
16
+ - Verify Signal K 2.34.0/2.35.1 integration and the existing sandbox deployment.
17
+
18
+ ## 0.1.1 — 2026-10-31
19
+
20
+ - Enable Forecast Dataset Provider by default on fresh installations, so the
21
+ catalogue is available without a separate activation step.
22
+ - Preserve existing explicit enabled/disabled settings through Signal K's
23
+ standard activation behaviour. Already-disabled installations still require
24
+ enabling the provider once in Plugin Config.
25
+ - Keep storage, permissions and HTTP contract 0.1 unchanged.
26
+
3
27
  ## 0.1.0 — 2026-10-30
4
28
 
5
29
  - First stable catalogue release, promoting the tested streaming publication,
package/README.md CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  Signal K Resource Provider for **`forecastDatasets`**: a shared catalogue of persistent, immutable forecast content. Downloaders and receivers publish original bytes; routing, weather and navigation clients discover and retrieve them without depending on private directories.
4
4
 
5
- **Version 0.1.0 is the first stable catalogue release.** It implements staged multi-asset publication (including unknown-size streams), live publication progress, SHA-256 content versions, resumable publication after restart, verified-inventory enrichment, filtered discovery, HTTP Range/HEAD/ETag access, deletion coordination and v2 resource deltas. No GRIB/Zarr decoder is bundled. Formats are declared, not detected or validated meteorologically.
5
+ **Version 0.2.0 requires Node.js 22 or later.** It implements staged multi-asset publication (including unknown-size streams), live publication progress, SHA-256 content versions, resumable publication after restart, verified-inventory enrichment, filtered discovery, HTTP Range/HEAD/ETag access, deletion coordination and v2 resource deltas. No GRIB/Zarr decoder is bundled. Formats are declared, not detected or validated meteorologically.
6
6
 
7
7
  The architecture and examples come from the [project outline](https://github.com/macjl/signalk-grib-resources/blob/c289876e4d4e5faadbb6779d865d7aa21a0d9822/docs/project-outline.md). The wire contract selected for this prototype is documented in [contract 0.1](docs/contract-v0.1.md); it is experimental, not an adopted Signal K specification.
8
8
 
9
9
  ## Install
10
10
 
11
- Requires **Signal K server 2.34 or later within 2.x** and Node.js 20.19 or later for this plugin (the server may require a newer Node version). The target server for integration verification is 2.34.0.
11
+ Requires **Signal K server 2.34 or later within 2.x** and Node.js 22 or later for this plugin. Dependency migration integration was also verified against Signal K 2.35.1.
12
12
 
13
13
  See the [coordinated upgrade guide](docs/upgrading.md) before upgrading the downloader and Weather Provider from local-file releases. The complete chain requires Node.js 24+.
14
14
 
@@ -18,7 +18,7 @@ Install the stable release from the Signal K App Store or from its configuration
18
18
  npm install signalk-forecast-dataset-provider@latest
19
19
  ```
20
20
 
21
- Restart the server and enable **Forecast Dataset Provider** in Plugin Config. To build a local archive from source:
21
+ Restart the server. From version **0.1.1**, fresh installations enable **Forecast Dataset Provider** automatically. Signal K preserves an existing explicit disabled setting: for an already installed but disabled provider, enable it once in Plugin Config and save. To build a local archive from source:
22
22
 
23
23
  ```sh
24
24
  npm ci
@@ -26,7 +26,7 @@ npm run check
26
26
  npm pack
27
27
  ```
28
28
 
29
- Install the resulting archive using the server's normal plugin installation workflow, for example `npm install /path/to/signalk-forecast-dataset-provider-0.1.0.tgz` from its configuration directory. No npm release is required for local testing.
29
+ Install the resulting archive using the server's normal plugin installation workflow, for example `npm install /path/to/signalk-forecast-dataset-provider-0.2.0.tgz` from its configuration directory. No npm release is required for local testing.
30
30
 
31
31
  The default storage location is `<plugin data directory>/catalogue`. An optional absolute `storageDirectory` can be configured. Use a local filesystem supporting hard links and atomic rename, with a single catalogue writer. Limits are configurable for asset bytes, dataset bytes, asset count and unfinished publications. They are per-publication limits, not an automatic total-disk-quota policy. Unfinished publications persist until resumed or explicitly aborted.
32
32
 
package/RELEASING.md CHANGED
@@ -5,15 +5,16 @@ GRIB Downloader and History Explorer): publish a GitHub Release, or manually run
5
5
  `.github/workflows/publish.yml`. Publication uses npm trusted publishing (OIDC)
6
6
  with provenance; no `NPM_TOKEN` GitHub secret is used.
7
7
 
8
- The package and trusted publisher already exist. Release **0.1.0** is the first
9
- stable version and publishes to `latest`; the earlier public release was
10
- `0.1.0-beta.1`. Older 0.1.0/0.1.1/0.1.2 entries were unpublished development
11
- snapshots. See [the coordinated upgrade guide](docs/upgrading.md).
8
+ The package and trusted publisher already exist. Version **0.2.0** is a stable
9
+ release published to `latest`. It requires Node.js 22+ and keeps HTTP contract
10
+ 0.1. Version 0.1.0 was the first stable release, followed by 0.1.1. The dated
11
+ prototype entries under “Development snapshots” in the changelog were never
12
+ published. See [the coordinated upgrade guide](docs/upgrading.md).
12
13
 
13
14
  ## First publication setup
14
15
 
15
16
  The package must exist before an npm trusted publisher can be configured. The
16
- existing `0.0.0-stage` bootstrap already satisfies this: **do not repeat the
17
+ existing `0.0.1` bootstrap already satisfies this: **do not repeat the
17
18
  bootstrap for this package**. For a new package, as with History Explorer,
18
19
  bootstrap an **empty** placeholder outside its checkout.
19
20
  These commands publish publicly and must be run deliberately by the maintainer:
@@ -59,8 +60,8 @@ and [npm trust prerequisites](https://docs.npmjs.com/cli/v11/commands/npm-trust/
59
60
 
60
61
  ## Prepare the real plugin version
61
62
 
62
- The first stable version is **0.1.0**. Decide the new version before tagging
63
- subsequent releases.
63
+ The release prepared here is **0.2.0**. Decide the new version before tagging
64
+ each subsequent release.
64
65
  Keep `package.json`, `package-lock.json`, the OpenAPI info version and changelog
65
66
  consistent if changing it. The resource contract stays `0.1` independently of
66
67
  the npm package version.
@@ -77,7 +78,9 @@ helpers, local configuration and credentials must not be included. Compilation
77
78
  runs during `prepack`, so the installed App Store package needs no install/build
78
79
  script. `prepublishOnly` reruns the full checks before a real publish.
79
80
 
80
- The local CI checks Node 20/22/24. The separate Signal K reusable CI follows the
81
+ Development and release checks require Node 22.19 or later (the OpenAPI
82
+ validator requires this version baseline); installed runtime code supports Node 22+.
83
+ The local CI checks Node 22/24. The separate Signal K reusable CI follows the
81
84
  other plugins and adds Linux/ARM64/macOS/Windows plus real-server integration.
82
85
  Those hosted checks only run after pushing; local success does not claim they
83
86
  have already passed. Publication uses Node 24 on a GitHub-hosted runner.
@@ -99,7 +102,19 @@ provenance. The OIDC path cannot be fully validated by a local dry run: npm must
99
102
  recognise the configured GitHub workflow. Tighten npm publishing access only
100
103
  after that first successful OIDC publication, as in the other plugins.
101
104
 
102
- ## Preparation checks (2026-10-21)
105
+ ## Validation for 0.2.0
106
+
107
+ The migration passed 29 top-level tests on Node.js 22/24, including an additional
108
+ execution of the HTTP suite with an Express 4 host and Express 5 middleware.
109
+ Hosted CI passed on Linux x64/ARM64, macOS and Windows, with integration against
110
+ Signal K 2.34.0 and 2.35.1. The existing sandbox deployment also passed the
111
+ publication, recovery, inventory, permissions and original-content transport
112
+ checks, and the maintainer confirmed acceptance. Production dependency audit
113
+ reported zero vulnerabilities. The release commit is checked again before
114
+ publication; inspect its hosted checks rather than assuming earlier results
115
+ apply to later changes.
116
+
117
+ ## Historical preparation checks (2026-10-21)
103
118
 
104
119
  The 27 local tests, formatting and TypeScript checks passed. `npm publish --dry-run`
105
120
  completed without publishing; the inspected archive contains 24 files, including
package/dist/http.js CHANGED
@@ -106,15 +106,15 @@ function registerRoutes(router, getStore, report) {
106
106
  read.get('/datasets', wrap((req, res) => json(req, res, (0, discovery_1.listResources)(getStore(), req.query))));
107
107
  read.get('/datasets/:id', wrap((req, res) => {
108
108
  const store = getStore();
109
- json(req, res, store.resource(store.get(req.params.id)));
109
+ json(req, res, store.resource(store.get((0, validation_1.identifier)(req.params.id))));
110
110
  }));
111
111
  read.get('/datasets/:id/versions/:version', wrap((req, res) => {
112
112
  const store = getStore();
113
- json(req, res, store.resource(store.get(req.params.id, req.params.version)));
113
+ json(req, res, store.resource(store.get((0, validation_1.identifier)(req.params.id), (0, validation_1.identifier)(req.params.version))));
114
114
  }));
115
115
  read.get('/datasets/:id/versions/:version/index', wrap(async (req, res) => {
116
116
  const store = getStore();
117
- const { record, inventory } = await store.readInventory(req.params.id, req.params.version);
117
+ const { record, inventory } = await store.readInventory((0, validation_1.identifier)(req.params.id), (0, validation_1.identifier)(req.params.version));
118
118
  json(req, res, {
119
119
  datasetId: record.id,
120
120
  version: record.version,
@@ -128,7 +128,7 @@ function registerRoutes(router, getStore, report) {
128
128
  });
129
129
  }));
130
130
  read.get('/datasets/:id/versions/:version/assets/:asset', wrap(async (req, res) => {
131
- const opened = await getStore().openAsset(req.params.id, req.params.version, req.params.asset);
131
+ const opened = await getStore().openAsset((0, validation_1.identifier)(req.params.id), (0, validation_1.identifier)(req.params.version), (0, validation_1.identifier)(req.params.asset));
132
132
  try {
133
133
  const { asset, handle } = opened;
134
134
  const tag = `"sha256-${asset.sha256}"`;
@@ -171,7 +171,7 @@ function registerRoutes(router, getStore, report) {
171
171
  }
172
172
  }));
173
173
  const begin = wrap(async (req, res) => {
174
- const stage = await getStore().begin(req.body, req.params.id);
174
+ const stage = await getStore().begin(req.body, req.params.id === undefined ? undefined : (0, validation_1.identifier)(req.params.id));
175
175
  res.status(201).location(`${types_1.API_ROOT}/publications/${stage.id}`).json(stage);
176
176
  });
177
177
  write.post('/publications', body, begin);
@@ -182,14 +182,14 @@ function registerRoutes(router, getStore, report) {
182
182
  write.get('/publications/:publication', wrap((req, res) => {
183
183
  res
184
184
  .set('Cache-Control', 'no-store')
185
- .json(getStore().publication(req.params.publication));
185
+ .json(getStore().publication((0, validation_1.identifier)(req.params.publication)));
186
186
  }));
187
187
  write.put('/publications/:publication/assets/:asset', wrap(async (req, res) => {
188
188
  if (req.is('application/octet-stream') !== 'application/octet-stream' ||
189
189
  (req.get('Content-Encoding') &&
190
190
  req.get('Content-Encoding') !== 'identity'))
191
191
  throw new validation_1.ApiError(415, 'RAW_UPLOAD_REQUIRED', 'Upload unencoded application/octet-stream bytes');
192
- const stage = getStore().publication(req.params.publication);
192
+ const stage = getStore().publication((0, validation_1.identifier)(req.params.publication));
193
193
  const expected = stage.declaration.assets.find((a) => a.id === req.params.asset);
194
194
  if (!expected)
195
195
  throw new validation_1.ApiError(404, 'ASSET_NOT_DECLARED', 'Asset was not declared');
@@ -201,23 +201,23 @@ function registerRoutes(router, getStore, report) {
201
201
  throw new validation_1.ApiError(413, 'SIZE_LIMIT_EXCEEDED', 'Content-Length exceeds configured asset size limit');
202
202
  }
203
203
  req.setTimeout(120000, () => req.destroy(new Error('Upload timed out')));
204
- const asset = await getStore().upload(req.params.publication, req.params.asset, req);
204
+ const asset = await getStore().upload((0, validation_1.identifier)(req.params.publication), (0, validation_1.identifier)(req.params.asset), req);
205
205
  res.status(201).json(asset);
206
206
  }));
207
207
  write.post('/publications/:publication/commit', wrap(async (req, res) => {
208
208
  const store = getStore();
209
- const record = await store.commit(req.params.publication);
209
+ const record = await store.commit((0, validation_1.identifier)(req.params.publication));
210
210
  res
211
211
  .status(201)
212
212
  .location(`${types_1.API_ROOT}/datasets/${record.id}/versions/${record.version}`)
213
213
  .json(store.resource(record));
214
214
  }));
215
215
  write.delete('/publications/:publication', wrap(async (req, res) => {
216
- await getStore().abort(req.params.publication);
216
+ await getStore().abort((0, validation_1.identifier)(req.params.publication));
217
217
  res.status(204).end();
218
218
  }));
219
219
  write.delete('/datasets/:id', wrap(async (req, res) => {
220
- await getStore().remove(req.params.id);
220
+ await getStore().remove((0, validation_1.identifier)(req.params.id));
221
221
  res.status(204).end();
222
222
  }));
223
223
  // Verified inventory is an explicit assertion from an administrator/indexer,
@@ -227,7 +227,7 @@ function registerRoutes(router, getStore, report) {
227
227
  if (!value)
228
228
  throw new validation_1.ApiError(428, 'REVISION_REQUIRED', 'If-Match with the current dataset ETag is required');
229
229
  const store = getStore();
230
- const record = store.get(req.params.id, req.params.version);
230
+ const record = store.get((0, validation_1.identifier)(req.params.id), (0, validation_1.identifier)(req.params.version));
231
231
  if (value !== etag(store.resource(record)))
232
232
  throw new validation_1.ApiError(412, 'REVISION_CHANGED', 'Dataset metadata has changed');
233
233
  const updated = await store.enrich(record.id, record.version, req.body, record.metadataRevision);
package/dist/openapi.js CHANGED
@@ -124,7 +124,7 @@ exports.openApi = {
124
124
  openapi: '3.0.3',
125
125
  info: {
126
126
  title: 'Forecast Dataset Provider',
127
- version: '0.1.0',
127
+ version: '0.2.0',
128
128
  description: 'Experimental contract 0.1. Original-content catalogue; no GRIB/Zarr decoding or subset processor is bundled.'
129
129
  },
130
130
  servers: [{ url: types_1.API_ROOT }],
@@ -54,3 +54,24 @@ Update/rollback note: committed contract 0.1 datasets retain the same storage an
54
54
  Plugin Config now exposes `maxAssetMiB: 2048` and `maxDatasetMiB: 8192`, with explicit MiB labels (1,048,576 bytes). Effective default byte limits and byte-based HTTP/index/progress semantics are unchanged. No configuration migration or compatibility aliases are included for the unpublished prototype. Only this sandbox plugin's settings were manually adjusted through its configuration API.
55
55
 
56
56
  The updated 0.1.2 archive was installed with a backup at `local-packages/forecast-provider-before-update-2026-10-21T18-45-50-384Z`. The actual `/plugins` schema and saved settings were verified. The 45 other plugin configurations, enable/debug flags and both existing dataset content versions remained unchanged. The provider's streaming/recovery/transport integration helper passed again and removed its synthetic data. Local checks pass 27 tests, including unit conversion, invalid configuration and enforcement of a MiB-configured limit during an upload.
57
+
58
+ ## Dependency migration verification — 2026-10-31
59
+
60
+ The provider now requires Node.js 22+. Local checks passed on Node.js 22.23.4
61
+ and 24.22.2 with TypeScript 7, Express 5 and the updated OpenAPI validator.
62
+ The 29 top-level tests include a second execution of the HTTP suite using an
63
+ Express 4 host with the provider's Express 5 JSON middleware.
64
+
65
+ A separate Signal K 2.35.1 instance used Node.js 24.22.1 and the server's
66
+ Express 4.22.3. It ran on a free port with its own temporary configuration,
67
+ catalogue and test accounts, using the packed provider with production-only
68
+ dependencies. Fresh-install activation, reader/write permissions, anonymous
69
+ access rejection, unknown-size streaming, commit, core Resource API discovery
70
+ and filtering, Range, HEAD, ETag and deletion passed. The instance was stopped
71
+ and its temporary configuration removed. The existing sandbox configuration,
72
+ installed plugins and datasets were not changed.
73
+
74
+ The targeted sandbox install/update helpers require Express 5 to be available
75
+ for the provider. They deliberately do not resolve dependencies; do not use
76
+ them to reuse the server's Express 4 installation during this migration. Use
77
+ a normal package installation that installs the provider's dependencies.
package/docs/upgrading.md CHANGED
@@ -1,11 +1,15 @@
1
1
  # Coordinated forecast-plugin upgrade
2
2
 
3
- Stable versions: Forecast Dataset Provider **0.1.0**, GRIB Downloader **0.4.0**,
3
+ The first coordinated stable migration used: Forecast Dataset Provider **0.1.0**, GRIB Downloader **0.4.0**,
4
4
  GRIB Weather Provider **0.7.0**, Forecast Dataset Browser **0.1.0**.
5
5
  The catalogue HTTP contract remains **0.1**; it is not an adopted Signal K standard.
6
6
 
7
7
  ## Requirements and upgrade from local-file releases
8
8
 
9
+ Provider 0.2.0 requires Node.js 22+ and preserves the same catalogue storage and
10
+ HTTP contract. It does not require a dataset or configuration migration. Use the
11
+ latest compatible releases when applying the historical migration steps below.
12
+
9
13
  The complete chain requires **Node.js 24+** (the Weather Provider's ecCodes
10
14
  WebAssembly decoder needs memory64) and **Signal K 2.34+ within 2.x**.
11
15
  Downloader 0.3.x and Weather Provider 0.6.x shared local files. Upgrade both
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signalk-forecast-dataset-provider",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Signal K Resource Provider for immutable forecast datasets, original assets and verified inventories",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -46,9 +46,9 @@
46
46
  "url": "https://github.com/macjl/signalk-forecast-dataset-provider/issues"
47
47
  },
48
48
  "engines": {
49
- "node": ">=20.19.0"
49
+ "node": ">=22"
50
50
  },
51
- "signalk-plugin-enabled-by-default": false,
51
+ "signalk-plugin-enabled-by-default": true,
52
52
  "signalk": {
53
53
  "displayName": "Forecast Dataset Provider",
54
54
  "appIcon": "icon.svg"
@@ -57,14 +57,15 @@
57
57
  "@signalk/server-api": ">=2.34.0 <3"
58
58
  },
59
59
  "devDependencies": {
60
- "@apidevtools/swagger-parser": "^12.1.0",
61
- "@signalk/server-api": "2.34.0",
62
- "@types/express": "^4.17.21",
63
- "@types/node": "^20.19.0",
60
+ "@apidevtools/swagger-parser": "^13.1.0",
61
+ "@signalk/server-api": "2.35.0",
62
+ "@types/express": "^5.0.6",
63
+ "@types/node": "^22.20.5",
64
+ "express4": "npm:express@^4.22.3",
64
65
  "prettier": "^3.6.2",
65
- "typescript": "~5.9.3"
66
+ "typescript": "7.0.2"
66
67
  },
67
68
  "dependencies": {
68
- "express": "^4.21.2"
69
+ "express": "^5.3.0"
69
70
  }
70
71
  }