Frontend Services API
Complete API reference for Effect-TS service layer implementations across all domains.
Service Architecture
All services follow the standardized Effect-TS pattern with dependency injection using Context.Tag.
Base Service Interface Pattern
export interface DomainService {
readonly createEntity: (
input: CreateEntityInput,
) => Effect.Effect<UIEntity, DomainError>;
readonly getAllEntities: () => Effect.Effect<UIEntity[], DomainError>;
readonly getEntity: (
hash: ActionHash,
) => Effect.Effect<UIEntity | null, DomainError>;
readonly updateEntity: (
hash: ActionHash,
input: UpdateEntityInput,
) => Effect.Effect<UIEntity, DomainError>;
readonly deleteEntity: (hash: ActionHash) => Effect.Effect<void, DomainError>;
}
Service Implementation Pattern
export const makeDomainService = Effect.gen(function* () {
const client = yield* HolochainClientService;
const createEntity = (input: CreateEntityInput) =>
Effect.gen(function* () {
const record = yield* client.callZome({
zome_name: "domain",
fn_name: "create_entity",
payload: input,
});
const entity = createUIEntity(record);
if (!entity) {
yield* Effect.fail(
DomainError.create("Failed to create UI entity from record"),
);
}
return entity;
}).pipe(
Effect.mapError((error) =>
DomainError.fromError(error, DOMAIN_CONTEXTS.CREATE_ENTITY),
),
Effect.withSpan("DomainService.createEntity"),
);
return { createEntity /* ... other methods */ };
});
Domain Services
Service Types Service
File: ui/src/lib/services/zomes/serviceTypes.service.ts
Interface
export interface ServiceTypeService {
readonly createServiceType: (
input: CreateServiceTypeInput,
) => Effect.Effect<UIServiceType, ServiceTypeError>;
readonly getAllServiceTypes: () => Effect.Effect<
UIServiceType[],
ServiceTypeError
>;
readonly getServiceType: (
hash: ActionHash,
) => Effect.Effect<UIServiceType | null, ServiceTypeError>;
readonly updateServiceType: (
hash: ActionHash,
input: UpdateServiceTypeInput,
) => Effect.Effect<UIServiceType, ServiceTypeError>;
readonly deleteServiceType: (
hash: ActionHash,
) => Effect.Effect<void, ServiceTypeError>;
readonly approveServiceType: (
hash: ActionHash,
) => Effect.Effect<UIServiceType, ServiceTypeError>;
readonly rejectServiceType: (
hash: ActionHash,
) => Effect.Effect<UIServiceType, ServiceTypeError>;
readonly searchServiceTypes: (
query: string,
) => Effect.Effect<UIServiceType[], ServiceTypeError>;
readonly getServiceTypesByTag: (
tag: string,
) => Effect.Effect<UIServiceType[], ServiceTypeError>;
}
Usage
// Dependency injection
export const ServiceTypeService =
Context.GenericTag<ServiceTypeService>("ServiceTypeService");
// Layer definition
export const ServiceTypeServiceLive = Layer.effect(
ServiceTypeService,
makeServiceTypeService,
).pipe(Layer.provide(HolochainClientServiceLive));
// Usage in stores or composables
const serviceTypes = await Effect.runPromise(
Effect.gen(function* () {
const service = yield* ServiceTypeService;
return yield* service.getAllServiceTypes();
}).pipe(Effect.provide(ServiceTypeServiceLive)),
);
Requests Service
File: ui/src/lib/services/zomes/requests.service.ts
Interface
export interface RequestService {
readonly createRequest: (
input: CreateRequestInput,
) => Effect.Effect<UIRequest, RequestError>;
readonly getAllRequests: () => Effect.Effect<UIRequest[], RequestError>;
readonly getRequest: (
hash: ActionHash,
) => Effect.Effect<UIRequest | null, RequestError>;
readonly updateRequest: (
hash: ActionHash,
input: UpdateRequestInput,
) => Effect.Effect<UIRequest, RequestError>;
readonly deleteRequest: (
hash: ActionHash,
) => Effect.Effect<void, RequestError>;
readonly fulfillRequest: (
hash: ActionHash,
) => Effect.Effect<UIRequest, RequestError>;
readonly closeRequest: (
hash: ActionHash,
) => Effect.Effect<UIRequest, RequestError>;
readonly searchRequests: (
query: string,
) => Effect.Effect<UIRequest[], RequestError>;
readonly getRequestsByServiceType: (
serviceTypeHash: ActionHash,
) => Effect.Effect<UIRequest[], RequestError>;
}
Offers Service
File: ui/src/lib/services/zomes/offers.service.ts
Interface
export interface OfferService {
readonly createOffer: (
input: CreateOfferInput,
) => Effect.Effect<UIOffer, OfferError>;
readonly getAllOffers: () => Effect.Effect<UIOffer[], OfferError>;
readonly getOffer: (
hash: ActionHash,
) => Effect.Effect<UIOffer | null, OfferError>;
readonly updateOffer: (
hash: ActionHash,
input: UpdateOfferInput,
) => Effect.Effect<UIOffer, OfferError>;
readonly deleteOffer: (hash: ActionHash) => Effect.Effect<void, OfferError>;
readonly acceptOffer: (
hash: ActionHash,
) => Effect.Effect<UIOffer, OfferError>;
readonly closeOffer: (hash: ActionHash) => Effect.Effect<UIOffer, OfferError>;
readonly searchOffers: (
query: string,
) => Effect.Effect<UIOffer[], OfferError>;
readonly getOffersByServiceType: (
serviceTypeHash: ActionHash,
) => Effect.Effect<UIOffer[], OfferError>;
}
Users Service
File: ui/src/lib/services/zomes/users.service.ts
Interface
export interface UsersService {
readonly createUser: (
input: CreateUserInput,
) => Effect.Effect<UIUser, UserError>;
readonly getAllUsers: () => Effect.Effect<UIUser[], UserError>;
readonly getUser: (
hash: ActionHash,
) => Effect.Effect<UIUser | null, UserError>;
readonly updateUser: (
hash: ActionHash,
input: UpdateUserInput,
) => Effect.Effect<UIUser, UserError>;
readonly deleteUser: (hash: ActionHash) => Effect.Effect<void, UserError>;
readonly getUserProfile: (
agentHash: AgentPubKey,
) => Effect.Effect<UIUser | null, UserError>;
readonly searchUsers: (query: string) => Effect.Effect<UIUser[], UserError>;
}
Organizations Service
File: ui/src/lib/services/zomes/organizations.service.ts
Interface
export interface OrganizationService {
readonly createOrganization: (
input: CreateOrganizationInput,
) => Effect.Effect<UIOrganization, OrganizationError>;
readonly getAllOrganizations: () => Effect.Effect<
UIOrganization[],
OrganizationError
>;
readonly getOrganization: (
hash: ActionHash,
) => Effect.Effect<UIOrganization | null, OrganizationError>;
readonly updateOrganization: (
hash: ActionHash,
input: UpdateOrganizationInput,
) => Effect.Effect<UIOrganization, OrganizationError>;
readonly deleteOrganization: (
hash: ActionHash,
) => Effect.Effect<void, OrganizationError>;
readonly addMember: (
orgHash: ActionHash,
userHash: ActionHash,
) => Effect.Effect<void, OrganizationError>;
readonly removeMember: (
orgHash: ActionHash,
userHash: ActionHash,
) => Effect.Effect<void, OrganizationError>;
readonly searchOrganizations: (
query: string,
) => Effect.Effect<UIOrganization[], OrganizationError>;
}
Administration Service
File: ui/src/lib/services/zomes/administration.service.ts
Interface
export interface AdministrationService {
readonly promoteToAdmin: (
userHash: ActionHash,
) => Effect.Effect<void, AdministrationError>;
readonly demoteFromAdmin: (
userHash: ActionHash,
) => Effect.Effect<void, AdministrationError>;
readonly promoteToModerator: (
userHash: ActionHash,
) => Effect.Effect<void, AdministrationError>;
readonly demoteFromModerator: (
userHash: ActionHash,
) => Effect.Effect<void, AdministrationError>;
readonly suspendUser: (
userHash: ActionHash,
reason: string,
) => Effect.Effect<void, AdministrationError>;
readonly unsuspendUser: (
userHash: ActionHash,
) => Effect.Effect<void, AdministrationError>;
readonly getAllAdmins: () => Effect.Effect<UIUser[], AdministrationError>;
readonly getAllModerators: () => Effect.Effect<UIUser[], AdministrationError>;
readonly getAllSuspendedUsers: () => Effect.Effect<
UIUser[],
AdministrationError
>;
}
Base Services
Holochain Client Service
File: ui/src/lib/services/HolochainClientService.svelte.ts
Interface
export interface HolochainClientService {
readonly callZome: <T>(
args: CallZomeRequest,
) => Effect.Effect<T, HolochainError>;
readonly callZomeRaw: <T>(
args: CallZomeRequest,
) => Effect.Effect<T, HolochainError>;
readonly adminClient: () => AdminWebsocket;
readonly appClient: () => AppWebsocket;
readonly isConnected: () => boolean;
readonly disconnect: () => Effect.Effect<void, never>;
readonly reconnect: () => Effect.Effect<void, HolochainError>;
}
Usage
export const HolochainClientService =
Context.GenericTag<HolochainClientService>("HolochainClientService");
// Usage in domain services
const result = await Effect.runPromise(
Effect.gen(function* () {
const client = yield* HolochainClientService;
return yield* client.callZome({
zome_name: "service_types",
fn_name: "get_all_service_types",
payload: null,
});
}).pipe(Effect.provide(HolochainClientServiceLive)),
);
hREA Service
File: ui/src/lib/services/hrea.service.ts
Interface
export interface HreaService {
readonly getResourceSpecifications: () => Effect.Effect<
ResourceSpecification[],
HreaError
>;
readonly createResourceSpecification: (
input: CreateResourceSpecificationInput,
) => Effect.Effect<ResourceSpecification, HreaError>;
readonly updateResourceSpecification: (
id: string,
input: UpdateResourceSpecificationInput,
) => Effect.Effect<ResourceSpecification, HreaError>;
readonly deleteResourceSpecification: (
id: string,
) => Effect.Effect<void, HreaError>;
readonly getIntents: () => Effect.Effect<Intent[], HreaError>;
readonly createIntent: (
input: CreateIntentInput,
) => Effect.Effect<Intent, HreaError>;
}
Error Handling
All services use domain-specific tagged errors following the pattern:
export class DomainError extends Data.TaggedError("DomainError")<{
readonly message: string;
readonly cause?: unknown;
readonly context?: string;
readonly entityId?: string;
readonly operation?: string;
}> {
static fromError(
error: unknown,
context: string,
entityId?: string,
operation?: string,
): DomainError {
const message = error instanceof Error ? error.message : String(error);
return new DomainError({
message,
cause: error,
context,
entityId,
operation,
});
}
}
Common Patterns
Effect.gen vs .pipe Usage
Use Effect.gen for:
- Dependency injection
- Sequential operations
- Conditional logic
- Complex business logic
Use .pipe for:
- Error handling and transformation
- Adding spans and tracing
- Simple transformations
- Layer composition
Service Composition
// Composing multiple services
const complexOperation = Effect.gen(function* () {
const serviceTypeService = yield* ServiceTypeService;
const requestService = yield* RequestService;
const serviceType = yield* serviceTypeService.getServiceType(serviceTypeHash);
if (!serviceType) {
yield* Effect.fail(DomainError.create("Service type not found"));
}
return yield* requestService.createRequest({
...requestInput,
serviceTypeHash,
});
});
Error Context Usage
// Using standardized error contexts
export const DOMAIN_CONTEXTS = {
CREATE_ENTITY: "Failed to create entity",
GET_ENTITY: "Failed to get entity",
UPDATE_ENTITY: "Failed to update entity",
DELETE_ENTITY: "Failed to delete entity",
GET_ALL_ENTITIES: "Failed to fetch entities",
} as const;
// Apply in service methods
const result = operation.pipe(
Effect.mapError((error) =>
DomainError.fromError(error, DOMAIN_CONTEXTS.CREATE_ENTITY, input.id),
),
Effect.withSpan("DomainService.createEntity"),
);
Testing Services
describe("DomainService", () => {
it("should create entity with proper error handling", async () => {
const MockHolochainClient = Layer.succeed(HolochainClientService, {
callZome: () => Effect.succeed(mockRecord),
});
const TestDomainServiceLive = Layer.provide(
DomainServiceLive,
MockHolochainClient,
);
const result = await Effect.runPromise(
Effect.gen(function* () {
const service = yield* DomainService;
return yield* service.createEntity(mockInput);
}).pipe(Effect.provide(TestDomainServiceLive)),
);
expect(result.name).toBe(mockInput.name);
});
});
This service layer provides the foundation for all data operations in the application, with consistent patterns for error handling, dependency injection, and Effect-TS integration.