This file orients agents (Claude or others) working on lib-bridge-js. It deliberately does not duplicate information that already lives in this repo — it links you to the right document for each concern. Read this first, then the linked sources as you start a task.
Before touching code, read these in order:
- README.md — what the package is, public API surface, and the typical bridge-author flow.
- INSTALL.md — server-side install steps for a partner deployment (nginx, certbot, managing-user creation).
- CHANGELOG.md — every behaviour change in reverse chronological order. Read recent entries before estimating an API.
- doc/onboarding-flow-detail.md — sequence diagram and step-by-step description of the partner→bridge→HDS onboarding handshake. Essential before changing any onboarding code.
src/index.ts— the canonical list of public exports. Anything a bridge consumes goes through here.src/server.ts,src/start.ts— the Express app factory and the cluster entry point.src/initBoiler.ts— config initialization. The order ofinitBoilervs everything else is the most common source of bridge bugs (see "Gotchas").
A bridge is a server that mediates between a third-party partner platform and an HDS ecosystem account. Each user gets a long-lived "bridge account" on HDS; the bridge holds the partner credentials and pushes data into the user's HDS streams.
Partner platform ──HTTPS──▶ bridge (this lib + plugin) ──HTTPS──▶ HDS (Pryv) account
│
└──▶ persistent bridge state (boiler config, user store)
This library provides the shared scaffolding:
- HTTP server (Express + cluster + CORS + error handling).
- Partner-auth middleware (
partnerAuthTokenheader check). - The user onboarding flow (initiate → HDS auth → finalize → optional partner webhook).
- A
PluginBridgebase class that each partner-specific bridge extends. - Test utilities (
testServer) for consumer-side integration tests.
A bridge consumer:
extends PluginBridgewith partner-specific logic (key,potentialCreatedItemKeys,init,newUserAssociated).- Calls
startCluster(MyBridge, configDir)(orcreateBridgeAppfor tests). - Ships its own
localConfig.ymldescribing partner endpoints, secrets, and HDS-side connection.
The README's "Creating a bridge" section is the canonical worked example — keep it as the source of truth for the consumer flow; this file points you at the internal invariants that make it work.
This repo ships compiled JS to consumers because Node 24 forbids TS type-stripping inside node_modules/ (ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING):
tsconfig.build.jsonsetsrewriteRelativeImportExtensions: trueso.tsimports compile to.jscorrectly.npm run build→tsc -p tsconfig.build.json→ emitsdist/.npm run preparerunsnpm run buildautomatically — every git-dep install rebuilds.- Consumers import from the published
exports:"."→./dist/src/index.js"./test"→./dist/tests/helpers/testServer.js
When you change src/, run npm run build and verify dist/ is updated before committing. Do not import from dist/ inside src/.
initBoiler(name, configDir) must run before anything that reads config — including new PluginBridge(...). The constructor calls getLogger() which auto-initialises boiler with the wrong config dir if boiler isn't initialised yet, leaving you with a half-loaded bridge that fails in subtle ways.
The bootstrap shape every bridge follows:
import { initBoiler, startCluster } from 'lib-bridge-js';
initBoiler('my-bridge', __dirname); // FIRST
await import('./postBoot.js'); // Then everything elseIf you see "config key not found" for a key that exists in localConfig.yml, check the init order before suspecting the config.
Boiler / nconf maps env-var __ to config-path :. So MY_BRIDGE__partnerAuth__token=foo sets myBridge:partnerAuth:token. Deep merging happens at the leaf level, not the object level. Useful for Dokku / docker overrides.
Everything a bridge consumer can touch is re-exported by src/index.ts. If a consumer needs something deeper, add the export — don't let bridges import from dist/src/lib/... paths that we may rename.
The current public exports (full list in src/index.ts):
PluginBridge— base class.startCluster,createBridgeApp— entry points.initBoiler— config init.errors,Router— utilities.initHDSModel,getHDSModel— re-exported from hds-lib-js.testServer— test helpers (under the"./test"export).
tests/— mocha + nodeassert. Tests run vianpm testagainst a real test server (no mocks for bridge boundary code).- New tests go next to similar existing ones. New behaviour without a test is not done.
- Many tests need a configured
localConfig.yml— never commit local secrets; use the example config and document required keys in the README.
Use the boiler-provided logger (via getLogger), not console.log. The logger respects log-level config and writes to the configured destination (file in prod, stdout in dev).
- Bump
versioninpackage.jsonfor every observable change (any change a downstream bridge could see). - Add a
## [x.y.z] - YYYY-MM-DDblock under[Unreleased]in CHANGELOG.md. One bullet = one observable change. - Consumers pin to git URLs, but the version + changelog is how humans read the diff.
The handshake spans 3 actors: partner (their server), bridge (this code + plugin), and HDS (Pryv account). The full sequence is in doc/onboarding-flow-detail.md. Headline beats:
- Partner → bridge
POST /onboarding/initiatewithpartnerUserId+partnerAuthToken. Bridge issues a one-time HDS auth request and returns a redirect URL. - User → HDS authenticates in their browser, grants the bridge access, returns to the bridge's redirect URL with a Pryv API endpoint.
- Bridge → partner webhook (configured per-partner) confirming the new association.
- Bridge stores
{partnerUserId → bridgeAccount}and starts whatever periodic sync the plugin defines.
Anything that diverges from this flow needs a strong reason and an updated diagram.
initBoilerorder: see "Critical init order" above. This is the #1 source of "works on my machine but not in prod" bugs.- Stale
dist/: if you editsrc/, push, and the consumer still reports old behaviour afternpm install, yourdist/is stale. Runnpm run build, verify the file indist/, recommit. tsconfig.build.jsonvstsconfig.json: the devtsconfig.jsonhasnoEmit: true. Onlytsconfig.build.jsonemits — always runtsc -p tsconfig.build.json(ornpm run build) to regeneratedist/.- Cluster vs single-process:
startClusterforks workers; in dev you usually wantcreateBridgeAppdirectly so breakpoints work. - Partner auth header:
partnerAuthTokenis checked by middleware on every partner-facing route. Do NOT bypass it for "convenience" endpoints. - HDS access scoping: a bridge access on a user account has a finite stream scope. Pushing to a stream outside the granted scope errors with
forbiddenfrom Pryv. When adding new item types, declare them up-front inpotentialCreatedItemKeysso the access request includes them.
- hds-lib-js — the runtime data-model + Pryv-extensions library.
lib-bridge-jsre-exportsinitHDSModel/getHDSModel. Whenhds-lib-jsships a breaking API change, this repo'sdist/needs a rebuild + version bump. - data-model — the YAML source for HDS items / streams / event types. A bridge maps partner observations to the canonical items declared here (e.g. partner appetite →
nutrition-appetite, partner cervical-fluid →body-vulva-mucus-inspect). Never create source-prefixed items; see the data-modelAGENTS.mdfor the rule.
A bridge runs against a real HDS (Pryv) account. The Pryv concepts you must hold in your head are documented in hds-lib-js's AGENTS.md — read its "Pryv concepts" table once. The ones bridges hit constantly:
- Service info — discovery endpoint of the platform the bridge writes into.
- Accesses & permissions — bridges typically hold an
appaccess scoped to the streams they create. - Events / event types / streams — what the bridge writes; shapes are defined in
data-model. - Integrity — per-event hash; do not strip or recompute manually.
Authoritative Pryv reference: https://pryv.github.io/reference/. Conceptual primer: https://pryv.github.io/data-in-pryv/.
- For the data shape of an event you're producing: read the item YAML in data-model.
- For the Pryv method to call (
events.create,events.update, …): read the Pryv API reference. - For the right place to add new shared scaffolding: re-read
src/index.tsand the section above titled "Public surface". If the right place doesn't exist yet, propose it in the PR description.
If the answer to a question is not in any of these places, that is a documentation bug — extend this file or README.md before fixing the code.