204 lines
6.6 KiB
TypeScript
204 lines
6.6 KiB
TypeScript
import {
|
|
businessAppError,
|
|
type RequestBusinessBinding,
|
|
requestBusinessBinding,
|
|
toBusinessError,
|
|
} from "@conusai-kit/business"
|
|
import type { Container, ProcedureKind } from "@conusai-kit/core"
|
|
import {
|
|
type Account,
|
|
type Balance,
|
|
type Entry,
|
|
ledgerCreateAccount,
|
|
ledgerGetAccount,
|
|
ledgerGetBalances,
|
|
ledgerGetTransaction,
|
|
ledgerListAccounts,
|
|
ledgerListEntries,
|
|
ledgerListTransactions,
|
|
ledgerPostTransaction,
|
|
ledgerRecordTransaction,
|
|
ledgerReverseTransaction,
|
|
ledgerUpdateAccount,
|
|
ledgerVerify,
|
|
ledgerVoidTransaction,
|
|
type Transaction,
|
|
} from "@conusai-sdk/ledger"
|
|
import { createClient } from "@conusai-sdk/ledger/client"
|
|
import { accountView, balanceView, entryView, transactionView, verificationView } from "../contract/index.js"
|
|
import { LEDGER_SERVICE, type LedgerReads, type LedgerService, type Page, readsOf } 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 account = (row: Account) =>
|
|
accountView.parse({
|
|
accountId: row.id,
|
|
code: row.code,
|
|
name: row.name,
|
|
currency: row.currency,
|
|
type: row.type,
|
|
normalBalance: row.normal_balance,
|
|
allowNegative: row.allow_negative,
|
|
status: row.status,
|
|
revision: row.revision.toString(),
|
|
})
|
|
const balance = (row: Balance) =>
|
|
balanceView.parse({
|
|
accountId: row.account_id,
|
|
currency: row.currency,
|
|
posted: row.posted.toString(),
|
|
pending: row.pending.toString(),
|
|
available: row.available.toString(),
|
|
})
|
|
const entry = (row: Entry) =>
|
|
entryView.parse({
|
|
entryId: row.id,
|
|
transactionId: row.transaction_id,
|
|
accountId: row.account_id,
|
|
currency: row.currency,
|
|
direction: row.direction,
|
|
amountMinor: row.amount_minor.toString(),
|
|
})
|
|
const transaction = (row: Transaction) =>
|
|
transactionView.parse({
|
|
transactionId: row.id,
|
|
revision: row.revision.toString(),
|
|
effectiveAt: row.effective_at,
|
|
description: row.description,
|
|
reference: row.reference ?? null,
|
|
reverses: row.reverses ?? null,
|
|
status: row.status,
|
|
entries: row.entries.map(entry),
|
|
})
|
|
const page = <A, B>(result: { data: A[]; next_after?: string | null }, view: (row: A) => B): Page<B> => ({
|
|
rows: result.data.map(view),
|
|
...(result.next_after ? { after: result.next_after } : {}),
|
|
})
|
|
const query = (after?: string) => ({ limit: 50, ...(after ? { after } : {}) })
|
|
export function sdkLedgerService(binding: RequestBusinessBinding): LedgerService {
|
|
const client = binding.client(createClient)
|
|
const org = { org_slug: binding.orgSlug }
|
|
return {
|
|
async accounts(after) {
|
|
return page(await answer(ledgerListAccounts({ client, path: org, query: query(after) })), account)
|
|
},
|
|
async account(id) {
|
|
return account(await answer(ledgerGetAccount({ client, path: { ...org, account_id: id } })))
|
|
},
|
|
async balances(id, after) {
|
|
return page(
|
|
await answer(
|
|
ledgerGetBalances({ client, path: org, query: { ...query(after), ...(id ? { account_id: id } : {}) } }),
|
|
),
|
|
balance,
|
|
)
|
|
},
|
|
async entries(id, after) {
|
|
return page(
|
|
await answer(ledgerListEntries({ client, path: { ...org, account_id: id }, query: query(after) })),
|
|
entry,
|
|
)
|
|
},
|
|
async transactions(after) {
|
|
return page(await answer(ledgerListTransactions({ client, path: org, query: query(after) })), transaction)
|
|
},
|
|
async transaction(id) {
|
|
return transaction(await answer(ledgerGetTransaction({ client, path: { ...org, transaction_id: id } })))
|
|
},
|
|
async verify() {
|
|
const result = await answer(ledgerVerify({ client, path: org }))
|
|
return verificationView.parse({ checkedAccounts: result.checked_accounts, drift: result.drift })
|
|
},
|
|
async createAccount(input, key) {
|
|
return account(
|
|
await answer(
|
|
ledgerCreateAccount({
|
|
client,
|
|
path: org,
|
|
body: {
|
|
code: input.code,
|
|
name: input.name,
|
|
currency: input.currency,
|
|
type: input.type,
|
|
normal_balance: input.normalBalance,
|
|
allow_negative: input.allowNegative,
|
|
idempotency_key: key,
|
|
},
|
|
}),
|
|
),
|
|
)
|
|
},
|
|
async updateAccount(id, input, revision, key) {
|
|
return account(
|
|
await answer(
|
|
ledgerUpdateAccount({
|
|
client,
|
|
path: { ...org, account_id: id },
|
|
body: {
|
|
name: input.name,
|
|
status: input.status,
|
|
allow_negative: input.allowNegative,
|
|
revision: BigInt(revision),
|
|
idempotency_key: key,
|
|
},
|
|
}),
|
|
),
|
|
)
|
|
},
|
|
async record(terms, key) {
|
|
return transaction(
|
|
await answer(
|
|
ledgerRecordTransaction({
|
|
client,
|
|
path: org,
|
|
body: {
|
|
effective_at: terms.effectiveAt,
|
|
description: terms.description,
|
|
reference: terms.reference ?? null,
|
|
status: terms.status,
|
|
entries: terms.entries.map((row) => ({
|
|
account_id: row.accountId,
|
|
direction: row.direction,
|
|
amount_minor: BigInt(row.amountMinor),
|
|
})),
|
|
idempotency_key: key,
|
|
},
|
|
}),
|
|
),
|
|
)
|
|
},
|
|
async transition(action, id, revision, key) {
|
|
const method =
|
|
action === "post" ? ledgerPostTransaction : action === "void" ? ledgerVoidTransaction : ledgerReverseTransaction
|
|
return transaction(
|
|
await answer(
|
|
method({
|
|
client,
|
|
path: { ...org, transaction_id: id },
|
|
body: { revision: BigInt(revision), idempotency_key: key },
|
|
}),
|
|
),
|
|
)
|
|
},
|
|
}
|
|
}
|
|
export function ledgerServiceOf(container: Container, kind: "effect"): LedgerService
|
|
export function ledgerServiceOf(container: Container, kind: ProcedureKind): LedgerReads
|
|
export function ledgerServiceOf(container: Container, kind: ProcedureKind): LedgerReads {
|
|
const service = container.has(LEDGER_SERVICE)
|
|
? container.resolve(LEDGER_SERVICE)
|
|
: sdkLedgerService(requestBusinessBinding(container))
|
|
return kind === "effect" ? service : readsOf(service)
|
|
}
|