Files
2026-11-14 09:56:45 +00:00

202 lines
6.7 KiB
TypeScript

// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
// The generated client receives only the current server-prepared binding.
import {
businessAppError,
type RequestBusinessBinding,
requestBusinessBinding,
toBusinessError,
} from "@conusai-kit/business"
import { type Container, notFound, type ProcedureKind } from "@conusai-kit/core"
import {
type Activity,
activitiesAddComment,
activitiesCompleteTask,
activitiesCreateTask,
activitiesFindActivities,
activitiesGetActivity,
activitiesListComments,
activitiesReopenTask,
activitiesTimelineFor,
activitiesUpdateActivity,
type Comment,
type Fields,
type Receipt,
} from "@conusai-sdk/activities"
import { createClient } from "@conusai-sdk/activities/client"
import type { CommentView, TaskReceipt } from "../contract/index.js"
import {
ACTIVITIES_SERVICE,
type ActivitiesReads,
type ActivitiesService,
readsOf,
type TaskRecord,
} from "../core/ports.js"
async function answer<T>(
operation: Promise<{ data?: T; error?: unknown; response?: Response }>,
): Promise<Exclude<T, undefined>> {
let result: Awaited<typeof operation>
try {
result = await operation
} catch (error) {
throw businessAppError(toBusinessError(error, undefined))
}
if (result.error !== undefined || result.data === undefined)
throw businessAppError(toBusinessError(result.error, result.response))
return result.data as Exclude<T, undefined>
}
const view = (value: Activity): TaskRecord => {
if (value.kind !== "task" || value.status === undefined || value.status === null || value.erased || value.deleted)
throw notFound()
return {
taskId: value.id,
subject: value.subject,
body: value.body ?? "",
revision: value.revision.toString(),
status: value.status,
targets: value.targets,
createdAt: value.created_at,
}
}
const commentView = (value: Comment): CommentView => ({
commentId: value.id,
body: value.erased ? "" : value.body,
author: value.author ?? null,
erased: value.erased,
})
const receiptView = (value: Receipt): TaskReceipt => ({ taskId: value.id, revision: value.revision.toString() })
const fieldsOf = (row: Activity): Fields => ({
subject: row.subject,
targets: row.targets,
...(row.body === undefined ? {} : { body: row.body }),
...(row.assignee === undefined ? {} : { assignee: row.assignee }),
...(row.due_at === undefined ? {} : { due_at: row.due_at }),
...(row.ext === undefined ? {} : { ext: row.ext }),
...(row.participants === undefined ? {} : { participants: row.participants }),
...(row.pinned === undefined ? {} : { pinned: row.pinned }),
})
/** No org-wide client constructor or deployment singleton is supported. */
export function sdkActivitiesService(binding: RequestBusinessBinding): ActivitiesService {
const client = binding.client(createClient)
const org = { org_slug: binding.orgSlug }
const get = (id: string) => answer(activitiesGetActivity({ client, path: { ...org, activity_id: id } }))
return {
bindingId: binding.bindingId,
async list(input) {
const result = await answer(
activitiesFindActivities({
client,
path: org,
query: {
target: input.target,
...(input.status ? { status: input.status } : {}),
...(input.after ? { after: input.after } : {}),
limit: 50n,
},
}),
)
return {
rows: result.data.filter((row) => row.kind === "task" && !row.erased && !row.deleted).map(view),
...(result.next_after ? { after: result.next_after } : {}),
}
},
async get(id) {
return view(await get(id))
},
async timeline(input) {
const result = await answer(
activitiesTimelineFor({
client,
path: org,
query: { target: input.target, ...(input.after ? { after: input.after } : {}), limit: 50n },
}),
)
return {
rows: result.data.filter((row) => row.kind === "task" && !row.erased && !row.deleted).map(view),
...(result.next_after ? { after: result.next_after } : {}),
}
},
async comments(input) {
const result = await answer(
activitiesListComments({
client,
path: { ...org, activity_id: input.taskId },
query: {
...(input.after ? { after: input.after } : {}),
limit: 50n,
},
}),
)
return { rows: result.data.map(commentView), ...(result.next_after ? { after: result.next_after } : {}) }
},
async create(input) {
return receiptView(
await answer(
activitiesCreateTask({
client,
path: org,
body: {
kind: "task",
idempotency_key: input.key,
fields: {
subject: input.subject,
body: input.body,
targets: [input.target],
participants: [input.participant],
},
},
}),
),
)
},
async update(input) {
const current = await get(input.task.taskId)
if (!current.targets.includes(input.target)) throw notFound()
return receiptView(
await answer(
activitiesUpdateActivity({
client,
path: { ...org, activity_id: current.id },
body: {
fields: { ...fieldsOf(current), subject: input.subject, body: input.body },
revision: BigInt(input.expectedVersion),
idempotency_key: input.key,
},
}),
),
)
},
async change(input) {
const operation = input.state === "done" ? activitiesCompleteTask : activitiesReopenTask
return receiptView(
await answer(
operation({
client,
path: { ...org, activity_id: input.taskId },
body: { revision: BigInt(input.expectedVersion), idempotency_key: input.key },
}),
),
)
},
async comment(input) {
const result = await answer(
activitiesAddComment({
client,
path: { ...org, activity_id: input.taskId },
body: { body: input.body, revision: BigInt(input.expectedVersion), idempotency_key: input.key },
}),
)
return { commentId: result.id, revision: result.revision.toString() }
},
}
}
export function activitiesServiceOf(container: Container, kind: "effect"): ActivitiesService
export function activitiesServiceOf(container: Container, kind: ProcedureKind): ActivitiesReads
export function activitiesServiceOf(container: Container, kind: ProcedureKind): ActivitiesReads {
const service = container.has(ACTIVITIES_SERVICE)
? container.resolve(ACTIVITIES_SERVICE)
: sdkActivitiesService(requestBusinessBinding(container))
return kind === "effect" ? service : readsOf(service)
}