TypeScript
The TypeScript client comes in two flavors: a Node.js library and a browser library. They share the same conceptual surface (IngestClient, OutletClient, ApiClient, LineageClient) but are built on different stacks.
- Node.js —
@dataspine/typescript-node-client-library. gRPC over HTTP/2 via@connectrpc/connect-nodeand REST via the platformfetch. - Browser —
@dataspine/typescript-browser-client-library. gRPC-Web viagrpc-web.
Per-data-product clients are generated by the Dataspine code generator from your published manifest and depend on the runtime library above. The placeholder used throughout this page is a data product com.acme.orders published by an organization acme, which produces the per-data-product npm package @acme/orders-node-client (or @acme/orders-browser-client).
Installation
Node.js:
npm install @dataspine/typescript-node-client-library @acme/orders-node-client
Browser:
npm install @dataspine/typescript-browser-client-library @acme/orders-browser-client
A single application typically depends on the runtime library transitively (it is a peer dependency of the per-data-product client) and pins compatible versions of both.
Configuration
The runtime library uses a ConfigLoader that resolves configuration from node-config, environment variables (prefixed DATASPINE_), and an optional inline overrides object. The minimum input is:
clientName— a free-form identifier for your application, used in logs and tracing.region— the Dataspine region the data product is deployed in.auth— one of:{ type: 'static-token', token }{ type: 'token-file', tokenFile }{ type: 'token-provider', tokenProvider }— your ownTokenProviderimplementation.{ type: 'exchange-token', tokenExchangeEndpoint, refreshInterval, inputToken }— STS-style token exchange.{ type: 'aws-sigv4', tokenExchangeEndpoint, principalId, refreshInterval, credentialsProvider? }{ type: 'ssh-ed25519', tokenExchangeEndpoint, principalId, privateKeyPath, publicKeyPath, refreshInterval }
The endpoint URLs (ingestEndpointUrl, outletEndpointUrl, apiGrpcEndpointUrl, apiRestEndpointUrl) are usually provided by the surrounding configuration so callers do not have to know them.
import { ConfigLoader } from '@dataspine/typescript-node-client-library';
import { UUID } from '@dataspine/typescript-node-client-library';
const config = await new ConfigLoader().load(
{ dataProductId: UUID.parse('00000000-0000-0000-0000-000000000000') },
{
clientName: 'orders-app',
region: 'eu-central-1',
auth: { type: 'token-file', tokenFile: '/var/run/secrets/dataspine/token' },
},
);
config is reusable across the typed clients you instantiate from it.
Per-data-product typed client
The generated package wires each declared API operation, ingest, and outlet to a typed method. The shape:
import { OrdersClient } from '@acme/orders-node-client';
const ordersClient = await OrdersClient.create(config);
OrdersClient.create instantiates the underlying IngestClient, OutletClient, and ApiClient for that data product and sets up the operation IDs.
API call
import { PlaceOrder, Order } from '@acme/orders-node-client';
const result = await ordersClient.placeOrder(
PlaceOrder.create({
customerId: '00000000-0000-0000-0000-000000000000',
items: [{ sku: 'BOOK-001', quantity: 1 }],
}),
);
if (result.isError) {
console.error('placeOrder failed:', result.errorMessage);
} else {
const order: Order = result.payload;
console.log('placed order', order.id);
}
The typed client wraps ApiResponse<T> (a ApiSuccessResponse<T> | ApiErrorResponse) with helpers for the ApiStatusCode mapping documented under gRPC (wire reference).
Ingest
const ingest = await ordersClient.openOrderIngest();
const batch = await ingest.beginBatch();
await batch.append({ customerId: '...', items: [...] });
await batch.append({ customerId: '...', items: [...] });
await batch.end();
await ingest.close();
For transactional producers the typed client also exposes beginRootTransaction / commit / abort, which map to the equivalent AppendMessageCommand envelopes documented in gRPC (wire reference).
Outlet
const subscription = ordersClient.subscribeToOrderEvents({
start: { type: 'Latest' },
});
for await (const event of subscription.poll(1000)) {
if (event.type === 'message') {
console.log('order event', event.offset, event.message);
} else if (event.type === 'errorCode') {
console.error('outlet error', event.errorCode);
}
}
subscription.close();
The typed wrapper handles message chunking and the pollCompleted envelope; consumers see one event.message per logical message.
Browser specifics
The browser client has the same conceptual API but:
OutletClientusesgrpc-webstreaming and exposes the samepoll(durationMillis)async iterator.ApiClientusesgrpc-webunary RPCs.RestApiClient(Node-only today) is replaced byfetchcalls in the browser bundle.- Token providers in the browser typically come from your auth context (e.g. an OIDC PKCE flow). The
static-tokenandtoken-providerauth types are the most common in browsers.
import { OrdersClient } from '@acme/orders-browser-client';
import { ConfigLoader } from '@dataspine/typescript-browser-client-library';
const config = new ConfigLoader().load({
clientName: 'orders-spa',
region: 'eu-central-1',
endpointUrl: 'https://orders.acme.example',
auth: { type: 'token-provider', tokenProvider: oidcTokenProvider },
});
const ordersClient = await OrdersClient.create(config);
Reauthentication
The runtime library subscribes to the TokenProvider and pushes a Reauthenticate envelope on every active ingest and outlet stream when the token rotates. You generally do not need to do anything; if your token provider blocks (e.g. your AWS SigV4 exchange momentarily fails), the client logs a warning and continues using the last valid token.
REST escape hatch
For raw HTTP calls (e.g. an API operation marked supports_raw_response, where the response is a non-Dataspine media type like a PDF), the Node runtime exposes RestApiClient:
import { RestApiClient } from '@dataspine/typescript-node-client-library';
const rest = new RestApiClient(tokenProvider, 'https://orders.acme.example', logger);
const stream = await rest.rawCall(
PlaceOrder.serialize,
'/api/v1/com.acme.orders.GetReceiptPdf@v1',
{ orderId: '00000000-0000-0000-0000-000000000000' },
);
stream is a ReadableStream<Uint8Array> so you can pipe the response into a file or another HTTP response without buffering it. This matches the raw HTTP response flow described in the wire reference.