@nfdi4plants/datahub-client
v0.8.0
Published
A polyglot client library for ARC DataHubs — JavaScript/TypeScript distribution.
Readme
DataHubClient
A polyglot client library for ARC DataHubs. Written once in F# and transpiled to .NET, JavaScript/TypeScript, and Python via Fable; published to NuGet, npm, and PyPI.
Documentation · Getting started · Resource API · API reference
Installation
| Target | Command |
|---|---|
| .NET | dotnet add package DataHubClient |
| JS/TS | npm install @nfdi4plants/datahub-client |
| Python | pip install datahub-client |
The npm package includes JavaScript ES modules and generated TypeScript declarations. JavaScript and TypeScript consumers use the same imports; TypeScript discovers the types automatically, and the package needs no TypeScript runtime:
import { DataHubClient } from "@nfdi4plants/datahub-client";
import { Authentication } from "@nfdi4plants/datahub-client/Http/Authentication.js";
const client = new DataHubClient("https://git.nfdi4plants.org", Authentication.Anonymous());
const projects = await client.Projects.ListAsync();
for (const project of projects) console.log(project.Name); // inferred as stringSee the TypeScript guide for compiler setup, inferred types, and explicit type imports. API examples use shared JavaScript / TypeScript tabs.
Use Authentication.Anonymous() to read public resources without a token.
Projects.ListAllAsync retrieves all matching projects across pages, with optional
search, compact-response, and visibility filters; Projects.ListAsync retrieves
one page. See listing public projects
for examples in F#, JavaScript, TypeScript, and Python.
Use client.Packages.DownloadROCrateAsync(projectId) to download an ARC's
generated metadata. The RO-Crate download guide
shows how to save it as a UTF-8 file in F#, JavaScript, TypeScript, and Python.
Development
Prerequisites
- .NET 10 SDK
- Python ≥ 3.12
uvfor Python environment management (see uv docs)- Node.js 20.19+ or 22.12+ and npm for JavaScript packaging (CI uses Node.js 24)
Run once after cloning (or after a devcontainer rebuild):
dotnet tool restore # restores `fable` and `fsdocs`
uv sync # creates .venv with httpx + fable-library⚠️ The
.venvis OS-specific — rebuild it when you switch OS.uvcreates a platform-specific virtual environment in.venv(it is.gitignored). A.venvcarried over from another OS — e.g. from the Linux devcontainer onto a Windows host — makesuv runand theRunTestsPythonbuild target fail. Delete.venvand letuv sync/uv runrebuild it for the current OS.
Tests
Build and test orchestration runs through the FAKE pipeline — see AGENTS.md for the full layout and conventions.
./build.sh # default target — builds the solution
./build.sh RunTestsDotNet # .NET unit tests
./build.sh RunTestsJavaScript # transpile + run the JS suite on node
./build.sh RunTestsPython # transpile + run the Python suite under uv/python
./build.sh RunTestsAll # all threeUse build.cmd on Windows with the same arguments.
Browser tests install the packed npm artifact and run it in Chromium and Firefox,
using native ES modules, the Vite development server, and a Vite production build.
They exercise real HTTP requests and CORS against a local test server, including
RO-Crate downloads saved with the browser's Blob API. No DataHub account or token
is needed.
Install the browsers once, then run the browser target:
npm ci
node node_modules/playwright/cli.js install chromium firefox
./build.sh RunTestsBrowser # packs npm, checks TypeScript, then runs browsersOn Linux, add --with-deps to the browser installation command to install required
system libraries. CI runs this target on every pull request.
Documentation
The published documentation pairs an MkDocs Material guide with an fsdocs-generated F# API reference (design record: plans/docs.md). The guide provides F#, JavaScript / TypeScript, and Python tabs backed by sample programs under docs/samples/, plus a dedicated TypeScript setup guide. CI executes the examples against the freshly-packed NuGet/npm/PyPI artifacts; TypeScript samples also compile in strict mode against the packaged declarations. Browser-only downloads run in the browser checks.
./build.sh RunDocsSamples # pack + run F#, JavaScript, TypeScript, and Python samples
./build.sh RunDocsSamplesTypeScript # pack + compile and run TypeScript samples
./build.sh BuildDocs # build the full site into site/ (guide + API reference)
./build.sh WatchDocs # live-reload preview of the guide at http://127.0.0.1:8000
./build.sh WatchApiDocs # live-reload preview of the F# API reference at http://127.0.0.1:8901CI deploys site/ to GitHub Pages on every version tag.
Packaging
A single F# source tree produces three published artifacts. Each language uses a different transpile-output location, manifest, and packer:
| Target | Build output dir | Manifest | Built into pkg/ |
|--------|--------------------------|------------------------------------------|----------------------------------------------|
| .NET | n/a (compiled .NET assembly) | src/DataHubClient/DataHubClient.fsproj | DataHubClient.<version>.nupkg |
| JS/TS | dist/ts/ → dist/js/ | src/DataHubClient/package.json | nfdi4plants-datahub-client-<version>.tgz |
| Python | src/DataHubClient/py/ | root pyproject.toml (poetry-core) | datahub_client-<version>-py3-none-any.whl |
The version comes from RELEASE_NOTES.md: the FAKE build stamps it into each manifest before packing.
The JavaScript/TypeScript build uses Fable to generate TypeScript in dist/ts/,
checks it with TypeScript in strict mode, then runs Vite 7 with vite-plugin-dts
4.5.4 to produce JavaScript ES modules and adjacent .d.ts files in dist/js/.
Both are packed together, preserving the existing root and deep import paths.
Packing runs npm ci to install the pinned build dependencies from the root
package.json and lockfile. These tools are build dependencies; consumers run the
JavaScript using native fetch in modern browsers or Node.js.
The Python build mirrors ARCtrl's layout:
- Fable transpiles into
src/DataHubClient/py/(next to the.fssource, gitignored — it's a regenerated build artifact). - A hand-written src/DataHubClient/__init__.py lives next to the
.fssource and re-exportsDataHubClientandAuthentication, so end users writefrom datahub_client import DataHubClient, Authentication. - The root pyproject.toml uses
poetry-corewithpackages = [{ include = "**/*.py", from = "src/DataHubClient/", to = "datahub_client" }]to renamesrc/DataHubClient/→datahub_client/inside the wheel, so the wheel installs a single package and the relative imports inside the Fable output resolve. include = [{ path = "src/DataHubClient/py/**/*.py", format = ["sdist", "wheel"] }]force-pulls the gitignored Fable output into the wheel. The explicitformatis required — poetry-core 2.x silently drops gitignored files whenincludeis given as a plain string.[tool.uv] package = falsekeepsuv syncfrom editable-installing the project during dev test runs; dev tests operate on Fable's separately-transpileddist/py-tests/output, never on an installeddatahub_client.
Build all three packages into pkg/:
./build.sh Pack # production version from RELEASE_NOTES.md
./build.sh PackPrerelease # same, with a prerelease suffixTo build only the npm package, run ./build.sh PackJavaScript. Both this target
and the full Pack target install the packed tarball into an isolated consumer,
check its declarations with strict TypeScript under NodeNext and Bundler
module resolution, and run offline request checks against the packaged JavaScript.
Package smoke tests
TestPackages installs each freshly-packed artifact the way a real end user would — no Fable, no F# — and runs a one-line script that constructs DataHubClient to prove package metadata, entry points, and transitive deps resolve cleanly.
./build.sh TestPackages # runs all three
./build.sh TestPackagesDotNet # F# script via `dotnet fsi`, NuGet from local pkg/
./build.sh TestPackagesJavaScript # `npm install <tarball>` + `node smoke.mjs`
./build.sh TestPackagesPython # isolated venv + `pip install <wheel>` + `python smoke.py`Each smoke regenerates its consumer script into pkg/testScripts/{dotnet,js,py}/ on every run; the Python venv is created at /tmp/datahubclient-py-smoke-venv (outside the repo) because uv venv places a lib64 symlink that FAKE's Shell.cleanDirs chokes on.
Publishing
Releases are published by CI (.github/workflows/ci.yml) on every version tag:
Add a
### X.Y.Z (Released ...)entry at the top of RELEASE_NOTES.md — the build derives the published version from it, and CI refuses tags that don't match it.Regenerate the tracked version source and include it in the same commit:
./build.sh GenerateVersionSource # rewrites src/DataHubClient/Version.fsThis does not affect what gets published: CI exports
DATAHUBCLIENT_VERSIONfrom the tag,packageVersion()prefers it overRELEASE_NOTES.md, and the file is regenerated before packing regardless. It matters becauseVersion.fsis tracked, and a plaindotnet buildfrom a fresh checkout — the quick-iteration path that skips the FAKE pipeline — compiles whatever is committed. Forget this step andDataHubClientVersion.Valuereports the previous release to anyone building that way.Commit, then tag and push:
git tag X.Y.Z git push origin main X.Y.Z
CI then runs all three test suites, packs, and publishes DataHubClient to NuGet, @nfdi4plants/datahub-client to npm, and datahub-client to PyPI. The publish steps skip already-published versions, so re-running a failed release run is safe.
The same tag deploys the documentation site. That requires the github-pages environment to permit tags — it carries a tag: *.*.* deployment policy alongside branch: main. Without the tag policy the Deploy Docs job is rejected while packages still publish, so a release looks half-successful. The branch: main entry exists so a manual workflow_dispatch run can redeploy the site without cutting a new tag.
Publishing is keyless via trusted publishing (OIDC) on all three registries — no long-lived registry credentials are stored on the repo. The moving parts:
- Each registry has a trusted-publishing policy pointing at this repo,
ci.yml, and thereleaseGitHub environment: nuget.org → Trusted Publishing, npm package settings → Trusted publisher, PyPI project settings → Publishing. - The only Actions secret is
NUGET_USER— the nuget.org profile name the NuGet policy belongs to (theNuGet/loginaction exchanges the job's OIDC token for a temporary API key under that account). - The publish job runs in the
releaseenvironment; adding required reviewers to that environment in the repo settings turns publishing into a manually approved step.
For local artifacts without publishing, use ./build.sh Pack (see Packaging).
