@atelia/pg-change-context

中文文档
0.1.1发布于 2026/08/10MITTypeScriptCommonJS零依赖构建来源已验证近 30 天下载 1871

Emit application context alongside PostgreSQL data changes for logical-decoding CDC

原始使用文档
文档目录7 节
  1. What it does
  2. The one thing to get right
  3. What this package deliberately does not do
  4. Why the context is an argument
  5. Behaviour
  6. Releasing
  7. Licence

@atelia/pg-change-context

Emits application context alongside PostgreSQL data changes, for consumers that read it out of the write-ahead log via logical decoding.

MIT licensed. Independent of the rest of this repository, which is SSPL-1.0 — see Licence.

What it does

One thing: issues pg_logical_emit_message(true, '_bemi', <json>) on a client you give it.

import { emitChangeContext } from '@atelia/pg-change-context'

await prisma.$transaction(async (tx) => {
  await emitChangeContext(tx, { tenantId, userId })
  await tx.thing.update({ ... })
})

The message is transactional, so it becomes visible only if the transaction commits, and a rollback discards it exactly as it discards the writes. A CDC consumer pairs it to the changes by transaction id.

The one thing to get right

Emit on the same client the writes go to. Inside an interactive transaction that is the transaction client, not the top-level one.

Emitting on the top-level client sends the message over a different connection, in a different transaction. It then has a different transaction id, so it pairs with nothing, and it does not roll back with the writes. Both failures are silent: changes are recorded correctly but with no context, which is indistinguishable from a code path that never set any.

What this package deliberately does not do

It does not decide whether to open a transaction.

A lone write with no enclosing transaction is the awkward case. Prisma autocommits it, so emitting and then writing produces two transactions, two transaction ids, and no pairing. Making them atomic means opening a transaction — but doing that when the caller already has one is what breaks the caller's transaction.

So the rule is: wrap when there is no caller transaction, never wrap when there is. This package cannot evaluate that rule, because only the application's transaction manager knows whether one is open. Guessing produces exactly the defect this package exists to avoid.

Own that decision where transactions are already managed — a CLS-scoped transaction host, a unit-of-work, or an explicit $transaction at the call site — and call emitChangeContext with the client that manager hands you.

If you do wrap a previously-autocommitting write, keep the transaction to exactly the emit and the write. Nothing else belongs inside it — no reads, no service calls, no outbound requests. A write that used to hold a pooled connection for a single statement now holds one for a transaction, on what is usually the hottest path in the system, and anything added inside later extends that hold. Worth asserting the statement count where the wrapping is implemented, so a third statement fails a test rather than quietly changing connection behaviour under load.

Why the context is an argument

The context is passed in, rather than read from an ambient store the library owns. That is deliberate.

An emitter that reads from async-local storage inherits whatever is in the store, and a store populated with enterWith can persist into later work on the same execution context. The failure that produces is not a missing context — it is one request's tenant and user attached to another request's changes. On any system where attribution matters, a change credited to the wrong tenant is worse than a change credited to nobody: the first is wrong and looks right, the second is merely incomplete and obvious.

Taking the value as an argument makes that class of bug unreachable here. If the calling application keeps context in a request scope, it resolves it there and passes the result — the scoping stays where the request lifecycle is understood, and this package cannot outlive it.

Behaviour

InputResult
Empty context {}Returns without emitting
Context over the byte limitThrows — never truncated or skipped
Non-serialisable valueThrows
Not a plain objectThrows
Executor rejectsPropagates

It returns void. Every failure throws, so there is no result to check and no way to ignore one — a call that returns normally either emitted or had nothing to emit. A boolean would have to be checked to mean anything, and an unchecked return that quietly meant "did nothing" is the shape of bug this package exists to remove.

Nothing here fails quietly. A context that did not reach the log means changes will be attributed to nobody, and only the caller can decide whether that should fail the write.

The limit defaults to 8 KiB and is per-call configurable. It is generous relative to real payloads; exceeding it usually means something unintended is being attached.

Releasing

Tagged in its own namespace, separate from the bemi-v* tags that select what the deployment pipeline builds.

# bump "version" in context/package.json, commit, then:
git tag pg-change-context-v0.2.0
git push origin pg-change-context-v0.2.0

.github/workflows/publish-context.yml runs the full repository gate before publishing, and refuses if the tag disagrees with the manifest version or if the tarball is missing its licence or build output.

Licence

MIT — see LICENSE in this directory.

The rest of this repository is SSPL-1.0. This package is separate and imports nothing from it, so that applications can link it without their own licensing being affected. scripts/licence-boundary.sh enforces that separation as a build failure rather than a convention, because the import that would break it looks like a harmless cleanup.

版本与发布记录

@atelia/pg-change-context 共发布 3 个版本,首次发布于 2026/08/10;过去 12 个月发布了 3 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.1.1latest2026/08/102 个月前
0.1.02026/08/102 个月前
0.0.12026/08/102 个月前

关于 @atelia/pg-change-context 的常见问题

@atelia/pg-change-context 是什么?

Emit application context alongside PostgreSQL data changes for logical-decoding CDC

如何安装 @atelia/pg-change-context?

在项目目录运行 npm install @atelia/pg-change-context,或使用 pnpm add / yarn add / bun add。

@atelia/pg-change-context 支持 TypeScript 吗?

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

@atelia/pg-change-context 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@atelia/pg-change-context 需要什么版本的 Node.js?

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

@atelia/pg-change-context 可以商用吗?

@atelia/pg-change-context 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@atelia/pg-change-context 的下载量有多少?

根据 npm 官方下载统计,@atelia/pg-change-context 在 2026/10/07 – 2026/11/05 这 30 天内被下载 1,871 次,日均约 62 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@atelia/pg-change-context 还在维护吗?

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