@@ -0,0 +1,60 @@
|
||||
import { implement } from "@conusai-kit/core"
|
||||
import * as c from "../contract/index.js"
|
||||
import { operationKey, recordBalanced, transition } from "../core/index.js"
|
||||
import { ledgerServiceOf } from "./service.js"
|
||||
export const LEDGER_PROCEDURES = [
|
||||
implement(c.accounts, ({ ctx, input }) => ledgerServiceOf(ctx.container, ctx.kind).accounts(input.after)),
|
||||
implement(c.account, ({ ctx, input }) => ledgerServiceOf(ctx.container, ctx.kind).account(input.accountId)),
|
||||
implement(c.balances, ({ ctx, input }) =>
|
||||
ledgerServiceOf(ctx.container, ctx.kind).balances(input.accountId, input.after),
|
||||
),
|
||||
implement(c.entries, ({ ctx, input }) =>
|
||||
ledgerServiceOf(ctx.container, ctx.kind).entries(input.accountId, input.after),
|
||||
),
|
||||
implement(c.transactions, ({ ctx, input }) => ledgerServiceOf(ctx.container, ctx.kind).transactions(input.after)),
|
||||
implement(c.transaction, ({ ctx, input }) =>
|
||||
ledgerServiceOf(ctx.container, ctx.kind).transaction(input.transactionId),
|
||||
),
|
||||
implement(c.verify, ({ ctx }) => ledgerServiceOf(ctx.container, ctx.kind).verify()),
|
||||
implement(c.createAccount, ({ ctx, input }) =>
|
||||
ledgerServiceOf(ctx.container, "effect").createAccount(input, operationKey("account", input.requestId)),
|
||||
),
|
||||
implement(c.updateAccount, ({ ctx, input }) =>
|
||||
ledgerServiceOf(ctx.container, "effect").updateAccount(
|
||||
input.accountId,
|
||||
input,
|
||||
input.expectedVersion,
|
||||
operationKey("account-update", input.requestId),
|
||||
),
|
||||
),
|
||||
implement(c.record, ({ ctx, input }) =>
|
||||
recordBalanced(ledgerServiceOf(ctx.container, "effect"), input.terms, input.requestId),
|
||||
),
|
||||
implement(c.post, ({ ctx, input }) =>
|
||||
transition(
|
||||
ledgerServiceOf(ctx.container, "effect"),
|
||||
"post",
|
||||
input.transactionId,
|
||||
input.expectedVersion,
|
||||
input.requestId,
|
||||
),
|
||||
),
|
||||
implement(c.voidTransaction, ({ ctx, input }) =>
|
||||
transition(
|
||||
ledgerServiceOf(ctx.container, "effect"),
|
||||
"void",
|
||||
input.transactionId,
|
||||
input.expectedVersion,
|
||||
input.requestId,
|
||||
),
|
||||
),
|
||||
implement(c.reverse, ({ ctx, input }) =>
|
||||
transition(
|
||||
ledgerServiceOf(ctx.container, "effect"),
|
||||
"reverse",
|
||||
input.transactionId,
|
||||
input.expectedVersion,
|
||||
input.requestId,
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,203 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user