@epistola.app/epistola-client

中文文档
2.0.0发布于 2026/11/12EUPL-1.2TypeScriptES Module零依赖构建来源已验证近 30 天下载 3038

Epistola文档生成API的Node.js客户端,在平台的自取. 由Epistola OpenAPI合同产生,有身份头,RFC 9457问题-细节错误处理,API-key和自签的JWT auth,NDJSON结果收集,

中文使用文档
文档目录11 节
  1. 安装
  2. 快开始
  3. 部分更新
  4. 二进制下载
  5. 上传文件
  6. 更新服务器
  7. 处理错误
  8. 结果收集
  9. 模板数据验证
  10. 正在验证处理中
  11. 发展

Epistola Node.js 客户端

节点Js 客户端库 爱皮斯托拉 epistola OpenAPI 合同生成的文档生成 API OpenAPI 生成器 (单位:千美元)typescript-fetch,在月台上独自 fetch)和手写胶水,用于身份、认证、错误处理、结果收集,以及 客户端验证。 包含类型脚本声明.

除了股票产生的客户端之外

  • 客户身份 页眉 (User-Agent + 键 X-EP-Node-Id每一份请求都需要.
  • RFC 9457 问题详情 错误处理 - 输入 ProblemDetailException 带有 typeSlug 歧视者和产生者 KnownProblemSlugs 常数.
  • 静态 API- key 通过 Authorization: ApiKey <key>,支持的方法.
  • 自签 JWT 认证( e)JwtSigner, (中文(简体) ), 实验{\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}用新鲜的短命 每个请求的代号 node:crypto 一个人.
  • 这个 Accept 每个操作都用页眉声明. 生成的课从不设置一个 节点的 fetch 发送 */* 默认的,所以没有这个请求,它永远不会说接受 application/problem+json,文档中每个错误处理器都会被构建为解析.
  • NDJSON 结果收集 (单位:千美元)ResultCollector具有适应性投票、压缩和 分区意识路由帮助器.
  • 模板数据验证 (单位:千美元)TemplateSchemaValidator),询问服务器,带有可插件 TemplateDataValidator 用于想要在流程中进行检查的消费者 validate<Model> 帮助解决合同本身的限制.

软件包版本跟踪Epistola合同版本(info.version并锁定释放 与克特林,雅加达EE,.NET和Python客户端. 它需要22.12节或以后的节点和船舶 一个ES模块(节点的) require() 也可以装入).

它已经 没有运行时间依赖性,也没有可选的: HTTP 是该平台的 fetch,JWT签名 实值 node:crypto,减压是 node:zlib。模板数据验证询问服务器而不是 在此编译计划, 因此不再安装 JSON Schema 编译器( 见 模板数据验证) (中文(简体) ).

发布历史 : 变动情况。 。 。 .

安装

npm install @epistola.app/epistola-client

快开始

import { ClientIdentity, EpistolaClient, TemplatesApi } from '@epistola.app/epistola-client'

const identity = ClientIdentity.builder()
  .nodeId('my-pod-123')                       // defaults to the hostname
  .product('my-app', '1.0.0')                 // appended to User-Agent
  .build()

const client = EpistolaClient.builder('https://epistola.example.com/api')
  .identity(identity)                         // User-Agent + X-EP-Node-Id
  .apiKey('epk_...')                          // Authorization: ApiKey <key>
  .build()

const templates = new TemplatesApi(client)
const template = await templates.getTemplate({ tenantKey: 'acme', registry: '~', namespace: 'acme', catalogKey: 'invoices', release: '1.4.0', templateKey: 'invoice' })

其目录涉及 registry, (中文(简体) ), namespace 财务报告和审定财务报表 catalogKey。 。 。 . registry: '~' 地址: 服务器的默认注册; 任何其他注册都以全名命名(registry: 'acme.com') (中文(简体) ).

EpistolaClient.builder(baseUrl, apiKey) 是同一事物的简称。 一些Epistola套间 部署完全禁用 API- key 认证; 开关 e.typeSlug === KnownProblemSlugs.API_KEY_AUTH_DISABLED 并引导呼叫者前往JWT auth.

自签名 JWT 认证为 实验: 爱皮斯托拉 套房可能尚未执行 流动仍然会改变。 试试看,通过 .jwtSigner(signer) 改为 .apiKey(...)数字 :

import { JwtSigner } from '@epistola.app/epistola-client'

const signer = JwtSigner.builder()
  .consumerId('invoice-service')
  .privateKey(JwtSigner.loadPrivateKey('private.pem'))
  .build()

EpistolaClient 实值 a 生成 Configuration,因此每个生成的API类直接取用. 一个构建者可以生产一个以上的客户端:呼叫 build() 更改后再次 requestTimeoutMs(...) 长期消费者通常需要对照 同一后端——投票、渲染和大额转账不超时(缺省),有限度 为了其他一切.

部分更新

生成的模型是平版Script接口,生成的序列器会丢出一个属性 您发送一个您设定到 null 作为 null在 API 上 PATCH 操作 a 省略或无成员保留其值,因为大多数生成的服务器无法区分两者: updateConsumer({ ..., updateConsumerRequest: { name: 'Billing' } }) 重新命名消费者 别碰别的东西,也一样 { name: 'Billing', description: null }。要删除一个值,请发送 可选文本成员空字符串({ description: '' },空数组或对象 将列表或地图替换为一无所有,或成员的明确旗帜({ clearExpiresAt: true }, (中文(简体) ), clearDefaultTheme, (中文(简体) ), clearCodeListBinding) (中文(简体) ).

二进制下载

合同宣布的每一项业务 format: binary —— 说 downloadDocument, (中文(简体) ), previewDocument, (中文(简体) ), downloadImageContent - 决定 Blob· ; Buffer.from(await blob.arrayBuffer()) 给你的字节.

上传文件

多部分业务,例如: uploadImage 财务报告和审定财务报表 importCatalog 带一个 Blob通过 a File,哪个 带有名称和类型 :

import { readFile } from 'node:fs/promises'

const file = new File([await readFile('logo.png')], 'logo.png', { type: 'image/png' })
const image = await new ImagesApi(client).uploadImage({ tenantKey: 'acme-corp', registry: '~', namespace: 'acme-corp', catalogKey: 'main', release: 'k7m2x9qa', file, name: 'Logo' })

一个平地 Blob 总是随文件名一起发送 blob,然后作为 application/octet-stream 除非这 被赋予了一种类型。 服务器然后命名图像 blob。 。 。 .

更新服务器

合同的细微版本可以添加响应成员和enum值("兼容性在 API描述中的 2.x",因此这个客户端继续针对后2.x服务器工作:一个响应 它不知道的成员被忽略, 而它不知道的enum值被保存为字符串 收到,尽管生成的类型没有列出。 保持一个 default 切换时的分支 um.

客户端忽略的是什么,它不能发回. 根据答复提出的要求只涉及成员 这个客户知道。 这与书写取代所读内容有关: getVariantContent 并写回 updateVariantContent 失去每个加入的更新目录的线条版本. 编辑至少与服务器一样新客户端的模板文档,并更改消费者的 通过只发送您更改的资源,从不发送您读取的对象.

处理错误

接着 ProblemDetailException 打开 typeSlug 反对 KnownProblemSlugs。弹片列表是 打开 ——API可以在没有客户端发布的情况下引入新的问题类型——所以始终保持一个 default 分支并返回未识别类型的 HTTP 状态.

import { KnownProblemSlugs, ProblemDetailException, ResponseError, TenantsApi } from '@epistola.app/epistola-client'

try {
  await new TenantsApi(client).getTenant({ tenantKey: 'acme' })
} catch (e) {
  if (e instanceof ProblemDetailException) {
    switch (e.typeSlug) {
      case KnownProblemSlugs.NOT_FOUND:
        log.warn(`tenant not found: ${e.detail}`)
        break
      case KnownProblemSlugs.VALIDATION_ERROR:
        for (const err of e.errors) log.warn(`${err.field}: ${err.message}`)     // field-level ValidationProblemDetail
        break
      case KnownProblemSlugs.DATA_MODEL_VALIDATION_ERROR:
        for (const [example, failures] of Object.entries(e.validationErrors)) {  // 422 data-model failures
          for (const f of failures) log.warn(`${example} ${f.path}: ${f.message}`)
        }
        break
      case KnownProblemSlugs.RELEASE_DEPENDENCIES_MISSING:
        for (const d of e.missingDependencies) {                                 // 409 from deployCatalogRelease
          log.warn(`${d.registry}/${d.namespace}/${d.catalogKey}@${d.version}: ${d.reason}`)
        }
        break
      case KnownProblemSlugs.RELEASE_DEPENDENCIES_UNRELEASED:
        for (const d of e.unreleasedDependencies) {                              // 409 from releaseCatalogDraft
          log.warn(`${d.registry}/${d.namespace}/${d.catalogKey}: draft ${d.releaseName} (${d.releaseId})`)
        }
        break
      case KnownProblemSlugs.CATALOG_READ_ONLY:
        // any write to a catalog that cannot be changed here
        log.warn(`catalog cannot be changed here: ${e.readOnlyReason}`)
        break
      case KnownProblemSlugs.MERGE_CONFLICTS_UNRESOLVED:
        for (const conflict of e.unresolvedConflicts) log.warn(`needs ours or theirs: ${conflict}`)  // mergeIntoCatalogDraft (alpha)
        break
      case KnownProblemSlugs.MERGE_PREVIEW_OUTDATED:
        log.warn('the draft or the release changed since the preview: preview again')  // expectedToken (alpha)
        break
      case KnownProblemSlugs.MERGE_BASE_MISSING:
        // the merge base is not installed here: install it first, then merge again
        log.warn(`install ${e.missingBaseVersion} (${e.missingBaseFingerprint}) first`)
        break
      case KnownProblemSlugs.CATALOG_VERSION_BEHIND:
        log.warn(`the draft is behind ${e.behind.join(', ')}`)                  // releaseCatalogDraft: name them in acknowledgedBehind
        break
      case KnownProblemSlugs.CATALOG_DRAFT_IN_USE:
        // deleteCatalogRelease: drafts of other catalogs name the draft, or it alone holds resources
        // something outside it uses (resourceType/resourceKey); change those first
        log.warn(`named by ${e.dependents.map((d) => `${d.catalogKey}: ${d.releaseName}`).join(', ')}; holds ${e.heldResources.join(', ')}`)
        break
      case KnownProblemSlugs.AMBIGUOUS_VARIANT:
        log.warn(`several variants match: ${e.tiedVariants.join(', ')}`)        // generateDocument, previewDocument: name one by variantKey
        break
      case KnownProblemSlugs.FORBIDDEN:
        // any refused authorization: a permission in a tenant, access to a tenant, a platform role,
        // or a policy (refusalReason), each read from this problem only
        log.warn(`refused: missing ${e.requiredPermission} in ${e.refusedTenantKey}`)
        break
      case KnownProblemSlugs.CATALOG_RENDERING_DEFAULTS_TOO_NEW:
        // importCatalog / installCatalogRelease: the release renders with rendering defaults this
        // installation does not have
        log.warn(`upgrade Epistola: the release needs ${e.extensions.renderingDefaultsVersion}, this has up to ${e.extensions.supportedVersion}`)
        break
      case KnownProblemSlugs.RELEASE_NUMBER_TAKEN:
        // publishReleaseToRegistry (alpha): the registry has this version with other content;
        // importCatalog, installCatalogRelease, installRegistryRelease: the tenant has this version
        // with another fingerprint, or this release id as another release
        log.warn('release the changes under a new version')
        break
      case KnownProblemSlugs.CATALOG_SOURCE_UNAVAILABLE:
        // installCatalogRelease / listAvailableCatalogReleases: 503 when the catalog's source could
        // not be reached (Retry-After may say when), 502 when it answered badly
        log.warn(`catalog source unavailable (${e.problemStatus}): ${e.detail}`)
        break
      default:
        throw e                                                                  // a type this client does not know: handle it by e.problemStatus
    }
  } else if (e instanceof ResponseError) {
    // an error response without a problem document
  }
}

每个字段出错 code 说为什么球场失败(min-length, (中文(简体) ), stencil-recursion, (中文(简体) ), catalog-resources-not-ready,或为 undefined 当服务器发送无。 比较一下 KnownValidationCodes生成自合同登记处,并保留 default 分支: 新建 代码可能会以小版本出现.

ProblemDetailException 扩展生成的 ResponseError,因此已经存在 instanceof ResponseError 网站继续工作。 它会暴露 type, (中文(简体) ), typeSlug, (中文(简体) ), title, (中文(简体) ), problemStatus, (中文(简体) ), detail, (中文(简体) ), instance, (中文(简体) ), help (该类型带有此请求上下文的页面,例如 ?operation=getTemplate,或 undefined:在类型上切换,而不是在这个上,打开它以示提示), errors, (中文(简体) ), validationErrors, (中文(简体) ), missingFields, (中文(简体) ), invalidFields, (中文(简体) ), missingDependencies, (中文(简体) ), unreleasedDependencies, (中文(简体) ), readOnlyReason, (中文(简体) ), unresolvedConflicts, (中文(简体) ), isMergeConflictsUnresolvedProblem, (中文(简体) ), missingBaseVersion, (中文(简体) ), missingBaseFingerprint, (中文(简体) ), isMergeBaseMissingProblem, (中文(简体) ), behind, (中文(简体) ), isCatalogVersionBehindProblem, (中文(简体) ), environments, (中文(简体) ), pinnedBy, (中文(简体) ), isReleaseInUseProblem, (中文(简体) ), dependents, (中文(简体) ), heldResources, (中文(简体) ), isCatalogDraftInUseProblem, (中文(简体) ), tiedVariants, (中文(简体) ), isAmbiguousVariantProblem, (中文(简体) ), requiredPermission, (中文(简体) ), refusedTenantKey, (中文(简体) ), requiredPlatformRole, (中文(简体) ), refusalReason, (中文(简体) ), isForbiddenProblem, (中文(简体) ), extensions (每个成员) 在基地成员之外,RFC 9457的五和 help, (中文(简体) ), statusCode, (中文(简体) ), rawBody, (中文(简体) ), isValidationProblem, (中文(简体) ), isDataModelValidationProblem, (中文(简体) ), isTemplateDataProblem, (中文(简体) ), isReleaseDependenciesMissingProblem, (中文(简体) ), isReleaseDependenciesUnreleasedProblem,以及 isCatalogReadOnlyProblem。 。 。 .

A 类 release-dependencies-unreleased 问题(409) releaseCatalogDraft 被拒绝的原因是 声明草案列出其他目录的草稿,并只列出一个发布针 释放. unreleasedDependencies 列出每一份此类草案(registry, (中文(简体) ), namespace, (中文(简体) ), catalogKey, (中文(简体) ), releaseId, (中文(简体) ), releaseName; 先释放, 或发送 releaseDependencies: true 释放他们 与草案.

A 类 catalog-draft-in-use 问题(409) deleteCatalogRelease 被拒绝是因为什么 在草案之外,它仍然依赖它. dependents 列出其他目录的草稿 扶养声明名称registry, (中文(简体) ), namespace, (中文(简体) ), catalogKey, (中文(简体) ), releaseId, (中文(简体) ), releaseName, (中文(简体) ), 财务报告和审定财务报表 heldResources 资源只有它认为它以外的东西 仍然使用,因为 resourceType/resourceKey (单位:千美元)theme/house-style先换个吧.

A 类 release-dependencies-missing 问题(409) deployCatalogRelease 由于释放而被拒绝 在其他目录中,解禁针不能取出:租户没有,他们不能取出 用于获取或核实. missingDependencies 各列表,并附有 reason 保留为字符串 服务器已发送( 比较它与 MissingReleaseDependencyReason) (中文(简体) ). 使用依赖释放 默示; 通过 includeDependencies: true 明确部署:

await new EnvironmentsApi(client).deployCatalogRelease({
  tenantKey: 'acme', environmentKey: 'production',
  registry: '~', namespace: 'acme', catalogKey: 'invoices', release: '1.4.0',
  includeDependencies: true,
})

结果收集

import { ResultCollector } from '@epistola.app/epistola-client'

const collector = ResultCollector.builder()
  .client(client)
  .tenantKey('acme')
  .handler(async (result) => console.log(result.requestId, result.status))
  .build()

await collector.start()   // adaptive polling; resolves after collector.stop()

一次一个结果流,批次只有在结果逐一结果之后才予以确认 处理: 丢出批量的处理器未被确认, 因此它被重新交付。 gzip 总是 已解码;zstd提供并解码到Node的zlib拥有它的地方(22.15+). partitionFor, (中文(简体) ), isMyPartition 财务报告和审定财务报表 routingKeyToMe 从任务中计算服务器报告的一个路由密钥 他们的结果会回到这个节点.

模板数据验证

模板数据由 爱皮斯托拉,通过 validateTemplateData这里没有编译, 因此,没有计划编译器可以安装,判决不能不同意哪一代人会 do ——服务器在提交任务时验证相同数据,并且它也知道哪个可选 字段所显示的释放实际上已读取.

import { ServerTemplateDataValidator, TemplateSchemaValidator, ValidatingGenerationApi, validateCreateTenantRequest } from '@epistola.app/epistola-client'

const validator = new TemplateSchemaValidator(templatesApi)
// tenant, catalog registry (`~` for the server's default, or in full: 'acme.com'), namespace, catalog key, template
await validator.validate('acme', '~', 'acme', 'invoices', 'invoice', data)   // throws TemplateDataValidationException

// Against a particular release: a version range, an environment, or both together
const pinned = new TemplateSchemaValidator(
  new ServerTemplateDataValidator(templatesApi, { versionRange: '^1.4.0', environmentKey: 'production' }),
)

// Or around generation, which turns the server's `template-data-invalid` problem into the same error:
const generation = new ValidatingGenerationApi(new GenerationApi(client), templatesApi)

// A request model against the constraints the contract declares on it (key patterns, lengths, ranges):
validateCreateTenantRequest({ key: 'Acme Corp', name: 'Acme' })  // throws ModelValidationException

每一次失败都带有 JSON 指针 输入数据(/customer/emailJSON斯克马 keyword 失败了,一个人能读 message ——所以一种形式可以标记错误的字段.

正在验证处理中

避免每次检查的请求——如在飞行前进行大量检查——执行 TemplateDataValidator 在您所选择的编译器之上, 而不是通过 API :

const validator = new TemplateSchemaValidator(new MyAjvValidator(templatesApi, '1.4.0'))
const generation = new ValidatingGenerationApi(new GenerationApi(client), new MyAjvValidator(templatesApi, '1.4.0'))

解答进程内声明的验证器 preflightsGeneration = true (默认),故 ValidatingGenerationApi 在提交和报告每批物品之前进行核对 路径前缀 items[<index>]。 。 。 . TtlSchemaCache 仍然导出用于缓存取来的计划; 不需要"本地"验证员 就需要每通电话的往返费 模版在发布 他们的目录,所以一个本地验证器从您命名的发布中获取其计划( ) 1.4.0 名称: 您将发布, 因为服务器验证 相对于该发布, 而不是反对草稿.

有两件事,一个是执行的,因为呼吁者都提到 TemplateDataValidator: 一个空 数组表示数据是可以接受的,而且 path 是JSON指针 编纂者不同意这一点 最后一点——Ajv's instancePath 已经是一个指针,但是要单独列出一个丢失的财产, 而其他引擎则会发出JSONPath——因此转换是适配器的工作. 一个有效的Ajv适配器, 大约四十行,在 test/validation/local/ajvTemplateDataValidator.ts; 拷贝.

注意当地的判决是自己的: format 语法、方言覆盖范围和未知关键词 处理是编译器的选择,因此它可以接受数据 Epistola 会拒绝,或者拒绝数据 埃皮斯托拉会变的.

发展

客户端由捆绑的光谱生成. 从寄存器根 :

make bundle                       # produce openapi.yaml
cd contracts/api/clients/nodejs-fetch
pnpm install --frozen-lockfile
./generate.sh                     # stock client + derived sources into src/generated/
pnpm build                        # dist/
pnpm test                         # node:test, against the compiled tree

src/generated/ (股票客户端 api/,并生成 contractVersion, (中文(简体) ), contractIdentity, (中文(简体) ), contractMediaTypes, (中文(简体) ), contractOperations, (中文(简体) ), knownProblemSlugs 财务报告和审定财务报表 modelValidation)被从光谱中分出并重建出每次. 跨客户的合规性 套件驱动此客户端 make conformance-node。 。 。 .

版本与发布记录

@epistola.app/epistola-client 共发布 17 个版本,首次发布于 2026/09/09;过去 12 个月发布了 17 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
2.0.0latest2026/11/121 天前
2.0.0-rc.2next2026/11/103 天前
2.0.0-rc.12026/11/094 天前
2.0.0-beta.42026/11/077 天前
2.0.0-beta.32026/11/049 天前
2.0.0-beta.22026/11/0410 天前

关于 @epistola.app/epistola-client 的常见问题

@epistola.app/epistola-client 是什么?

Epistola文档生成API的Node.js客户端,在平台的自取. 由Epistola OpenAPI合同产生,有身份头,RFC 9457问题-细节错误处理,API-key和自签的JWT auth,NDJSON结果收集,

如何安装 @epistola.app/epistola-client?

在项目目录运行 npm install @epistola.app/epistola-client,或使用 pnpm add / yarn add / bun add。

@epistola.app/epistola-client 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@epistola.app/epistola-client 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@epistola.app/epistola-client 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.12.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@epistola.app/epistola-client 可以商用吗?

@epistola.app/epistola-client 使用 EUPL-1.2 许可。声明的许可为 EUPL-1.2,请阅读许可原文确认使用条件。以上为概括,不构成法律意见,请以许可原文为准。

@epistola.app/epistola-client 的下载量有多少?

根据 npm 官方下载统计,@epistola.app/epistola-client 在 2026/10/14 – 2026/11/12 这 30 天内被下载 3,038 次,日均约 101 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@epistola.app/epistola-client 还在维护吗?

最新版本 2.0.0 发布于 2026/11/12(1 天前);至今共发布 17 个版本,其中过去 12 个月发布了 17 个;首次发布于 2026/09/09。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。