@havena/esm-core-api
v1.0.1
Published
Core API library for ESM
Readme
@havena/esm-core-api
Core ESM API library providing authentication, HTTP client utilities, React hooks, and type definitions for the Haven ESM platform.
Installation
npm install @havena/esm-core-api
# or
yarn add @havena/esm-core-apiOverview
This package provides a comprehensive set of APIs and utilities for building applications on the Haven ESM platform:
- Authentication: Better Auth integration with organization, admin, and multi-session support
- HTTP Client: Axios-based API client with SWR integration
- React Hooks: Custom hooks for session management, permissions, and data fetching
- Storage: Local storage utilities
- Utilities: URL construction, object manipulation, and helper functions
- Types: TypeScript type definitions for the platform
Core Features
Authentication API
The authentication API is built on top of Better Auth and provides a comprehensive authentication system with organization support, admin roles, and multi-session capabilities.
Auth Client
import { Auth } from "@havena/esm-core-api";
// Access the auth client
const { data: session } = Auth.client.useSession();
// Sign out with confirmation
Auth.confirmedSignOut();
// Delete account with confirmation
Auth.confirmedDeleteAccount();Features:
- Organization management with teams
- Admin role-based access control
- Username-based authentication
- Anonymous user support
- API key authentication
- Multi-session support
- JWT token support
Auth ACL (Access Control Lists)
The package includes predefined ACL configurations for organization and admin plugins, providing role-based permission management.
Hive API
The Hive API provides HTTP client functionality, data fetching hooks, and API utilities.
API Configuration Provider
Wrap your application with ApiConfigProvider to configure SWR with the API fetcher:
import { ApiConfigProvider } from "@havena/esm-core-api";
function App() {
return <ApiConfigProvider>{/* Your app components */}</ApiConfigProvider>;
}API Fetch
Make HTTP requests using the apiFetch function:
import { apiFetch } from "@havena/esm-core-api";
// GET request
const response = await apiFetch("/users", {
method: "GET",
});
// POST request
const response = await apiFetch("/users", {
method: "POST",
data: { name: "John Doe", email: "[email protected]" },
});
// With custom headers
const response = await apiFetch("/protected", {
method: "GET",
headers: {
Authorization: "Bearer token",
},
});Use API Hook
A wrapper around SWR for data fetching:
import { useApi } from "@havena/esm-core-api";
function UserList() {
const { data, error, isLoading } = useApi("/users");
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{data?.data?.results?.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}URL Construction
Build URLs with query parameters:
import { constructUrl } from "@havena/esm-core-api";
// Basic usage
const url = constructUrl("/api/users", { id: 123, active: true });
// → '/api/users?id=123&active=true'
// Array values
const url = constructUrl("/api/search", {
tags: ["javascript", "typescript"],
limit: 10,
});
// → '/api/search?tags=javascript&tags=typescript&limit=10'
// With merge strategy
const url = constructUrl(
"/api/data?sort=name",
{ sort: "date", limit: 5 },
{ mergeStrategy: "preserve" }
);
// → '/api/data?sort=name&limit=5' (preserves existing 'sort')UrlBuilder Class:
import { UrlBuilder } from "@havena/esm-core-api";
const url = new UrlBuilder("/api/users")
.param("page", 1)
.param("limit", 10)
.paramIf(includeInactive, "includeInactive", true)
.build();File Upload
Upload files with progress tracking:
import { uploadFiles } from "@havena/esm-core-api";
const result = await uploadFiles({
files: {
avatar: [file1, file2],
documents: [file3],
},
onProgress: (progress) => {
console.log(`Upload progress: ${progress}%`);
},
relatedModelName: "User",
relatedModelId: "123",
purpose: "profile",
tags: ["avatar", "profile"],
});
// Get file URL
import { getHiveFileUrl } from "@havena/esm-core-api";
const fileUrl = getHiveFileUrl(result.files[0].blob.storagePath);HTTP Client
Direct access to the configured Axios instance:
import { httpClient } from "@havena/esm-core-api";
const response = await httpClient.get("/users");
const response = await httpClient.post("/users", { name: "John" });React Hooks
Session Management
import { useSession, SessionProvider } from "@havena/esm-core-api";
// Wrap your app with SessionProvider
function App() {
return (
<SessionProvider value={{ user, session }}>
<YourComponents />
</SessionProvider>
);
}
// Use session in components
function Profile() {
const { user, session } = useSession();
return <div>Welcome, {user?.name}</div>;
}User Search
import { useSearchUser } from "@havena/esm-core-api";
function UserSearch() {
const { users, isLoading, setFilters, filters } = useSearchUser();
return (
<div>
<input
onChange={(e) => setFilters({ search: e.target.value })}
placeholder="Search users..."
/>
{isLoading && <div>Loading...</div>}
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}System Access Control
Check system-level permissions (server-side validation):
import { useUserHasSystemAccess } from "@havena/esm-core-api";
function AdminPanel() {
const { hasAccess, isLoading } = useUserHasSystemAccess({
user: ["impersonate", "delete"],
category: ["create"],
});
if (isLoading) return <Loader />;
return (
<>
{hasAccess && <ImpersonateUserButton />}
{hasAccess && <DeleteUserButton />}
</>
);
}Client-side permission check (for UI optimization only):
import { useUserHasSystemAccessSync } from "@havena/esm-core-api";
function Menu() {
const { hasAccess, isLoading } = useUserHasSystemAccessSync({
user: ["impersonate"],
});
// This is for UI only - backend must validate!
return <Menu>{hasAccess && <ImpersonateMenuItem />}</Menu>;
}Organization Access Control
Check organization-level permissions:
import { useUserHasOrganizationAccess } from "@havena/esm-core-api";
function PropertyManagement() {
const { hasAccess, isLoading } = useUserHasOrganizationAccess({
property: ["create", "update"],
file: ["upload"],
});
if (isLoading) return <Loader />;
return (
<>
{hasAccess && <CreatePropertyButton />}
{hasAccess && <UploadFileButton />}
</>
);
}Search Parameters
Manage URL search parameters (React Router v5 compatible):
import { useSearchParams } from "@havena/esm-core-api";
function FilterComponent() {
const [searchParams, setSearchParams] = useSearchParams();
const page = searchParams.get("page") || "1";
const sort = searchParams.get("sort") || "name";
const handleSort = (newSort: string) => {
setSearchParams({ ...Object.fromEntries(searchParams), sort: newSort });
};
return (
<select value={sort} onChange={(e) => handleSort(e.target.value)}>
<option value="name">Name</option>
<option value="date">Date</option>
</select>
);
}Extended version with utility methods:
import { useSearchParamsExtended } from "@havena/esm-core-api";
function AdvancedFilters() {
const {
searchParams,
getParam,
setParam,
deleteParam,
updateParams,
clearParams,
getParamsObject,
} = useSearchParamsExtended();
return (
<div>
<button onClick={() => setParam("filter", "active")}>Set Filter</button>
<button onClick={() => deleteParam("filter")}>Remove Filter</button>
<button onClick={() => updateParams({ page: "1", sort: "name" })}>
Update Multiple
</button>
<button onClick={clearParams}>Clear All</button>
</div>
);
}Typed search parameters:
import { useTypedSearchParams, searchParamUtils } from "@havena/esm-core-api";
function TypedFilters() {
const [params, setParams, clearParams] = useTypedSearchParams({
page: {
parse: searchParamUtils.parseNumber,
serialize: String,
defaultValue: 1,
},
active: {
parse: searchParamUtils.parseBoolean,
serialize: String,
defaultValue: false,
},
tags: {
parse: searchParamUtils.parseArray,
serialize: searchParamUtils.serializeArray,
defaultValue: [],
},
});
return (
<div>
<p>Page: {params.page}</p>
<p>Active: {params.active ? "Yes" : "No"}</p>
<p>Tags: {params.tags.join(", ")}</p>
<button onClick={() => setParams({ page: params.page + 1 })}>
Next Page
</button>
</div>
);
}Storage
Local Storage Hook
import { useLocalStorage } from "@havena/esm-core-api";
function Preferences() {
const [theme, setTheme] = useLocalStorage<string>("theme");
return (
<select value={theme || "light"} onChange={(e) => setTheme(e.target.value)}>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
);
}Utilities
Object Manipulation
import {
flattenObject,
unflattenArray,
flattenObjectToPairs,
unflattenPairsToObject,
} from "@havena/esm-core-api";
// Flatten nested objects
const nested = { user: { name: "John", age: 30 } };
const flattened = flattenObject(nested);
// → ['user.name', '"John"', 'user.age', '30']
// Unflatten back
const restored = unflattenArray(flattened);
// → { user: { name: 'John', age: 30 } }
// Flatten to key-value pairs
const pairs = flattenObjectToPairs(nested);
// → [['user.name', 'John'], ['user.age', 30]]String Utilities
import {
getNameInitials,
getNameInitialsFromEmail,
parseDate,
} from "@havena/esm-core-api";
const initials = getNameInitials("John Doe"); // → 'JD'
const emailInitials = getNameInitialsFromEmail("[email protected]"); // → 'JD'
const date = parseDate("2024-01-01"); // → Date objectPath Utilities
import { extractIdFromPath } from "@havena/esm-core-api";
// Extract ID from path
const id = extractIdFromPath("/dashboard/users/123", "/dashboard/users");
// → '123'Type Utilities
import { Path, FieldPath } from "@havena/esm-core-api";
type User = {
name: string;
address: {
city: string;
zip: number;
};
};
type UserPaths = Path<User>;
// → 'name' | 'address' | 'address.city' | 'address.zip'Types
The package exports comprehensive TypeScript types:
import type {
User,
Session,
UserSession,
Organization,
OrganizationRole,
Member,
Team,
TeamMember,
Invitation,
APIFetchInit,
APIFetchResponse,
APIFetchError,
PaginationControls,
APIListResponse,
FileUploadPayload,
FileUploadResultsPayload,
FileBlob,
FileMetadata,
} from "@havena/esm-core-api";API Reference
Authentication
Auth.client- Better Auth client instanceAuth.confirmedSignOut()- Sign out with confirmation modalAuth.confirmedDeleteAccount()- Delete account with confirmation modalAuth.pluginOptions- Plugin configuration options
HTTP Client
apiFetch<T, K>(url, options?)- Make HTTP requestshttpClient- Axios instance with base URL configuredApiConfigProvider- React provider for SWR configurationuseApi<T>(resource, fetcher?, options?)- SWR hook wrappermutate- SWR mutate functionhandleApiErrors- Error handling utility
URL Utilities
constructUrl(path, params?, options?)- Build URLs with query parametersUrlBuilder- Fluent URL builder class
File Operations
uploadFiles(options, requestParams?)- Upload files with progress trackingcleanFiles(paths)- Delete files by pathsgetHiveFileUrl(path)- Get streaming URL for media files
Hooks
useSession()- Access user session contextuseSearchUser()- Search users with debouncinguseUserHasSystemAccess(permissions)- Server-side system permission checkuseUserHasSystemAccessSync(permissions)- Client-side system permission checkuseUserHasOrganizationAccess(permissions)- Server-side organization permission checkuseSearchParams()- Manage URL search parametersuseSearchParamsExtended()- Extended search params with utilitiesuseTypedSearchParams(schema)- Typed search parameters
Storage
useLocalStorage<T>(key)- Local storage hook
Utilities
flattenObject(obj, parentKey?)- Flatten nested objects to arrayunflattenArray(arr)- Restore object from flattened arrayflattenObjectToPairs(obj, parentKey?)- Flatten to key-value pairsunflattenPairsToObject(pairs)- Restore object from pairsgetNameInitials(name)- Get initials from namegetNameInitialsFromEmail(email)- Get initials from emailparseDate(date?, defaultNow?)- Parse date stringextractIdFromPath(pathname, basePath?)- Extract ID from URL pathsleep(milliseconds)- Promise-based delayPath<T>- Type utility for object pathsFieldPath<T>- Type utility for form field paths
Peer Dependencies
This package requires the following peer dependencies:
react^18.3.1react-dom^18.3.1react-router^5.3.4react-router-dom^5.3.4@mantine/core^8.3.4@mantine/hooks^8.3.4@mantine/notifications^8.3.4@mantine/modals^8.3.4swr^2.3.6zustand^5.0.8
Security Notes
⚠️ Important Security Considerations:
Permission Checks: Always use server-side permission checks (
useUserHasSystemAccess,useUserHasOrganizationAccess) for security-critical operations. Client-side checks (useUserHasSystemAccessSync) are for UI optimization only and can be bypassed.API Validation: The backend MUST validate all permissions and access control before executing any operations. Never rely solely on client-side checks.
Authentication: The auth client handles authentication tokens automatically. Ensure your backend properly validates these tokens.
