@danainnovations/directory
v4.1.0
Published
Sonance employee directory — zero-config React hooks and async functions
Readme
@danainnovations/directory
Sonance employee directory SDK. Zero-config access to the company's active directory (synced live from Okta/AD via SCIM).
Install
npm install @danainnovations/directoryNo environment variables or API keys needed. Just install and use.
User identity — the one rule
The canonical user identifier is oktaSub (the Okta OIDC sub). It is stable, immutable, and opaque.
// ✅ Correct — join on the canonical identifier
const link = { userOktaSub: user.oktaSub, projectId }
// ❌ Wrong — emails and display names change
const link = { userEmail: user.email, projectId }For SSO flows: match jwt.sub === user.oktaSub.
For manager relationships: prefer oManagerId (the manager's oktaSub) over managerId (which is actually the manager's email despite the legacy name).
externalId is kept as an alias of oktaSub for v4.0.x consumers — same value, prefer oktaSub in new code.
React Hooks
import { useUsers, useUser, useDepartments, useOrgChart } from '@danainnovations/directory/react'
// Search and browse employees
const { users, total, loading, error } = useUsers({ search: 'john', department: 'Engineering', page: 1, limit: 20 })
// Look up a single user by email or ID
const { user, loading, error } = useUser('[email protected]')
// Get all departments with employee counts
const { departments, loading, error } = useDepartments()
// Get the full org chart (manager hierarchy tree)
const { tree, loading, error } = useOrgChart()Async Functions
For server-side, API routes, scripts, or non-React code:
import { getUsers, getUser, getDepartments, getOrgChart } from '@danainnovations/directory'
const result = await getUsers({ search: 'sales', department: 'Sales', page: 1, limit: 50 })
// result = { data: DirectoryUser[], total: number, page: number, limit: number, totalPages: number }
const user = await getUser('[email protected]') // or pass a user ID
// user = DirectoryUser | null
const departments = await getDepartments()
// departments = [{ name: 'Engineering', count: 42 }, ...]
const tree = await getOrgChart()
// tree = OrgNode[] (recursive tree with .reports[])Types
interface DirectoryUser {
id: string
email: string
full_name: string | null
given_name: string | null
family_name: string | null
department: string | null
job_title: string | null
location: string | null
phone_number: string | null
avatar_url: string | null
manager_email: string | null
role: string | null
}
interface Department {
name: string
count: number
}
interface OrgNode {
id: string
email: string
name: string
title: string | null
department: string | null
reports: OrgNode[] // direct reports (recursive)
}
interface UserSearchParams {
search?: string // searches name, email, department
department?: string // exact department match
page?: number // default: 1
limit?: number // default: 50, max: 100
}How It Works
This package connects directly to a Supabase database that receives live SCIM provisioning from Okta. The data updates automatically whenever IT makes changes in Okta/Active Directory (new hires, terminations, department changes, etc.). Only active employees are returned — non-person accounts (service accounts, rooms, test users) are filtered out by row-level security.
AI Integration Notes
When integrating this package into a project:
- Use the React hooks (
useUsers,useUser,useDepartments,useOrgChart) for client components - Use the async functions (
getUsers,getUser,getDepartments,getOrgChart) for server components, API routes, or scripts - All hooks return
{ loading, error }alongside the data - The
useUsershook re-fetches automatically when search params change - No providers, context, or setup needed — just import and use
- For people pickers / autocomplete, use
useUsers({ search: inputValue })with a debounced input - For org charts,
useOrgChart()returns a tree — each node has a.reports[]array of direct reports - For department filters,
useDepartments()returns departments sorted alphabetically with employee counts
