@bx93tn/core-sdk v0.2.1
@bx93tn/core-sdk
SDK for the Entity Core API, providing tools for interacting with neuroscience data and models.
Installation
pnpm add @bx93tn/core-sdk zod
# or
bun add @bx93tn/core-sdk zod
# or
npm install @bx93tn/core-sdk zod
# or
yarn add @bx93tn/core-sdk zodNote:
zodare listed as peer dependencies.lru-cacheis meant to be used in node environment (future releases will allow different external cache services).- in the browser, the client use the cache api.
Design Philosophy
This SDK is built with the following principles in mind:
- Modular Architecture: The codebase is organized into distinct modules:
client: Handles the core API communication (ApiClient,RequestBuilder).functions: Contains individual, standalone functions for each API endpoint.sdk: Provides a structured, class-based SDK interface that groups related functions.models: Defines TypeScript types and Zod schemas for API request/response validation.filters: Defines TypeScript types for API query parameters.
- Type Safety: Leverages TypeScript and Zod for strong typing of function arguments, return values, and API schemas, ensuring data integrity and improving developer experience.
- Flexible Access Patterns: Offers two primary ways to interact with the API:
- A full SDK object (
EntityCoreSDK) with grouped endpoints. - Standalone functions that can be imported individually.
- A full SDK object (
- Robust Error Handling: API call results are wrapped in a
Result<T, E>type, making error handling explicit and preventing unexpected runtime errors. Anunwrap()method is provided for convenience when direct access to the successful value is desired.
Core Components
ApiClient
The ApiClient (imported from @bx93tn/core-sdk/client) is the foundation for all API interactions. It manages:
- The base URL of the API.
- Default request configurations (e.g., headers for authentication).
- Optional features like caching, request retries, and lifecycle hooks.
You need to instantiate ApiClient before using either the SDK or standalone functions.
import { ApiClient } from "@bx93tn/core-sdk/client";
const apiClient = new ApiClient("https://your-api-base-url.com", {
fetchOptions: {
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
},
// Optional configuration:
// cache: { enabled: true, ttl: 60000, maxSize: 100 },
// retry: { attempts: 3, delay: 1000 },
// debug: true,
});EntityCoreSDK
The SDK (imported from @bx93tn/core-sdk/sdk) provides a convenient, object-oriented wrapper around the ApiClient and standalone functions.
import { EntityCoreSDK } from "@bx93tn/core-sdk/sdk";
import { ApiClient } from "@bx93tn/core-sdk/client";
const apiClient = new ApiClient(/* ... */);
const sdk = new EntityCoreSDK(apiClient);
const morphology = await sdk.reconstructionMorphology.get("some-uuid").unwrap();Standalone Functions
For more granular control or potentially better tree-shaking, you can import and use functions directly (imported from @bx93tn/core-sdk/functions).
Each function requires an ApiClient instance as its first argument.
import { listReconstructionMorphologies } from "@bx93tn/core-sdk/functions";
import { ApiClient } from "@bx93tn/core-sdk/client";
const apiClient = new ApiClient(/* ... */);
const result = await listReconstructionMorphologies(apiClient, {
/* query parameters */
});
if (result.ok) {
console.log("Morphologies:", result.value);
} else {
console.error("API Error:", result.error);
}Why Offer Both SDK and Functions?
Providing both interfaces offers flexibility:
- SDK (
EntityCoreSDK):- Pros: Good discoverability of endpoints, convenient for applications using many related API calls, provides a structured, object-oriented feel.
- Cons: Might import more code than strictly necessary if only a few endpoints are used.
- Standalone Functions:
- Pros: Allows importing only the specific functions needed, potentially leading to smaller bundle sizes (better tree-shaking), aligns well with functional programming patterns.
- Cons: Requires manually passing the
ApiClientto each call, less discoverability compared to the SDK structure.
Choose the approach that best suits your project's needs and coding style.
Usage Examples
Setup
import { ApiClient } from "@bx93tn/core-sdk/client";
import { EntityCoreSDK } from "@bx93tn/core-sdk/sdk";
import {
listReconstructionMorphologies,
getReconstructionMorphology,
createReconstructionMorphology,
} from "@bx93tn/core-sdk/functions";
// 1. Use client for other purposes
const apiClient = new ApiClient("http://localhost:8000", {
fetchOptions: {
headers: { Authorization: "Bearer TOKEN" },
},
});
// 2. Instantiate the SDK with same parameters as the ApiClient
const sdk = new EntityCoreSDK("http://localhost:8000", {
debug: true,
cache: { server: true },
retry: { attempts: 5, backoff: "exponential", shouldRetry: () => true },
fetchOptions: {
headers: { Authorization: "Bearer TOKEN" },
},
});Using the SDK
async function useSDK() {
// List items (using flat access and unwrap)
console.log("Fetching list via SDK...");
const listData = await sdk.reconstructionMorphology
.list({ page: 1, page_size: 20 })
.unwrap();
// Access unwrapped data directly
// Get a specific item (using grouped access)
const morphologyId = listData.items[0]?.id;
if (!morphologyId) {
console.log("No morphology ID found to fetch.");
return;
}
const getItemData = await sdk.reconstructionMorphology
.get(morphologyId)
.unwrap();
console.log("Single Item (SDK):", getItemData);
// Create an item
const newItemData = {
name: "New Morphology SDK" /* other required fields */,
};
const createdItem = await sdk.reconstructionMorphology
.create({ ...newItemData })
.unwrap();
}
useSDK();Using Standalone Functions
async function useFunctions() {
const apiClient = new ApiClient("http://localhost:8000/api/", {/* client config */})
// or you can use the EntityCoreSDK.apiClient
// List items
const listResult = await listReconstructionMorphologies(apiClient, { page: 1, page_size: 20 } });
if (listResult.ok) {
const morphologyId = listResult.value.items[0]?.id;
if (!morphologyId) {
console.log("No morphology ID found to fetch.");
return;
}
}
const getResult = await getReconstructionMorphology(apiClient, "uuid");
if (getResult.ok) {
console.log('Single Item (Functions):', getResult.value);
} else {
console.error('Function Error (Get):', getResult.error);
}
// Create an item
const newItemData = { name: "New Morphology Function", /* other required fields */ };
const createResult = await createReconstructionMorphology(apiClient, { ...newItemData });
if (createResult.ok) {
console.log('Created Item (Functions):', createResult.value);
} else {
console.error('Function Error (Create):', createResult.error);
}
}
useFunctions();Result Handling
All API operations (both SDK methods and standalone functions) return a Promise<Result<T, E>>.
Result.ok: A boolean indicating success (true) or failure (false).Result.value: The successful data payload (ifokistrue).Result.error: The error object (ifokisfalse).
You can handle results manually:
const result = await sdk.reconstructionMorphology.list();
if (result.ok) {
console.log(result.value);
} else {
console.error("Error:", result.error);
}Or use the convenient .unwrap() method provided on the Result object, which returns the value on success or throws the error on failure:
try {
const data = await sdk.reconstructionMorphology.list().unwrap();
console.log(data);
} catch (error) {
console.error("Caught error:", error);
}Development
- Build:
pnpm run build(usestshyfor dual CJS/ESM output) - Lint:
pnpm run lint - Format:
pnpm run format - Check All:
pnpm run check - Check Package Exports:
pnpm run check-exports