-
Notifications
You must be signed in to change notification settings - Fork 0
Contributing
The SDK uses two patterns for API operations. Choose the right pattern based on your use case:
Use createApiOperation() for standard REST endpoints with no special logic:
// src/clients/ledger-json-api/operations/v2/version/get.ts
import { z } from 'zod';
import { createApiOperation } from '../../../../../core';
export const GetVersion = createApiOperation<void, GetVersionResponse>({
paramsSchema: z.void(),
method: 'GET',
buildUrl: (_params: void, apiUrl: string) => `${apiUrl}/v2/version`,
});When to use:
- Simple REST endpoints (GET, POST, DELETE, PATCH)
- No async logic needed before making the request
- No client method calls required (e.g.,
getLedgerEnd()) - Response transformation is simple or not needed
Benefits:
- Concise and declarative
- Consistent structure across operations
- Auto-generates JSDoc from operation files
Use createWebSocketOperation() for simple WebSocket subscriptions:
// src/clients/ledger-json-api/operations/v2/commands/subscribe-to-completions.ts
import { createWebSocketOperation } from '../../../../../core/operations/WebSocketOperationFactory';
export const SubscribeToCompletions = createWebSocketOperation<Params, Request, Message>({
paramsSchema: CompletionStreamRequestSchema,
buildPath: (_params, _apiUrl) => '/v2/commands/completions',
buildRequestMessage: (params, client) => ({
userId: params.userId ?? client.getUserId(),
parties: params.parties.length > 0 ? params.parties : client.buildPartyList(),
beginExclusive: params.beginExclusive,
}),
});When to use:
- WebSocket endpoints with simple request/response patterns
- No complex state management needed
- Connection lifecycle is straightforward
Note: Factory-pattern WebSocket operations still require manual connection handling via the returned subscription object. Unlike REST operations (fire-and-forget), you must manage the subscription lifecycle:
const subscription = await client.subscribeToCompletions(params, {
onMessage: (msg) => console.log(msg),
onError: (err) => console.error(err),
onClose: () => console.log('Connection closed'),
});
// Later: subscription.close() to disconnectUse classes extending ApiOperation when you need:
- Async pre-processing (e.g., fetching defaults before the main request)
-
Client method calls (e.g.,
client.getLedgerEnd(),client.getPartyId()) - Complex response aggregation (e.g., pagination, streaming results)
- WebSocket with state management (e.g., connection lifecycle, error handling)
// REST with async defaults
export class GetMemberTrafficStatus extends ApiOperation<Params, Response> {
public async execute(params: Params): Promise<Response> {
let { domainId } = params;
if (!domainId) {
// Needs async call to determine default
domainId = await getCurrentMiningRoundDomainId(this.client);
}
// ... make request
}
}
// WebSocket with complex lifecycle
export class SubscribeToUpdates {
constructor(private readonly client: LedgerJsonApiClient) {}
public async connect(params: Params): Promise<void> {
// Fetch ledger end if not provided
let { beginExclusive } = params;
if (beginExclusive === undefined) {
const ledgerEnd = await this.client.getLedgerEnd({});
beginExclusive = ledgerEnd.offset;
}
// ... complex WebSocket handling
}
}Current class-based operations:
-
GetActiveContracts— Uses WebSocket internally but exposes a simple async API; supports streaming callbacks and aggregates results until connection closes -
SubscribeToUpdates— Long-running WebSocket with complex message handling, error recovery, and async pre-processing to fetchledgerEndif not provided -
GetMemberTrafficStatus— Requires async call togetCurrentMiningRoundDomainId()before making the request whendomainIdis not provided -
GetParties/ListParties— UsesfetchAllParties()helper for automatic pagination across multiple API calls
| Scenario | Pattern |
|---|---|
| Simple REST GET/POST | Factory (createApiOperation) |
| REST with async defaults | Class extending ApiOperation
|
| Simple WebSocket subscription | Factory (createWebSocketOperation) |
| WebSocket with streaming/callbacks | Class with connect() method |
| Pagination/aggregation | Class with custom execute()
|
This package is automatically published via CI/CD when changes are pushed to the main branch. The publishing workflow:
- Runs on every push to the
mainbranch - Automatically increments the patch version
- Publishes to GitHub Packages
- Creates a git tag for the release
No manual publishing.
The publishing workflow requires the following environment setup:
-
GitHub Token: The workflow uses
GITHUB_TOKENwhich is automatically provided by GitHub Actions -
Repository Permissions: The workflow requires:
-
contents: read- to checkout code -
packages: write- to publish to GitHub Packages
-
These permissions are configured in the workflow file and should not need manual setup.
The following repository secrets must be configured:
| Secret | Purpose | Required Permissions |
|---|---|---|
PAT_TOKEN |
Push lint auto-fix commits on PRs |
contents:write, metadata:read
|
NPM_TOKEN |
Publish packages to NPM | NPM automation token |
PAT_TOKEN Details:
The PAT_TOKEN is a Personal Access Token used by the test-cn-quickstart workflow to push
automatic lint fixes back to PR branches. This is required because commits made with the default
GITHUB_TOKEN don't trigger subsequent workflow runs (by design, to prevent infinite loops).
To create the PAT:
- Go to GitHub Settings → Developer settings → Personal access tokens → Fine-grained tokens
- Create a token with:
- Repository access: This repository only
- Permissions: Contents (Read and write), Metadata (Read)
- Add the token as a repository secret named
PAT_TOKEN
Note: The token should ideally be owned by a bot/service account rather than a personal account.