@astram/engine

中文文档
0.20.0发布于 2026/11/06MITTypeScriptCommonJS近 30 天下载 5017

Astram framework — the paved road. ONE unbypassable write pipe (resolve-schema → permission gate → derive-write-context → validate → compute → write → junction-sync → audit → emit-event) over real physical tables, that every feature's CRUD flows through.

原始使用文档
文档目录3 节
  1. Duplicate keys: UniqueConflictError
  2. lockForUpdate and locked fetches: the walled lock
  3. Behaviour change (0.19)

@astram/engine

The Astram paved road: one fixed, transactional write pipe (type pre-gate → fetch-record → permission gate → derive → validate → write → junction sync → audit) over real tables. Feature code receives only the RecordEngine facade (RECORD_ENGINE / RecordsService), never a pool. The permission gate and the audit stage are mandatory. The consumer guide is @astram/guide (CAPABILITIES.md).

Duplicate keys: UniqueConflictError

A record create, update, createMany row or operation-write create that hits a UNIQUE index (driver errno 1062) throws UniqueConflictError (GAPS #58, docs/features/unique-conflict-error/SPEC.md). Guard's global filter answers it with 409 { statusCode: 409, code: 'unique_conflict', message: 'This record conflicts with an existing one.' }.

  • Fields: typeKey, constraint (the index name, e.g. uq_site_code, or null when the driver message cannot be read safely), rowIndex (createMany: the 0-based row; the whole batch rolled back), and read-only errno: 1062 / code: 'ER_DUP_ENTRY' for older catch code.
  • Match it with isUniqueConflict(err, '<constraint>'). The driver's sqlMessage and message are not exposed: they name the value and carry the SQL parameters. The error never carries a value and has no cause.
  • Inside transaction(fn) the failed write makes the transaction rollback-only: catching the error and writing again throws EngineUnavailableError (503), and nothing commits. For the idempotent answer, re-read in a fresh transaction after the catch.
  • Walled types. A non-root create on a type with readScope (not create: 'unscoped') whose input lies outside the actor's write scope answers create_outside_write_scope (403) even when the value is a duplicate, exactly like a fresh value (no oracle on values the actor cannot reach). The scope check on a duplicate compares the input byte-exactly, so a case or trailing-space variant of a scope value on a case-insensitive string column also answers 403 (fail closed); uuid scope columns are always canonical. A supplied primary key that collides with a row the actor cannot reach also answers 403, never a 409 confirming the id exists. If that scope check cannot run, the create fails with EngineUnavailableError (503).
  • Tenant types: lead every UNIQUE index with the tenant column (e.g. UNIQUE (s_tenant, s_code)). An index without it spans tenants, so a 409 tells the caller that another tenant holds the value.

lockForUpdate and locked fetches: the walled lock

lockForUpdate(typeKey, id) (inside transaction(fn)), the update/delete pre-fetch and tx.authorize(typeKey, id, action) inside a transaction lock a row only when the actor could write it: the same WHERE as an update (whereById: primary key + the actor's tenant + the readScope write wall; root: primary key only). Before any FOR UPDATE, the engine checks that the row exists for this actor with a read of the primary key and wall columns only, on the transaction and, when needed, on a dedicated lock-probe pool (2 connections per worker, same account and settings as the engine pool, READ COMMITTED per probe, acquire timeout 2000 ms). A missing, foreign-tenant or out-of-scope id takes no row lock.

Behaviour change (0.19)

Before this version, lockForUpdate on a missing, foreign or out-of-scope id was silently not locked (and a foreign id was locked by its primary key). From this version it throws the same deny an update of that id gets: AuthorizationDeniedError with reason no_record_permit (one answer for all three, no existence signal). Code that locks first and checks for a missing row after must either check existence first (get inside the transaction, then lockForUpdate) or catch that deny. The deny comes before any lock statement, so a caller that catches it may continue and commit the transaction.

  • EngineUnavailableError from lockForUpdate or a locked fetch means the lock-probe pool did not answer (acquire timeout, connection or query error). No lock was taken; let it propagate (503). Failures are counted (MariaDbUnitOfWork.lockProbeStats(), @astram/engine/internal) and logged as one warning per minute with the count, never ids.
  • A row inserted by another transaction but not yet committed is not found (the old primary-key lock waited for it).
  • If a row is deleted between the existence check and the lock, the FOR UPDATE takes a gap lock on the primary-key range around that id until the transaction ends. It locks no existing row and reveals nothing.
  • The application's ADR-0018 connection budget counts 2 more connections per worker (@astram/guard ENGINE_LOCK_PROBE_POOL_CONNECTIONS), in both database layouts.

Spec: docs/features/agent-proposals/SPEC.md §5 J (GAPS #51).

版本与发布记录

@astram/engine 共发布 33 个版本,首次发布于 2026/07/18;过去 12 个月发布了 33 个版本。

版本发布日期距今
0.20.0latest2026/11/061 天前
0.19.02026/11/034 天前
0.18.02026/11/034 天前
0.17.22026/11/016 天前
0.17.12026/10/308 天前
0.17.02026/10/2810 天前

关于 @astram/engine 的常见问题

@astram/engine 是什么?

Astram framework — the paved road. ONE unbypassable write pipe (resolve-schema → permission gate → derive-write-context → validate → compute → write → junction-sync → audit → emit-event) over real physical tables, that every feature's CRUD flows through.

如何安装 @astram/engine?

在项目目录运行 npm install @astram/engine,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:rxjs(^7)、@nestjs/core(^11)、@nestjs/common(^11)、reflect-metadata(^0.2)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@astram/engine 支持 TypeScript 吗?

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

@astram/engine 支持 ESM 还是 CommonJS?

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

@astram/engine 可以商用吗?

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

@astram/engine 的下载量有多少?

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

@astram/engine 还在维护吗?

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