@bikefinder/api-client
Generated REST and SignalR clients for the API service. Hey API generates named HTTP methods, types and Zod validation from OpenAPI. TypedSignalR.Client.TypeScript generates hub proxies from C# interfaces implemented by the server. The official @microsoft/signalr client owns streaming and connection management. Version 1 is a clean break from the former narrow endpoint SDK; the old /admin and /v1 package entry points are removed.
import { createBikeFinderClient } from "@bikefinder/api-client";
const api = createBikeFinderClient({
baseUrl: "https://api.example.com",
getToken: () => auth0.getAccessTokenSilently(),
});
const result = await api.requests.getTracker({ path: { id: trackerId } });
if (result.error) showProblem(result.error);
else render(result.data.data);
await api.realtime.connection.start();
const subscription = api.realtime.hub.watchTracker(trackerId).subscribe({
next: (document) => render(document.data),
error: (error) => showSubscriptionError(error),
complete: () => showSubscriptionEnded(),
});
// Cancel this native SignalR stream and its server-side Firestore listener.
subscription.dispose();
// On sign-out/account change, discard account data and dispose this client.
await api.dispose();
Use staffRealtime.connection and staffRealtime.hub for staff operations. Connections are created without starting; start only the ones the application uses. Paged streams accept one query object, for example api.realtime.hub.watchTrackers({limit: 50}). Pass the returned nextCursor as cursor for another page.
The application owns subscription sharing, retained data and account scope. Subscribe to native connection.onreconnecting, onreconnected and onclose events to update its UI. Automatic reconnect is configured on each connection, but does not retry an initial start() failure or restore stream invocations: recreate subscriptions after reconnect. A terminated stream is finished. There is no custom watch-state or subscription coordinator.
The application's Auth0 SDK supplies tokens and handles refresh. Create a new client for a new account session. dispose() aborts HTTP requests and stops both connections. Requests use bearer authentication, omit cookies and reject redirects. Network/validation failures reject; HTTP Problem Details are available as result.error by default. Writes are never automatically retried; a 503 operation_unconfirmed may already have committed.
REST responses use generated Zod validation. Stream values have generated static types and use the native SignalR decoder; for runtime validation, import zTrackerDocument from @bikefinder/api-client/schemas and call .parse(value). Both transports reference the same generated payload types. Firestore responses come from the actual C# DTOs in Firestore/TrackerData.cs and Firestore/ResourceData.cs. The API converts stored data before responding; readIssues identifies unusable stored fields. Additional metadata remains in explicit JSON extension fields. Server-serialized response fixtures are validated by both API and generated-client tests. IDs are exact Firestore IDs.
Other native resources expose their known fields through resource-specific document types. Their C# contracts expose only interpreted fields; unrelated stored fields stay inside the API. Tracker reports and statistics have concrete models, and access-code storage is omitted from responses. Observation values are generated unions: after observation.definitionId === 5, observation.value.percent is a number. Current readings expose the same discriminator; check observationId !== null before using a present reading. Device settings, the built-in workflow descriptor and command argument tuples are generated from the canonical protocol JSON contracts. Bike and preference patches expose supported properties and distinguish omission from explicit null. Response validators preserve additional API fields while checking known values, required properties and enums. Request models retain their declared constraints and closed objects reject unknown write fields. Shared payloads have generated read variants so a tolerant response never weakens a request schema.
The handwritten entry point only configures clients, authentication and disposal. Hub methods, wire names and streaming signatures are generated. A generated type-only bridge reuses OpenAPI payload definitions instead of publishing a second set of models. There is no RxJS or MQTT dependency.
AWS staff methods deal with general observation, command and settings resources. Use requests.readAwsObservations to select current observation series, getAwsSettings to read the singleton settings shadow, and updateAwsSettings to patch typed settings with an expected version. The generated staffRealtime.hub.watchAwsObservations, watchAwsCommand and watchAwsSettings methods return native SignalR streams. See the AWS resource map for database and command-acceptance semantics.
The runtime requires standard Fetch, Request, Blob, AbortController and AbortSignal.any/throwIfAborted, plus a WebSocket implementation supported by SignalR. Supply the relevant platform polyfills before creating a client when needed.
Regenerate and build
The protocol ownership guide identifies the source
for each contract and the upstream generation steps. Edit those sources before
regenerating this client. openapi/api.json, src/generated, src/signalr,
src/device-schema.ts and src/workflow-schema.ts are generated outputs required
by the client build and consumers.
From the repository root:
dotnet tool restore
dotnet restore services/api/src/Api.csproj --locked-mode
dotnet build services/api/src/Api.csproj --no-restore
npm ci --ignore-scripts
npm run contract --workspace @bikefinder/api-client
npm run generate --workspace @bikefinder/api-client
npm run check --workspace @bikefinder/api-client
npm test --workspace @bikefinder/api-client
npm run build --workspace @bikefinder/api-client
The pinned .NET tool is configured in .config/dotnet-tools.json. Contract export runs the built service locally to retrieve OpenAPI; no Firebase credentials or live requests are needed. Generation fails if either generator fails. Generated source is committed and must be regenerated together. tsc-alias resolves generated extensionless imports when compiling the ESM package. The focused tests cover authentication, document fidelity, binary writes, uncertain write responses, native proxy invocation/cancellation and disposal.
Publish
Bump this package's version and the root lockfile. Publish API client
(publish-client.yml) runs when the package manifest changes on main; it can also be run manually. The workflow checks the generated schemas, runs
the SDK tests, builds and publishes using GitHub OIDC. It does not deploy AWS
services and requires no npm token or per-release browser approval.
The npm package's trusted publisher must name organization bikefinder,
repository aws_cloud, workflow publish-client.yml, with direct npm publish
allowed and no environment name. This account setup is performed once in the
package's npm settings. See npm trusted publishing.
Command HTTP and SignalR methods return AwsCommand. Requests, broker publication and execution.accepted/execution.completed are distinct stages on that resource. The client no longer joins command-result observations or exposes a separate results endpoint. A completion includes a typed response data union and authenticated receipt time.
Observation timestamps
Observation history and current readings expose measuredAt and receivedAt.
Observation IDs are opaque server-generated references. They carry no boot or
sequence information. Distinct rows can have identical capture timestamps;
clients must not infer physical ordering from their IDs. History can contain
separate rows for an at-least-once delivery of the same reading.
The source contract has changed after SDK 1.0.14. Before deploying consumers, publish the next SDK release and update their registry dependency and lockfile. Local validation can use the newly built SDK; keep that temporary override out of committed consumer dependencies.
Settings responses use the API-owned AwsSettingsSnapshot for HTTP and SignalR: read typed desired, reported, application, capabilities and schema/version evidence directly. readIssues identifies stored values the API could not interpret. The resource does not expose the underlying AWS shadow document or its independent owners. Settings fields and bounds come from the same protocol registry as the firmware.
Command reads validate stored identity, catalog arguments, artifacts, and execution evidence. A malformed single-command read returns command_unreadable; command pages keep valid items and attach per-request readIssues. Both command streams emit AwsCommandUpdate with either a validated command or a readIssue, allowing subsequent events to continue.
Supported API consumers
SDK 2.0.0 is the supported baseline after the explicitly approved pre-launch mode
alignment. It replaces the experimental powerSaving/precision mode names and
their timing setting names with standard/security. Earlier clients require a
one-time upgrade for these AWS resources. The tolerant response policy introduced
in 1.0.27 remains in place. Mode availability is an ID list, and selected mode IDs
are open strings validated against each tracker's cloud catalog. Adding or removing
catalog choices does not require another SDK release.
API CI installs the oldest supported immutable npm release selected in
tools/check-api-compatibility.mjs. It validates freshly server-serialized
Firestore, command and settings fixtures with that published reader, exercises
its HTTP transformer and stream-message schema, and checks additions at envelope
and nested-settings levels. The package includes its exported OpenAPI contract
under @bikefinder/api-client/openapi for the comparison.
The compatibility job runs oasdiff against that published contract, applying the
same tolerant-read policy to both documents. Request validation stays strict.
The gate rejects removed endpoints or response fields, incompatible types and
bounds, newly required inputs, and incompatible enum changes. Add a separate API
version for breaking changes and retain the supported v1 routes. Raising the
oldest supported SDK version is an explicit retirement decision, not a routine
way to pass CI. Dev deployment and production promotion both require this job.
Runtime validation still rejects malformed known data. Applications should retain last valid stream messages and isolate failures per resource. When patching settings, send only changed supported fields; preserve unfamiliar response fields and do not copy a full response into a write payload.