152 lines
5.3 KiB
TypeScript
152 lines
5.3 KiB
TypeScript
// Generated SDK adapter. Production dispatch requires a server-prepared request binding.
|
|
|
|
import {
|
|
businessAppError,
|
|
type Client,
|
|
canonicalActionJson,
|
|
requestBusinessBinding,
|
|
requiresAppActionTransport,
|
|
toBusinessError,
|
|
} from "@conusai-kit/business"
|
|
import { type Container, forbidden, type ProcedureKind } from "@conusai-kit/core"
|
|
import {
|
|
type ContactPoint,
|
|
type ContactValue,
|
|
crmAddContactPoint,
|
|
crmCreateParty,
|
|
crmGetParty,
|
|
crmListContactPoints,
|
|
crmListParties,
|
|
type Party,
|
|
} from "@conusai-sdk/crm"
|
|
import { createClient } from "@conusai-sdk/crm/client"
|
|
import { type ContactView, type PartyView, SUBJECT_SCHEME } from "../contract/index.js"
|
|
import { contactRequestBody, partyRequestBody } from "../core/action-plan.js"
|
|
import { CRM_SERVICE, type CrmReads, type CrmService, crmReadsOf } from "../core/ports.js"
|
|
|
|
const partyView = (party: Party): PartyView => ({
|
|
partyId: party.id,
|
|
kind: party.kind,
|
|
displayName: party.display_name,
|
|
legalName: party.legal_name ?? null,
|
|
country: party.country,
|
|
status: party.status,
|
|
linked: (party.identifiers ?? []).some((identifier) => identifier.scheme === SUBJECT_SCHEME),
|
|
})
|
|
|
|
/** A contact value as a person reads it. */
|
|
function valueText(value: ContactValue): string {
|
|
switch (value.kind) {
|
|
case "postal":
|
|
return [value.value.street, value.value.postal_code, value.value.city, value.value.country]
|
|
.filter((part): part is string => typeof part === "string" && part !== "")
|
|
.join(", ")
|
|
case "peppol_endpoint":
|
|
return `${value.value.eas}:${value.value.id}`
|
|
default:
|
|
return value.value
|
|
}
|
|
}
|
|
|
|
const contactView = (contact: ContactPoint): ContactView =>
|
|
contact.fields
|
|
? {
|
|
contactId: contact.id,
|
|
kind: contact.fields.kind,
|
|
value: valueText(contact.fields),
|
|
purposes: contact.fields.purposes,
|
|
isPrimary: contact.fields.is_primary,
|
|
erased: contact.erased,
|
|
}
|
|
: // Erased: no personal value remains, only that there was a contact point.
|
|
{ contactId: contact.id, kind: "email", value: "", purposes: [], isPrimary: false, erased: true }
|
|
|
|
/** What a generated operation answered: its data, or the kit's app error for the refusal (`businessAppError`). */
|
|
async function answer<T>(
|
|
call: Promise<{ data?: T; error?: unknown; response?: Response }>,
|
|
): Promise<Exclude<T, undefined>> {
|
|
let result: { data?: T; error?: unknown; response?: Response }
|
|
try {
|
|
result = await call
|
|
} 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>
|
|
}
|
|
|
|
/** The port over one client, in one organization of one platform project. */
|
|
export function sdkCrmService(binding: { client: Client; orgSlug: string; bindingId: string }): CrmService {
|
|
const { client } = binding
|
|
const org = { org_slug: binding.orgSlug }
|
|
return {
|
|
bindingId: binding.bindingId,
|
|
async listParties(query) {
|
|
const page = await answer(crmListParties({ client, path: org, query: { ...query, limit: BigInt(query.limit) } }))
|
|
return { rows: page.data.map(partyView), ...(page.next_after ? { after: page.next_after } : {}) }
|
|
},
|
|
async getParty(partyId) {
|
|
return partyView(await answer(crmGetParty({ client, path: { ...org, id: partyId } })))
|
|
},
|
|
async listContacts(partyId, query) {
|
|
const page = await answer(
|
|
crmListContactPoints({
|
|
client,
|
|
path: { ...org, id: partyId },
|
|
query: { ...query, limit: BigInt(query.limit) },
|
|
}),
|
|
)
|
|
return { rows: page.data.map(contactView), ...(page.next_after ? { after: page.next_after } : {}) }
|
|
},
|
|
async createParty(input) {
|
|
const party = await answer(
|
|
crmCreateParty({
|
|
client,
|
|
path: org,
|
|
body: partyRequestBody(input),
|
|
bodySerializer: canonicalActionJson,
|
|
}),
|
|
)
|
|
return partyView(party)
|
|
},
|
|
async addContact(input) {
|
|
const contact = await answer(
|
|
crmAddContactPoint({
|
|
client,
|
|
path: { ...org, id: input.partyId },
|
|
body: contactRequestBody(input),
|
|
bodySerializer: canonicalActionJson,
|
|
}),
|
|
)
|
|
return contactView(contact)
|
|
},
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The service a handler of `kind` may use: the bound one (the token's view for the kind applies), or
|
|
* the current verified request's, narrowed to its reads for a `read` or `write` handler as the token would.
|
|
*/
|
|
export function crmServiceOf(container: Container, kind: "effect"): CrmService
|
|
export function crmServiceOf(container: Container, kind: ProcedureKind): CrmReads
|
|
export function crmServiceOf(container: Container, kind: ProcedureKind): CrmReads {
|
|
if (container.has(CRM_SERVICE)) {
|
|
if (requiresAppActionTransport(container)) throw forbidden("app action requires prepared business transport")
|
|
return container.resolve(CRM_SERVICE) as CrmReads
|
|
}
|
|
let service: CrmService
|
|
try {
|
|
const binding = requestBusinessBinding(container)
|
|
service = sdkCrmService({
|
|
client: binding.client(createClient),
|
|
orgSlug: binding.orgSlug,
|
|
bindingId: binding.bindingId,
|
|
})
|
|
} catch (error) {
|
|
throw businessAppError(error)
|
|
}
|
|
return kind === "effect" ? service : crmReadsOf(service)
|
|
}
|