API Reference Introduction
This section documents the domain services that power Asset360 v3. The backend has fully adopted neverthrow for error handling in core flows, so service APIs now expose typed Result<Success, DomainError> responses instead of throwing exceptions. Supporting methods that still return plain promises are explicitly called out.
Service Categories
Core Services
Core services own the primary business operations inside their domains:
- Fund Service – Fund lifecycle management, fee schedules, EOD snapshots
- Investor Service – Investor identities, unit transactions, holdings
- Accounting Service – Double-entry accounting, journal entries, NAV support
- Bank Account Service – Bank account onboarding, transactions, interest accruals
- FDR Service – Fixed deposit receipt lifecycle and calculations
- Organization Service – Multi-tenant organization management
- Auth Service – Authentication, authorization, user lifecycle
Portfolio Services
Specialized services for investment portfolios and pricing:
- Equity Portfolio Service – Equity securities, trades, snapshots
- Bond Portfolio Service – Bond securities, accrued interest, valuations
- Unrealized G/L Service – Mark-to-market accounting helpers
- Market Data Service – External pricing adapters for equities and bonds
Documentation Structure
Each service page now includes:
- Overview – Responsibility and context
- Dependencies – Required repositories/services
- Public Methods – Signatures grouped by capability with
Resultsemantics - Domain Errors – Error variants a consumer should handle
- Usage Examples – Typical orchestration patterns
- Legacy Notes – Methods that still return plain promises (ongoing migration)
Core Patterns
Service Structure with Result
import { ok, err, type Result } from "neverthrow";
import {
createNotFoundError,
toBusinessLogicError,
} from "@worker/services/shared/domain-errors";
import type { DomainError } from "@worker/trpc/utils/error-mapping";
export class ServiceName {
constructor(private readonly repository: RepositoryName) {}
async findById(id: string): Promise<Result<Entity, DomainError>> {
try {
const entity = await this.repository.findById(id);
if (!entity) {
return err(createNotFoundError("Entity", id));
}
return ok(entity);
} catch (error) {
return err(toBusinessLogicError(error, "Failed to fetch entity"));
}
}
}
neverthrow gives exhaustive handling via result.match(...), .map(...), .andThen(...), and keeps error types explicit.
DomainError Model
All domain errors implement the interfaces in @worker/trpc/utils/error-mapping:
ValidationError– Invalid input (field, optionalvalue)NotFoundError– Missing resource (resource,id)AuthorizationError– Access denied (reason)BusinessLogicError– Business rule violations (codefor machine handling)- Coordinator-specific variants (for example
PortfolioValuationError) extendDomainError
Shared constructors in worker/services/shared/domain-errors.ts keep error creation consistent across services.
Consuming Services in tRPC Routers
Use executeResult() to bridge Result values to tRPC responses:
import { executeResult } from "@worker/trpc/utils/error-mapping";
export const organizationRouter = router({
getBySlug: publicProcedure
.input(z.object({ slug: z.string() }))
.query(async ({ input, ctx }) => {
return executeResult(
ctx.domainServices.organization.getOrganizationBySlug(input.slug),
);
}),
});
executeResult unwraps the Result and rethrows mapped TRPCErrors with the appropriate HTTP status codes. For procedures that still call legacy promise-based methods, keep defensive try/catch blocks until the migration finishes.
React Integration with TanStack Query
import { useTRPC } from "@/lib/trpc";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
export function FundList() {
const trpc = useTRPC();
const queryClient = useQueryClient();
const listQuery = trpc.org.funds.list.queryOptions({ orgSlug: "demo-org" });
const { data } = useQuery(listQuery);
const createMutation = trpc.org.funds.create.mutationOptions();
const { mutate: createFund } = useMutation(createMutation, {
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: trpc.org.funds.list.queryKey(),
});
},
});
// …render data + mutation handlers…
}
Testing Result-Based Services
Prefer explicit assertions over thrown-error checks:
it("returns validation error for duplicate code", async () => {
const result = await services.fund.createFund({
name: "Alpha",
code: "ALPHA",
organizationId: "org-1",
});
expect(result.isErr()).toBe(true);
expect(result._unsafeUnwrapErr().type).toBe("ValidationError");
});
Helpers in worker/__tests__/support/helpers/result.ts provide ergonomic matchers to keep tests tidy.
Working with Legacy Methods
Some orchestration flows (for example, complex unit purchase workstreams and certain migration helpers) still return plain promises and throw on failure. These sections are annotated in the individual service docs under Legacy Notes and will be migrated in Phase 2 of the neverthrow plan. When consuming those methods:
- Keep
try/catchwithtoBusinessLogicError()or custom error formatting - Never swallow errors silently—rethrow or map them to
DomainError - Prefer wrapping legacy calls inside new Result-based helpers when adding features
Null and Optional Returns
Query helpers that are intentionally nullable (for example FundService.getFundByCode, FundService.getLatestSnapshot) continue to return Promise<T | null>. Handle nullability explicitly in the caller even when Results are used elsewhere.
Using Services via the Factory
Services are still produced through createDomainServices:
import { createDomainServices } from "@worker/services";
const { fund, organization } = await createDomainServices({ db, env });
const fundResult = await fund.createFund({
name: "Growth Fund",
code: "GR01",
organizationId: "org-1",
startDate: new Date().toISOString(),
});
const fundEntity = await fundResult.unwrap(); // throws mapped TRPC error if failure
For tests, use the helpers under worker/__tests__/support to spin up isolated service instances with predictable fixtures.
Related Reading
docs/reference/money-types.md– Minor unit conventionsdocs/guidelines/coding-standards.md– Repository-wide rules (Result requirements, logging, testing)NEVERTHROW_MIGRATION_PLAN.md– see repository root for migration TODOs
Last Validated: 2025-11-10 against commit
0342a62e