Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 23 additions & 11 deletions modules/sdk/@sfmc-sdk/src/sapi/db/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import type {
DeleteResult,
InsertResult,
QueryOptions,
TxError,
TxResponse,
TxStep,
UpdateResult,
Expand Down Expand Up @@ -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);
}

Expand All @@ -64,6 +66,15 @@ async function post<T>(path: string, body: unknown): Promise<T> {
{ 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;
Expand Down Expand Up @@ -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<T>(fn: (tx: TxContext) => Promise<T>): Promise<T> {
if (_currentTxId) throw new DbError("嵌套事务暂不支持", "nested_tx", 0);
Expand All @@ -164,6 +175,7 @@ export const db = {
patch: Partial<U>
): Promise<U> => {
push({ op: "update", table, id: String(id), patch });
// 录制期无服务端合并行;返回 patch 仅作入队确认,勿当完整行(LSP 残差)
return patch as U;
},
delete: async (table: string, id: string | number, opts?: { hard?: boolean }) => {
Expand All @@ -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 <U = unknown>(name: string, input: Record<string, unknown>): Promise<U> => {
call: async (name: string, input: Record<string, unknown>): Promise<void> => {
push({ op: "service", name, input });
// 录制期无服务端 result 可回放;勿依赖返回值
return undefined as unknown as U;
},
};

Expand All @@ -188,12 +198,13 @@ export const db = {
throw e;
}

const res = await post<TxResponse | TxError>("/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<TxResponse>("/api/sfmc/db/tx", { steps });
return userResult;
} finally {
_currentTxId = null;
}
return userResult;
},

/** 平台预置:审计日志(自动写 _audit 表) */
Expand Down Expand Up @@ -232,5 +243,6 @@ export interface TxContext {
): Promise<T>;
delete(table: string, id: string | number, opts?: { hard?: boolean }): Promise<void>;
audit(table: string, rowId: string | number, action: string, data?: Record<string, unknown>): Promise<void>;
call<T = unknown>(name: string, input: Record<string, unknown>): Promise<T>;
/** 入队跨模块 service;录制期无返回值(两阶段读回另议) */
call(name: string, input: Record<string, unknown>): Promise<void>;
}
13 changes: 11 additions & 2 deletions modules/sdk/@sfmc-sdk/src/sapi/runtime/httpdb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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。
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions modules/sdk/@sfmc-sdk/src/sapi/service/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 12 additions & 1 deletion tools/lib/npm-publish-packages.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Expand All @@ -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 };
}
2 changes: 1 addition & 1 deletion tools/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
4 changes: 4 additions & 0 deletions tools/resolve-npm-publish-tag.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Loading