Releases: iamkm1/Norway-Open-Data
Release list
v0.8.0 — Geospatial Norway
0.8.0 — 2026-07-26
Minor Changes
-
Geospatial Norway. Adds three anonymous, experimental namespaces for focused catalogue,
environmental and land-resource use. The SDK deliberately remains a curated data client rather
than a general GIS proxy.sdk.geodata— Geonorge / Kartverket catalogue.searchDatasets()andsearchServices()
return bounded, deterministically ordered metadata results;searchDatasetsAll()and
searchServicesAll()expose caller-bounded async iterators.getMetadata(),getDataset()and
getService()normalize publisher, themes, licence, geographic extent, update metadata,
distributions and related services. Discovery classifies WFS, WMS, OGC API Features, ArcGIS REST,
downloadable files and other published links, but the SDK never follows a discovered URL or
grants access to the resource behind it.sdk.environment— Miljødirektoratet / Naturbase. Adds point lookups, bounding-box searches
and lazy bounded iterators for current protected areas, proposed protected areas, modern NiN
nature localities and the January 2023 intervention-free nature status. The integration is
intentionally limited to these useful public vector layers instead of exposing every map layer.sdk.land— NIBIO AR50. Adds point lookup, bounding-box search and a bounded iterator for the
open generalized AR50 land-resource classification. Restricted agreement-based Geovekst AR5,
parcel-precision data and soil/cultivation-suitability products are not included.profiles.natureAtLocation()— a partial cross-provider nature profile. Composes all five
Naturbase/NIBIO datasets with the nearest official Kartverket place and municipality context.
Each operation retains its real source, retrieval time and attribution. Provider failures degrade
to component warnings without removing successful sections; caller cancellation still rejects
the complete call.Profile-source compatibility and provenance. Existing
profiles.company(),
profiles.address(),profiles.municipality()andprofiles.vessel()responses retain their
synthetic top-level composite source identities. The new nature profile also uses a synthetic
top-level source so a multi-provider result is not attributed to only one provider. The additive
sourcesarray is derived from successful components and carries their real provider descriptors,
preserving separate entries when licence or mandatory attribution text differs;components
retains per-operation provenance.Strict shared GeoJSON and bounded feature paging. Adds runtime validation for two-dimensional
WGS84 Point, MultiPoint, LineString, Polygon, MultiPolygon, polygon holes, null geometry, feature
identifiers, feature collections and CRS declarations. Naturbase and NIBIO request server-side
WGS84 output while retaining the advertised source CRS. Unsupported CRS declarations are rejected;
the SDK does not silently reinterpret coordinates and includes no client-side reprojection engine.
Feature searches default to 100 results, expose truncation and continuation metadata, propagate
AbortSignal, and stop repeated cursors, repeated pages and empty endless pages.Scope and verification. WMS image rendering, arbitrary caller-supplied upstream URLs,
restricted datasets and full-country downloads remain out of scope. Deterministic offline tests
cover geometry, CRS, ArcGIS/WFS/OGC paging, truncation, attribution and partial profiles; bounded
anonymous live probes are opt-in through the existingRUN_LIVE_TESTSswitch. No new credentials
or environment variables are required.
Release verification
- Public API: 22 namespaces and 118 supported methods, including Geonorge, Naturbase and NIBIO.
- Verification: 1,177 tests passed before publication.
v0.7.0 — Maritime Norway
Live AIS streaming, the vessel and aquaculture registers, a cross-provider vessel profile, and the reusable OAuth2 and NDJSON infrastructure they needed.
Two things to know before you start: BarentsWatch needs two separate credentials, and absent AIS data never means an absent vessel.
New namespaces
sdk.ais— BarentsWatch AIS. Vessel positions and identity (getVesselSnapshot,getVesselSnapshots,getLatestPositions), recorded tracks (getTrackLast24Hours,getTrack), area and name search (getMmsiInArea,searchVessels), the published coverage area (getCoverageArea), and live streaming (streamPositions,streamMessages) as a plainAsyncIterablewith cancellation.sdk.marine— BarentsWatch wave and sea-current point forecasts.sdk.fisheries— Fiskeridirektoratet's fishing-vessel register and public aquaculture register, both open and needing no credentials.profiles.vessel()— joins AIS to the fishing-vessel register on the radio call sign, plus MET conditions and the nearest Kartverket place name.
Infrastructure
Reusable OAuth2 client-credentials support with in-memory tokens, refresh ahead of expiry, single-flight concurrent refresh and abort support. Tokens are never persisted and never appear in errors or logs. A web-standard NDJSON parser returning AsyncIterable<T> with per-record validation, chunk-boundary reassembly, a configurable buffered-record limit and reader cleanup on early exit — no automatic reconnection in this release. Shared Coordinates and BoundingBox primitives.
Two credentials, not one
BarentsWatch issues separate AIS-clients and API-clients, and a secret registered for one is rejected by the other. Configure them independently as credentials["barentswatch-ais"] and credentials.barentswatch.
Coverage limits
AIS coverage excludes fishing vessels under 15 m and leisure vessels under 45 m, is limited to Norwegian waters, and retains 14 days. An empty track or a no-recent-data profile means only that BarentsWatch had nothing to publish — never that a vessel does not exist. ais.getCoverageArea() tells absence of data from absence of coverage.
Privacy
The vessel register's private-owner names, postal codes and towns are suppressed in the runtime schema, so raw payloads and cached values are covered too.
Verification
Verified end to end against the live BarentsWatch services with real credentials: both scopes, real tokens, 812 live positions, 403 snapshots, 54 track points, 40 streamed messages and all three marine endpoints validating with no undeclared fields. That live run caught four defects no offline test could reach — the met/hydro broadcast dropping 28 of its 43 fields, getWaveForecastSeries() mishandling both forms of "no coverage", message labels matched against a spelling the service does not use, and validated fields discarded in normalization.
1024 tests across 35 files; 96.55% lines, 81.21% branches, 99.18% functions. No breaking changes.
v0.6.0
Added
sdk.klass— Statistics Norway's SSB Klass API. Browse and search classifications, inspect dated versions, list version/date/range code lists, retrieve exact codes, and use the officialselectCodescode-pattern syntax.- Correspondence tables and mappings. List correspondence-table metadata and page fixed, dated or ranged correspondence. Because Klass does not page these upstream, locally bounded pages are labelled
pagination.upstreamPaged: false. - Ambiguity-preserving municipality and county resolution. Historical codes are resolved through official change graphs; merges, splits, multiple predecessors/successors, mixed-step ambiguity, missing context and no-match outcomes are all returned explicitly — no branch is silently selected.
- Strict runtime-validated types and stable classification IDs for municipalities, counties, industry, occupations, education and three country-code systems.
No breaking changes. profiles.municipality() behaviour is unchanged. Published with npm provenance (SLSA); the registry gitHead matches this tag.
v0.5.3
Fixes
- Incomplete SSB population years are no longer exposed as complete totals.
profiles.municipality()aggregated SSB's per-sex, per-age population cells and skipped any suppressed (null) cell, so a year missing one or more components was published as a finished count — and year-over-yearchangewas computed against that partial figure. Completeness is now tracked per year: a year yields a total only when every cell is numeric. Missing data is treated as unknown, never zero, so an incomplete year is omitted andchangeis only ever computed between two complete years. - Municipality input is validated before any provider request. An empty or whitespace-only query is now trimmed and rejected with
InputValidationErrorbefore the HTTP client is invoked, instead of spending an SSB request and failing later withNotFoundError.
No public API or type changes. Published with npm provenance (SLSA); the registry gitHead matches this tag.
v0.5.2
Patch release fixing two defects in the shared HTTP core and documenting an ESM/CommonJS limitation. Each fix is covered by a regression test that fails without it.
Fixes
-
A public
applicationNameno longer corrupts provider responses. Providers that identify the caller through a public header — Entur'sET-Client-Name, NVDB'sX-Client, MET Norway'sUser-Agent— had that header's value treated as a secret and stripped out of successful payloads before validation. AnapplicationNamethat also occurred in a provider's own data rewrote it: an application calledvegvesenturned NVDB's continuation URL intohttps://nvdbapiles.atlas.[REDACTED].no/…, which then failed URL validation and made everyroads.getRoadNetwork()androads.getRoadNetworkAll()call raiseResponseValidationError. Caller identification is public by design, so it is no longer a redaction target. Genuine secrets are unaffected:contactEmail, configuredapiKeyvalues, andAuthorization,CookieandX-API-Keyheader values are still redacted from payloads and errors, and properties named after request headers are still dropped. -
Retry-Afteris no longer shortened to the backoff cap. A provider asking for a 30-second pause was retried after 5 seconds, because the five-second cap on the SDK's own exponential backoff was also applied to the provider's explicit instruction. The two are now distinct:- with no
Retry-After, capped jittered backoff as before; - with a
Retry-Afterof up to 60 seconds, the stated duration is waited in full; - beyond 60 seconds the retry stops without waiting first.
The status-to-error mapping is unchanged:
RateLimitErrorfor HTTP 429, the usualProviderErrorfor a retryable 5xx. Both carryretryAfterin seconds, which is the stable cross-status signal that a provider asked you to wait. Cancelling the caller'ssignalstill rejects a waiting retry immediately. Retryable statuses and attempt counts are unchanged.This changes timing for callers that hit HTTP 429 or a retryable 5xx carrying
Retry-After: those retries now happen later than before, or surface as an error instead of retrying. That is the intended correction. - with no
-
Mixed ESM/CommonJS error identity is now documented. When one process loads both published builds, an error from one fails
instanceofagainst the other build's class, because class identity is per-bundle. This is a property of dual-published packages and is documented rather than worked around — no symbol branding or prototype manipulation was introduced. The README recommends branching on the stablename,provider,statusCodeandretryAfterfields where an error can cross that boundary, and the packed-package test now asserts same-buildinstanceofin both builds and verifies that fallback.
No public type or signature changed.
npm install norway-open-data-sdk@0.5.2v0.5.1
Patch release fixing three defects found in the 0.5.0 request-budget and profile code, each now covered by a regression test.
Fixes
- A provider's default request budget could be spent twice per window. A request naming a budget the provider never declared was keyed by the requested name, so it received its own limiter holding a copy of the
defaultpolicy instead of sharing it. The limiter is now keyed by the resolved budget name. Declared budgets, such as Data.norge'sresource, stay separate as before. profiles.address()credited NVE in the composedsourceeven when all three Varsom warning feeds had failed and contributed nothing. An emptyhazardsarray next to an NVE attribution reads as an all-clear NVE never issued. NVE is now named only when at least one feed answered, matching the behaviourprofiles.municipality()already had. Per-feed status remains available incomponents.profiles.municipality()fetched SSB's table 07459 metadata twice per call — once to resolve the municipality and once inside the population query — spending two requests of SSB's documented 30-per-minute budget on identical bytes. The metadata is now fetched once and reused. The supportedquery()andqueryRaw()surface is unchanged.
Also fixes pnpm check:portability leaving its generated probe file inside dist/ when the probe process failed, because process.exit in the error path pre-empted the cleanup.
No changes to the supported consumer surface. See CHANGELOG.md for full details.
npm install norway-open-data-sdk@0.5.1v0.5.0
-
Providers are now declared once, in one place. Each provider owns a
ProviderDescriptorunder
src/providers/<provider>/provider.tsholding its legal metadata, caller identification, request
budgets and cache lifetimes, andsrc/providers/registry.tscollects them. TheProviderIdunion
is derived from the registry's own keys, and the registry refuses to compile when a key and a
descriptoriddisagree, so one spelling of an identifier reaches error messages, response
source.id, cache keys and configuration. Requests carry the descriptor rather than a provider
name, making a mistyped provider a type error instead of a misleading message. Adding a provider now
means writing its folder and one registry line; no file undersrc/core/changes.Caller identification is declarative. A descriptor names the configuration values it needs and
builds its own headers from them; the HTTP client verifies they are present and raises
ConfigurationErrorwith the provider's own instructions before any network access. The bespoke
per-client identity plumbing for Entur, MET Norway, NVDB and NVE HydAPI is gone, and cross-provider
profiles ask the same question to decide whether to skip a section asnot-configured.New: per-provider request budgets, enforced by default. Every provider declares how often the SDK
may call it, and one sliding-window limiter per budget is shared by all clients on a
NorwayOpenDatainstance. Admission is serialized so concurrent callers cannot overshoot together.
Waiting happens before the request timeout is armed, so a queued request is not charged for its
wait; a cache hit costs no budget, a retry does, and a caller'ssignalrejects a queued request
immediately. Budgets are named per operation class because providers publish different limits per
service — Data.norge allows 10 searches per minute but 5 resource lookups per second, and those no
longer throttle each other.basisdistinguishes a provider's published number from a conservative
budget the SDK chose. Disable withrateLimit: { enabled: false }.New: pluggable cache storage.
cache.storeaccepts anyCacheStoreimplementingget,setand
clear, synchronously or asynchronously, so validated responses can be shared across instances,
workers or hosts instead of being trapped in one process. The default in-memory cache is unchanged.Breaking changes. This is a minor release before 1.0, so a
^0.4.1dependency range will not pick
it up automatically; upgrade deliberately and read this list first:- Per-provider request budgets are enforced by default. Code that previously issued unbounded
bursts now waits instead of failing, which is the intended behaviour but changes timing. Data.norge
is the tightest at 10 search requests per minute, as that service documents. Opt out with
rateLimit: { enabled: false }. NorwayOpenData.clearCache()returnsPromise<void>so it can await a custom store. Existing
calls that ignore the result keep working;awaitit when you depend on the cache being empty.providersis keyed by provider id rather than a camelCase alias, soproviders.dataNorgeand
providers.norgesBankbecomeproviders["data-norge"]andproviders["norges-bank"]. Its values
are nowProviderMetadatarather than literal types, and exposerateLimit. Every other key is
unchanged.credentialsis keyed by provider id and rejects unknown providers instead of ignoring them.
credentials.nve.apiKeyis unchanged.- A cancellation that arrives while the cache is being read now rejects before the request is sent
rather than after. Cancellation semantics are otherwise unchanged.
Provider rate limits were reconciled against the numbers recorded in
PROVIDERS.md, and a
regression test now fails if a documented budget drifts from its descriptor. - Per-provider request budgets are enforced by default. Code that previously issued unbounded
v0.4.1
Patch Changes
Verified and documented that the package runs unchanged on Node.js 22+, Deno, Bun, Cloudflare Workers and other edge hosts, and in browsers wherever the provider sends permissive CORS headers.
No runtime API changed and no capability was added — the SDK already used only web-standard APIs. The previous README claim that browser support was not guaranteed was simply inaccurate, and this release corrects it.
- Distribution targets ES2022 on esbuild's neutral platform rather than
node22. The ESM output is byte-identical and the CommonJS output is marginally smaller. exportsgains a trailingdefaultcondition, so resolvers matching neitherimportnorrequirestill reach the ESM build. This can only widen resolution.pnpm check:portability(part ofpnpm verify) scans the built bundles for any Node built-in reference and exercises the request, cache, validation, error and cancellation paths against an object implementing only the standardResponsesurface.- CI now builds the package and executes it on real Deno 2 and Bun runtimes on every change.
Host runtimes must supply a spec-compliant fetch, structuredClone and a full-ICU Intl for Europe/Oslo dates and Norwegian locale casing. See the new Runtime support section in the README.
Full changelog: v0.4.0...v0.4.1
v0.4.0
Minor Changes
-
New
profiles.municipality()composition answers one municipality from SSB, FHI, Brønnøysundregistrene and NVE in a single call.const kommune = await norway.profiles.municipality("Haugesund"); // or the code "1106" kommune.data.population; // SDK-aggregated residents for the two newest years kommune.data.lifeExpectancy; // FHI value, or years: null with a suppression flag kommune.data.companies; // Brønnøysundregistrene organization count kommune.data.hazards; // Exact NVE warning matches for the municipality
It resolves a four-digit municipality code or an exact municipality name against SSB's region register — counties and the whole-country region never resolve, and duplicated names such as Herøy require SSB's county-qualified label, so a profile never silently resolves to the wrong kommune. Population totals are summed by the SDK from SSB's per-sex, per-age rows (an SDK aggregation, not an SSB-published figure). Life expectancy preserves FHI's suppression flag with
years: nullfor small municipalities. Every optional section degrades to aprovider-errorcomponent instead of failing the call; caller cancellation still rejects.
The hosted API reference is now published at https://iamkm1.github.io/Norway-Open-Data/.
Full changelog: v0.3.0...v0.4.0
v0.3.0
Minor Changes
-
New
healthnamespace for the FHI Statistikk open API.getSources,getTables,getTableMetadata,getTableDimensions,queryandqueryRawcover source and table discovery, provider-authored documentation, hierarchical dimensions and JSON-stat2 data queries across Norwegian health registers — the Cause of Death Registry, the Abortion Registry, drug wholesale statistics, municipal public-health indicators and more.FHI's cell-suppression flags are preserved rather than hidden: flagged observations normalize to
value: nullwith the provider'sflagsymbol (for example":"— anonymized), and every result carries FHI's flag legend. Flagged cells must stay suppressed in downstream use. The JSON-stat parsing core is now shared between SSB and FHI, withparseJsonStatandparseTableMetadataunchanged. -
Cross-provider profiles now degrade optional provider failures to partial results instead of failing the whole call. If MET, NVDB or one Varsom warning feed errors at request time,
profiles.address()returns every surviving section and reports the failing operation as an omitted component with the newprovider-errorreason and a sanitized error name and message;profiles.company()likewise returns a location-less profile when the Kartverket lookup fails. Required operations still throw, and caller cancellation always rejects the whole call.
Full changelog: v0.2.2...v0.3.0