From 1eb1a5dd1b35ed85cceac040ea058e0ebc1977a7 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 22 Jul 2026 14:35:26 +0000 Subject: [PATCH] =?UTF-8?q?fix(wf):=20tools@0.1.1=20=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=AF=B9=E9=BD=90=20+=20HttpDB/db.tx=20SOLID=20=E8=A1=A5?= =?UTF-8?q?=E5=BC=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit npm-publish 在 tag @sfmc-bds/tools@v0.1.1 失败:package.json 仍为 0.1.0。 同步 bump tools 到 0.1.1;空 authToken 回落平台默认(DIP);tx.call 改为 Promise 并保留 tx 失败 step/code(LSP);publish 清单校验路径/name(DRY)。 Co-authored-by: Shiroha --- modules/sdk/@sfmc-sdk/src/sapi/db/client.ts | 34 +++++++++++++------ .../sdk/@sfmc-sdk/src/sapi/runtime/httpdb.ts | 13 +++++-- .../sdk/@sfmc-sdk/src/sapi/service/client.ts | 3 ++ package-lock.json | 2 +- tools/lib/npm-publish-packages.mjs | 13 ++++++- tools/package.json | 2 +- tools/resolve-npm-publish-tag.mjs | 4 +++ 7 files changed, 55 insertions(+), 16 deletions(-) diff --git a/modules/sdk/@sfmc-sdk/src/sapi/db/client.ts b/modules/sdk/@sfmc-sdk/src/sapi/db/client.ts index e3a5e4ff..a83831f6 100644 --- a/modules/sdk/@sfmc-sdk/src/sapi/db/client.ts +++ b/modules/sdk/@sfmc-sdk/src/sapi/db/client.ts @@ -17,7 +17,6 @@ import type { DeleteResult, InsertResult, QueryOptions, - TxError, TxResponse, TxStep, UpdateResult, @@ -53,6 +52,9 @@ export class DbError extends Error { } function withModuleId(path: string): string { + if (!_moduleId) { + throw new DbError("模块上下文未初始化,setDbModuleContext 未调用", "no_module_context", 0); + } return HttpDB.withModuleId(path, _moduleId); } @@ -64,6 +66,15 @@ async function post(path: string, body: unknown): Promise { { authToken: _authToken } ); if (!res.ok) { + // LSP: db/tx 失败信封带 step/code,勿只抛 generic error(死代码分支曾丢字段) + const data = res.data as { step?: number; code?: string } | undefined; + if (typeof data?.step === "number") { + throw new DbError( + `事务在 step ${data.step} 失败: ${res.error ?? "tx_failed"}`, + data.code ?? "internal", + res.status + ); + } throw new DbError(res.error ?? "db_server_error", "internal", res.status); } return res.data as T; @@ -138,7 +149,7 @@ export const db = { /** * 事务:边界在 db-server 进程。失败自动回滚,成功提交。 * 录制期:写操作(insert/update/delete/audit/call)可入队;query/get 显式抛错, - * 避免返回 []/null 假数据破坏 LSP。call 可入队但返回值不可用(两阶段协议另议)。 + * 避免返回 []/null 假数据破坏 LSP。call 入队返回 void(无服务端 result 可回放)。 */ async tx(fn: (tx: TxContext) => Promise): Promise { if (_currentTxId) throw new DbError("嵌套事务暂不支持", "nested_tx", 0); @@ -164,6 +175,7 @@ export const db = { patch: Partial ): Promise => { push({ op: "update", table, id: String(id), patch }); + // 录制期无服务端合并行;返回 patch 仅作入队确认,勿当完整行(LSP 残差) return patch as U; }, delete: async (table: string, id: string | number, opts?: { hard?: boolean }) => { @@ -173,10 +185,8 @@ export const db = { if (data) push({ op: "audit", table, rowId: String(rowId), action, data }); else push({ op: "audit", table, rowId: String(rowId), action }); }, - call: async (name: string, input: Record): Promise => { + call: async (name: string, input: Record): Promise => { push({ op: "service", name, input }); - // 录制期无服务端 result 可回放;勿依赖返回值 - return undefined as unknown as U; }, }; @@ -188,12 +198,13 @@ export const db = { throw e; } - const res = await post("/api/sfmc/db/tx", { steps }); - _currentTxId = null; - if (!res.ok) { - throw new DbError(`事务在 step ${res.step} 失败: ${res.error}`, res.code, 0); + try { + // post 已在失败信封上抛 DbError(含 step/code);成功即 TxResponse + await post("/api/sfmc/db/tx", { steps }); + return userResult; + } finally { + _currentTxId = null; } - return userResult; }, /** 平台预置:审计日志(自动写 _audit 表) */ @@ -232,5 +243,6 @@ export interface TxContext { ): Promise; delete(table: string, id: string | number, opts?: { hard?: boolean }): Promise; audit(table: string, rowId: string | number, action: string, data?: Record): Promise; - call(name: string, input: Record): Promise; + /** 入队跨模块 service;录制期无返回值(两阶段读回另议) */ + call(name: string, input: Record): Promise; } diff --git a/modules/sdk/@sfmc-sdk/src/sapi/runtime/httpdb.ts b/modules/sdk/@sfmc-sdk/src/sapi/runtime/httpdb.ts index 1cdaeff6..4a68ff93 100644 --- a/modules/sdk/@sfmc-sdk/src/sapi/runtime/httpdb.ts +++ b/modules/sdk/@sfmc-sdk/src/sapi/runtime/httpdb.ts @@ -39,6 +39,15 @@ export class HttpDB { this.authToken = token.trim(); } + /** + * 解析 Authorization token:opts 非空优先,否则 DataAdapter 默认。 + * 空字符串不算已传(与 `??` 不同),避免模块上下文未注入时阻断平台回落(DIP)。 + */ + static resolveAuthToken(opts?: HttpRequestAuthOpts): string { + const fromOpts = typeof opts?.authToken === "string" ? opts.authToken.trim() : ""; + return fromOpts || this.authToken.trim(); + } + /** * 给路径附上 ?moduleId= / &moduleId=(db/config/service 客户端共用,DRY)。 * verifyModuleAuth 只认 query 上的 moduleId。 @@ -112,8 +121,8 @@ export class HttpDB { req.body = JSON.stringify(bodyData); req.addHeader("Content-Type", "application/json"); } - // 请求级 token 优先,否则回落 DataAdapter 默认(ConfigManager) - const token = (opts?.authToken ?? this.authToken).trim(); + // 请求级非空 token 优先;空串视为未传,回落 DataAdapter 默认(DIP:勿用 ?? 阻断) + const token = HttpDB.resolveAuthToken(opts); if (token) req.addHeader("Authorization", `Bearer ${token}`); const res = await http.request(req); diff --git a/modules/sdk/@sfmc-sdk/src/sapi/service/client.ts b/modules/sdk/@sfmc-sdk/src/sapi/service/client.ts index 31208681..3a0681d8 100644 --- a/modules/sdk/@sfmc-sdk/src/sapi/service/client.ts +++ b/modules/sdk/@sfmc-sdk/src/sapi/service/client.ts @@ -45,6 +45,9 @@ export class ServiceError extends Error { } function withModuleId(path: string): string { + if (!_moduleId) { + throw new ServiceError("模块上下文未初始化,setServiceModuleContext 未调用", "no_module_context", 0); + } return HttpDB.withModuleId(path, _moduleId); } diff --git a/package-lock.json b/package-lock.json index a5cdac5c..1ecd5825 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3165,7 +3165,7 @@ }, "tools": { "name": "@sfmc-bds/tools", - "version": "0.1.0", + "version": "0.1.1", "license": "AGPL-3.0-only", "bin": { "sfmc-catalog-sync": "catalog-sync.mjs", diff --git a/tools/lib/npm-publish-packages.mjs b/tools/lib/npm-publish-packages.mjs index 121991bb..00b6bc94 100644 --- a/tools/lib/npm-publish-packages.mjs +++ b/tools/lib/npm-publish-packages.mjs @@ -59,6 +59,17 @@ export function assertPublishPackageInWorkspaces(pkgName, repoRoot = process.cwd throw new Error(`Unknown publish package: ${pkgName}`); } const pkgPath = NPM_PUBLISH_PACKAGES[resolved]; + const absPkg = path.join(repoRoot, pkgPath); + // DRY:清单路径须真实存在且 name 与键一致(防路径漂移 / 漏建包) + if (!fs.existsSync(absPkg)) { + throw new Error(`${resolved} 清单路径不存在: ${pkgPath}`); + } + const pkgJson = JSON.parse(fs.readFileSync(absPkg, "utf8")); + if (pkgJson.name !== resolved) { + throw new Error( + `${resolved} 清单路径 ${pkgPath} 的 name 为 ${JSON.stringify(pkgJson.name)},不一致(DRY)` + ); + } const workspaceDir = path.posix.dirname(pkgPath.replace(/\\/g, "/")); const rootPkgPath = path.join(repoRoot, "package.json"); const rootPkg = JSON.parse(fs.readFileSync(rootPkgPath, "utf8")); @@ -70,5 +81,5 @@ export function assertPublishPackageInWorkspaces(pkgName, repoRoot = process.cwd ` npm -w 会失败。请把该目录写入 package.json#workspaces(DRY)。` ); } - return { workspaceDir, workspaces }; + return { workspaceDir, workspaces, version: pkgJson.version }; } diff --git a/tools/package.json b/tools/package.json index a7fc45b9..f36048bd 100644 --- a/tools/package.json +++ b/tools/package.json @@ -1,6 +1,6 @@ { "name": "@sfmc-bds/tools", - "version": "0.1.0", + "version": "0.1.1", "description": "SFMC dev tools - testing before build", "type": "module", "license": "AGPL-3.0-only", diff --git a/tools/resolve-npm-publish-tag.mjs b/tools/resolve-npm-publish-tag.mjs index 2a982a7d..b0c52623 100644 --- a/tools/resolve-npm-publish-tag.mjs +++ b/tools/resolve-npm-publish-tag.mjs @@ -52,6 +52,10 @@ const pkgPath = NPM_PUBLISH_PACKAGES[resolved]; const actual = JSON.parse(fs.readFileSync(pkgPath, "utf8")).version; if (actual !== ver) { console.error(`Tag version ${ver} != package.json version ${actual} (${pkgPath})`); + console.error( + `修复:先把 ${pkgPath} bump 到 ${ver} 并合入默认分支,再 workflow_dispatch 本 tag` + + `(checkout 默认分支),或删除后重打指向含新版本 commit 的 tag。` + ); process.exit(1); }