@c7-数字/引导器
OpenAPI v2 兼容的 Daml 分类账客户端( 可以替换) @daml/ledger为新Canton JSON API v2.
概览
此软件包提供了TypeScript客户端,用于与 Canton 的 JSON Ledger API v2 交互,该客户端取代了已贬值的 v1 API @daml/ledger 目标.
特征
- 类型安全 API: 从 OpenAPI 规格生成类型脚本
- OpenAPI v2 支持:瞄准新的Canton JSON API v2终点
- 自动生成类型: 用途
openapi-typescript生成准确的类型定义
版本
Npm 软件包有自己的 0.0.x 版本线,独立于Canton Spec 它的目标。 1.0前,每次发布都是补丁起落;打破的更改也得到一个 Migrating to 0.0.N 页:1 Canton 运行时间和 JSON API spec 此客户端的构建对象被单独版本并浮出水面 SDK_VERSION (见下文),不作为软件包版本.
这个包船对抗两个独立版本的东西:
- JSON 编导 API spec (表面为
SDK_VERSION:3.6.5-snapshot.20261027从坎顿release-line-3.6. Splice 0.9.x运行 Canton 3.6.x,这是DevNet,TestNet和MainNet服务的内容. 仅检查此规格( N)specs/openapi_<version>.yaml财务报告和审定财务报表specs/asyncapi_<version>.yaml) (中文(简体) ). - 达姆SDK (单位:千美元)
@daml/types: 同伴依赖(>=3.4.0 <4.0.0),所以应用程序选择其副本. 测试和CI使用3.5.2 Splice 0.9.x 搭配的 SDK.
这个 已支持的版本表格 列出Canton、Splice和Daml SDK版本的每个版本。 发行对象为Canton一行;不支持旧行.
安装
pnpm install @c7-digital/ledger
或者从源头构建:
pnpm install
pnpm build
使用量
import { Ledger } from "@c7-digital/ledger";
const ledger = new Ledger({
token: "your-jwt-token",
httpBaseUrl: "http://localhost:7575",
});
// Query contracts
const contracts = await ledger.query(MyTemplate);
// Create contracts
const result = await ledger.create(MyTemplate, payload, [actAsParty]);
// Exercise choices
const choiceResult = await ledger.exercise(MyChoice, contractId, argument, [actAsParty]);
// Read whole transactions (update id, record time, exercised choices) a page
// at a time; the participant can cut a page short, so loop on reachedOffset.
const participantId = await ledger.getParticipantId();
const end = await ledger.getLedgerEnd();
let offset = 0;
while (offset < end) {
const page = await ledger.getTransactionPage(
{ beginExclusive: offset, endInclusive: end },
{ parties: [party] },
);
for (const tx of page.transactions) console.log(tx.updateId, tx.recordTime);
offset = page.reachedOffset;
}
服务账户代号
在分类账上作为服务账户导入的服务器
@c7-digital/ledger/service-account。它通过 OAuth 2.0 获得符号
任何OpenID Connecting 发行器提供的客户端证书,它保留了符号
无效。 此切入点与主要切入点分开,原因有两个:
服务器可以保密客户端, 它需要可选的对等依赖
openid-client (单位:千美元)pnpm add openid-client) (中文(简体) ).
import { Ledger } from "@c7-digital/ledger";
import {
ClientCredentialsTokenSource,
TokenRefresher,
} from "@c7-digital/ledger/service-account";
const source = new ClientCredentialsTokenSource({
issuer: new URL("https://auth.example.com/realms/my-realm"),
clientId: "my-service",
clientSecret: process.env.CLIENT_SECRET!,
audience: "https://ledger.example.com", // only if the issuer needs it
});
let ledger: Ledger | null = null;
const refresher = new TokenRefresher({
source,
onToken: ({ token }) => ledger?.setToken(token),
onFailure: ({ error, failures, retryInMs }) =>
console.warn("token refresh failed", { error, failures, retryInMs }),
});
const { token } = await refresher.start(); // throws if the first token fails
ledger = new Ledger({ token, httpBaseUrl: "https://participant.example.com" });
TokenRefresher以80%的象征寿命刷新。 失败之后 反转( 5 s, 每次翻倍, 最多60 s) 直至刷新 成功 。 拥有自己标志的流也必须在onToken。该标志过期得太快,无法在过期前刷新 算作失败.onToken财务报告和审定财务报表onFailure能作同声. 抛出或拒绝承诺 记录下来,时间表不变.refreshNow()立即获得新的代号, 例如分类账拒绝后 当前的符号。 电话在刷新 加入这个刷新.status()给最后的刷新, 下一个刷新, 符号过期 最后一个错误和失败次数,用于健康页面.stop()停止新鲜.ClientCredentialsTokenSource通过发行商找到符号端点.well-known/openid-configuration它把客户的秘密传送到 当发行方支持时请求正文client_secret_post,则在 授权页眉( E)client_secret_basic) (中文(简体) ). 除非allowInsecureHttp设置; 仅为本地发行人设置。 与allowInsecureHttp,一个 http 符号端点必须具有相同的来源 (scheme, host and port)作为发行商,因为客户端的秘密会去它.- 任何其他
TokenSource(对象有:mint()与TokenRefresher我也一样.
错误
一个非OK HTTP 响应丢出一个 LedgerApiError 拥有状态和状态的
反应体,标记为如何读取:
export type LedgerErrorBody =
| { kind: "canton"; error: JsCantonError } // a rejection Canton can explain
| { kind: "json"; value: unknown } // some other structured error
| { kind: "text"; text: string } // a proxy's HTML page, a plain message
| { kind: "empty" }; // no body, or unreadable
标记记录了一次的决定, 当身体被读取, 所以一个呼叫者做
不从中重新找到它 typeof 无法从 JSON 字符串中分辨
一个 HTML 错误页.
cantonError 财务报告和审定财务报表 responseBody 来源于 body,所以他们不能
不同意这一点。 使用 cantonErrorOf 达到坎顿有效载荷
解决一个裸载荷和一个钱包门道包装器, 所以一个呼叫覆盖每个
拒绝的路径可以到达.
try {
await ledger.exercise(MyChoice, cid, arg, [party]);
} catch (e) {
const canton = cantonErrorOf(e);
if (canton && isRetryable(canton)) return retry();
if (e instanceof LedgerApiError && e.body.kind === "text") {
// Markup here means a proxy answered, not the ledger.
}
}
将提交重试/一意孤行,改为Canton's 结构化 分类
errorCategory 整数和准确 code),从不传递消息. 三个
帮助过往 unknown 组成运输(LedgerApiError()和广州
(单位:千美元)cantonError)信号,所以呼叫者不会重新定义:
- 我应该再试一次吗?
isRetriableSubmit(e)- 运输瞬间 (408 / 429 / 5xx) 或州级复议拒绝(errorCategory∈ 参数 / 短暂 / 截止日期 / 搜索- 后端 . - 我必须保持不变
commandId? 吗?isIndeterminateSubmit(e)- HTTP 503或a 广deadline(第3类) 呈文可能已经犯过错,所以重试 与 不变commandId等一会 平地瞬间(429)是安全的 重新尝试新鲜。 检查这个 在此之前isRetriableSubmit。 。 。 . - 我重新交代了没有?
isAlreadyApplied(e)—— 说DUPLICATE_COMMAND(单位:千美元) 重试 不变commandId发现参与者已经应用了 之前的尝试。 呼叫者重试不确定的提交文件 稳定commandId把这当成成功. - 我的档案已经没有了?
isAlreadyArchived(e)—— 说isAlreadyApplied或者说 合同已经不复存在(CONTRACT_NOT_FOUND, (中文(简体) ),LOCAL_VERDICT_INACTIVE_CONTRACTS) (中文(简体) ). 用于任何归档的应用程序 政策 将它视为成功 留在呼叫者. - 确认永久失败?
isConfirmedPermanentReject(e)结构化 坎顿拒绝 坎顿标记是不可重审的 并且还没有存档. 赤出 HTTP 4xx / 纯错误为 虚假 (因被忽略而关闭).
每一个都要 unknown 并阅读结构状态(httpStatusOf,还有
因此,它们直接工作在一个 catch 约束 - 不 instanceof 需要时,
它们在树上活下来了 复制的包裹副本.
try {
await ledger.exercise(Archive, cid, arg, [party]);
} catch (e) {
if (isAlreadyArchived(e)) return; // idempotent: already gone
if (isIndeterminateSubmit(e)) return retrySameCommandId();
if (isRetriableSubmit(e)) return retry();
if (isConfirmedPermanentReject(e)) return markIgnored(); // domain policy
throw e;
}
message 上面有120个字符的体型,所以记录
它不能发出整个错误页。 全部值保持不变 body。 。 。 .
迁移到0.0.48
添加数 Ledger.getLatestPrunedOffsets() 财务报告和审定财务报表
TypedHttpClient.getLatestPrunedOffsets(),改为
GET /v2/state/latest-pruned-offsets类型 PrunedOffsets。读取
刷新参与者的历史必须始于
participantPrunedUpToInclusive添加;没有删除.
const { participantPrunedUpToInclusive } = await ledger.getLatestPrunedOffsets();
const end = await ledger.getLedgerEnd();
const page = await ledger.getTransactionPage(
{ beginExclusive: participantPrunedUpToInclusive, endInclusive: end },
{ parties: [party] },
);
移动到 0.045
添加切入点 @c7-digital/ledger/service-account (见
服务账户代号: TokenRefresher, (中文(简体) ),
ClientCredentialsTokenSource, (中文(简体) ), retryDelayMs类型 TokenSource, (中文(简体) ),
MintedToken, (中文(简体) ), RefreshFailure, (中文(简体) ), TokenRefresherStatus, (中文(简体) ),
TokenRefresherOptions 财务报告和审定财务报表 ClientCredentialsOptions。 。 。 . openid-client 是一个
新的可选同伴依赖,仅此切入点需要。 添加剂;
没有删除.
迁移到 0.043
导出请求体建构器 Ledger.query 财务报告和审定财务报表
Ledger.queryInterface 使用时。 当请求被重复时使用
运输,例如CIP-0103钱包 ledgerApi 代理,所以身体已经
和那个一样 Ledger 发送。 添加; 没有删除.
activeContractsRequest(parties, filters, activeAtOffset, verbose?)正在建设 页:1POST /v2/state/active-contracts躯体。 派对过滤器在eventFormat3.6州参与者拒绝贬值的最高级别filter默认(DEPRECATED_API_DISABLED) (中文(简体) ).templateFilter(templateId, includeCreatedEventBlob?)财务报告和审定财务报表interfaceFilter(interfaceId, includeCreatedEventBlob?)构建IdentifierFilters. 界面过滤器总是询问界面视图.- 类型
IdentifierFilter财务报告和审定财务报表GetActiveContractsRequest导出.
import { activeContractsRequest, templateFilter } from "@c7-digital/ledger/lite";
const body = activeContractsRequest([party], [templateFilter(Token.templateId)], offset);
迁移到 0.042
增加更新历史和参与者阅读。 添加; 没有删除.
Ledger.getTransactionPage(range, { parties, shape, limit })返回整个 交易(beginExclusive, endInclusive](默认形状:分类账) 影响,因此,存在已行使的选择)reachedOffset将恢复 从.Ledger.getParticipantId()财务报告和审定财务报表TypedHttpClient.getParticipantId()读取GET /v2/parties/participant-id。 。 。 .TypedHttpClient.getUpdates(request, { limit })发送POST /v2/updates。 。 。 .Ledger.getLedgerEnd()现为公用(它是私用) 一秒缓存.
迁移到 0.041
查询时间 "end" 现在看一样 Ledger 例本身的写作。 A 类
已经成功 create, (中文(简体) ), exercise, (中文(简体) ), exerciseResult, (中文(简体) ), submit 或者说
submitWithDisclosures 将缓存分类账端移动到
页:1 之前,一秒钟的分类账端缓存可以提供
因此,查询仍然可以返回合同中
书信存档了 无 API 更改 .
移动到 0.0.40
添加数 isConfirmedPermanentReject(e) 结构化的州不可复辩拒绝,
不包括已经存档的无操作程序。 短程运输 4xx / 平面误差
false 所以,忽略和处理 呼叫者失败关闭。 添加; 没有删除.
迁移到 0.0.39
添加数 isAlreadyApplied(e) —— 说 DUPLICATE_COMMAND 单独( 已经命令)
(已判决),对于尝试无常者
commandId 并且必须把破解的打击视为成功. isAlreadyArchived 现在
编曲:isAlreadyApplied ——行为相同,没有改变
它的召唤者。 添加; 没有删除.
迁移到 0.038
添加结构化,可重复使用的提交分类器,使消费者停止手卷:
isRetriableSubmit, (中文(简体) ), isIndeterminateSubmit,以及 isAlreadyArchived (见上文),
加号 httpStatusOf 读取一个结构状态。 全部读取 cantonError是因为
code 编号 : errorCategory 和 HTTP 状态 — 没有信件字符串匹配 .
短暂的人生 LedgerApiError.isTransient() 编号 : .isIndeterminate() 方法
(0.0.37)为: 删除 一种方法迫使
instanceof 检查呼叫站点, 该站点在复制的软件包副本中失败
也是自由函数所避免的准确反模式.
// 0.0.37
if (e instanceof LedgerApiError && e.isIndeterminate()) retrySameCommandId();
// 0.0.38
if (isIndeterminateSubmit(e)) retrySameCommandId();
迁移到 0.037
这个 submitError 帮助者( e)isArchiveAlreadyDoneError, (中文(简体) ),
isIndeterminateArchiveTimeout, (中文(简体) ), isIndeterminateSubmitError, (中文(简体) ),
isLockedContractsError, (中文(简体) ), isTransientArchiveError页:1 他们重复了
cantonError ——取而代之的是Canton的分类:
// before
if (isTransientArchiveError(e)) retry();
if (isIndeterminateSubmitError(e)) retrySameCommandId();
// after (0.0.38 free functions)
if (isIndeterminateSubmit(e)) retrySameCommandId();
else if (isRetriableSubmit(e)) retry();
合同缺失或闲置是否成功,是征召人政策——
isAlreadyArchived(e) 报告事实; 呼叫者决定停止作为成功.
迁移到 0.034
这个 LedgerApiError 构造器需要标记 LedgerErrorBody 而不是一个
未标记 unknown。读取错误时没有变化 status, (中文(简体) ), statusText, (中文(简体) ),
cantonError,以及 responseBody 都像以前一样 只有密码
构造 在实际操作中,它意味着测试和模拟,需要更新:
new LedgerApiError(404, "Not Found", cantonPayload) // before
new LedgerApiError(404, "Not Found", { kind: "canton", error: cantonPayload })
new LedgerApiError(403, "Forbidden", "<html>…</html>") // before
new LedgerApiError(403, "Forbidden", { kind: "text", text: "<html>…</html>" })
二辩相形式仍指"无体"并需要改变.
ScanApiError 内 @c7-digital/scan 同样的 status, (中文(简体) ), statusText, (中文(简体) ),
body,以及 responseBody,所以一个 catch 读取一个集
田间。 没有 canton 案例.
从@daml/ledger 迁移
关键差异 :
create()财务报告和审定财务报表exercise()现在需要一种actAs参数- 一些方法签名已经更新以匹配 OpenAPI v2 spec
- 根据 OpenAPI 规格生成的新类型定义
发展
# Build the package
pnpm build
# Clean build artifacts
pnpm clean
# Watch for changes
pnpm watch
# Run tests
pnpm test
# Lint code
pnpm lint
OpenAPI 规格
该套件采用了由Canton公司提供的JSON Ledger API规格,作为销售商 specs/openapi_<canton-version>.yaml 财务报告和审定财务报表 specs/asyncapi_<canton-version>.yaml。该版本编码了Canton运行时的发布.
要搬入新的Canton线:
- 获取网络运行的Canton线的规格. 两道道:
- 从广东来的 (两个文件):在
release-line-<major.minor>(或最新数据)<major.minor>-open-source-<date>树枝 树枝 树枝community/ledger/ledger-json-api/src/test/resources/json-api-docs/{openapi,asyncapi}.yaml。 Splice 只销售 OpenAPI 文件, 下canton/它的释放线。 用它来检查 路径和计划一致. - 从现场参与者: 卷轴
http://<participant>:<json-api-port>/docs/openapi财务报告和审定财务报表/docs/asyncapi。 。 。 .
- 从广东来的 (两个文件):在
- 替换文件为
ledger/specs/与{openapi,asyncapi}_<canton-version>.yaml,并删除上一对. - 更改构建脚本
--sdk-version=<version>标记为ledger/package.json(旗帜名称为历史;它选择了 spec 文件)和预期SDK_VERSION内src/api-surface.test.ts。 。 。 . - 添加一行到 已支持的版本表格。 。 。 .
- 运行
pnpm build用于再生类型.