@yss123/cage-kernel 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -9
- package/dist/authorization.d.ts +34 -0
- package/dist/authorization.js +89 -0
- package/dist/authorization.js.map +1 -0
- package/dist/brands.d.ts +10 -0
- package/dist/brands.js +11 -1
- package/dist/brands.js.map +1 -1
- package/dist/db/connect.d.ts +14 -0
- package/dist/db/connect.js +18 -2
- package/dist/db/connect.js.map +1 -1
- package/dist/db/ddl.d.ts +40 -1
- package/dist/db/ddl.js +111 -7
- package/dist/db/ddl.js.map +1 -1
- package/dist/db/event-version-schema.d.ts +9 -0
- package/dist/db/event-version-schema.js +10 -0
- package/dist/db/event-version-schema.js.map +1 -0
- package/dist/db/index.d.ts +4 -0
- package/dist/db/index.js +4 -0
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrate.d.ts +10 -0
- package/dist/db/migrate.js +9 -1
- package/dist/db/migrate.js.map +1 -1
- package/dist/db/rows.d.ts +2 -0
- package/dist/db/rows.js +6 -0
- package/dist/db/rows.js.map +1 -1
- package/dist/db/runtime-schema.d.ts +9 -0
- package/dist/db/runtime-schema.js +41 -0
- package/dist/db/runtime-schema.js.map +1 -0
- package/dist/db/session.d.ts +6 -0
- package/dist/db/session.js +6 -0
- package/dist/db/session.js.map +1 -1
- package/dist/errors.d.ts +12 -0
- package/dist/errors.js +14 -0
- package/dist/errors.js.map +1 -1
- package/dist/files/links.d.ts +10 -0
- package/dist/files/links.js +50 -0
- package/dist/files/links.js.map +1 -0
- package/dist/files/schema.d.ts +9 -0
- package/dist/files/schema.js +51 -0
- package/dist/files/schema.js.map +1 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/dist/kernel.d.ts +88 -0
- package/dist/kernel.js +82 -41
- package/dist/kernel.js.map +1 -1
- package/dist/mask.d.ts +7 -1
- package/dist/mask.js +11 -0
- package/dist/mask.js.map +1 -1
- package/dist/org.d.ts +7 -0
- package/dist/org.js +5 -0
- package/dist/org.js.map +1 -1
- package/dist/pipeline/draft.d.ts +16 -0
- package/dist/pipeline/draft.js +39 -0
- package/dist/pipeline/draft.js.map +1 -0
- package/dist/pipeline/idempotency.d.ts +6 -0
- package/dist/pipeline/idempotency.js +6 -0
- package/dist/pipeline/idempotency.js.map +1 -1
- package/dist/pipeline/invoker.d.ts +9 -0
- package/dist/pipeline/invoker.js +22 -0
- package/dist/pipeline/invoker.js.map +1 -0
- package/dist/pipeline/load.d.ts +13 -0
- package/dist/pipeline/load.js +22 -6
- package/dist/pipeline/load.js.map +1 -1
- package/dist/primitives/aggregate.d.ts +95 -2
- package/dist/primitives/aggregate.js +15 -1
- package/dist/primitives/aggregate.js.map +1 -1
- package/dist/primitives/business-process.d.ts +128 -0
- package/dist/primitives/business-process.js +62 -0
- package/dist/primitives/business-process.js.map +1 -0
- package/dist/primitives/collection.d.ts +65 -0
- package/dist/primitives/collection.js +79 -0
- package/dist/primitives/collection.js.map +1 -0
- package/dist/primitives/command.d.ts +110 -11
- package/dist/primitives/command.js +16 -0
- package/dist/primitives/command.js.map +1 -1
- package/dist/primitives/constraints.d.ts +30 -1
- package/dist/primitives/constraints.js +14 -0
- package/dist/primitives/constraints.js.map +1 -1
- package/dist/primitives/event.d.ts +36 -0
- package/dist/primitives/event.js +12 -1
- package/dist/primitives/event.js.map +1 -1
- package/dist/primitives/fields.d.ts +176 -36
- package/dist/primitives/fields.js +134 -14
- package/dist/primitives/fields.js.map +1 -1
- package/dist/primitives/flow.d.ts +173 -8
- package/dist/primitives/flow.js +29 -2
- package/dist/primitives/flow.js.map +1 -1
- package/dist/primitives/states.d.ts +34 -2
- package/dist/primitives/states.js +15 -0
- package/dist/primitives/states.js.map +1 -1
- package/dist/read.d.ts +39 -3
- package/dist/read.js +193 -48
- package/dist/read.js.map +1 -1
- package/dist/registry.d.ts +51 -1
- package/dist/registry.js +105 -5
- package/dist/registry.js.map +1 -1
- package/dist/relations.d.ts +21 -0
- package/dist/relations.js +65 -0
- package/dist/relations.js.map +1 -0
- package/dist/runtime.d.ts +35 -0
- package/dist/runtime.js +9 -0
- package/dist/runtime.js.map +1 -0
- package/dist/scope/index.d.ts +5 -0
- package/dist/scope/index.js +5 -0
- package/dist/scope/index.js.map +1 -1
- package/dist/telemetry.d.ts +8 -0
- package/dist/telemetry.js +28 -3
- package/dist/telemetry.js.map +1 -1
- package/dist/transfer/csv.d.ts +19 -0
- package/dist/transfer/csv.js +125 -0
- package/dist/transfer/csv.js.map +1 -0
- package/dist/transfer/index.d.ts +67 -0
- package/dist/transfer/index.js +271 -0
- package/dist/transfer/index.js.map +1 -0
- package/dist/transfer/schema.d.ts +9 -0
- package/dist/transfer/schema.js +36 -0
- package/dist/transfer/schema.js.map +1 -0
- package/dist/types.d.ts +38 -2
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -1
- package/dist/util/names.d.ts +6 -0
- package/dist/util/names.js +6 -0
- package/dist/util/names.js.map +1 -1
- package/dist/worker/consumers.d.ts +7 -0
- package/dist/worker/consumers.js +21 -9
- package/dist/worker/consumers.js.map +1 -1
- package/dist/worker/health.d.ts +38 -0
- package/dist/worker/health.js +20 -0
- package/dist/worker/health.js.map +1 -0
- package/dist/worker/index.d.ts +34 -1
- package/dist/worker/index.js +176 -37
- package/dist/worker/index.js.map +1 -1
- package/dist/worker/outbox.d.ts +25 -2
- package/dist/worker/outbox.js +40 -2
- package/dist/worker/outbox.js.map +1 -1
- package/dist/worker/scheduler.d.ts +10 -1
- package/dist/worker/scheduler.js +34 -4
- package/dist/worker/scheduler.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// CSV 是传输格式,不是一条新的写入通道:这里只拆分单元格、按命令输入类型转换,
|
|
2
|
+
// 必填、长度、业务约束仍由 worker 调用原命令时统一检查。
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { cageError } from '../errors.js';
|
|
5
|
+
/** UTF-8 编码后的字节上限为 1 MiB;不能用字符串长度代替中文等多字节内容的实际大小。 */
|
|
6
|
+
export const MAX_IMPORT_BYTES = 1_048_576;
|
|
7
|
+
/** 仅计数据行,不含表头;限制单任务事务、错误报告及内存使用量。 */
|
|
8
|
+
export const MAX_IMPORT_ROWS = 1000;
|
|
9
|
+
/**
|
|
10
|
+
* 把值编码为供表格软件阅读的安全单元格,对象使用 JSON,公式前缀加单引号。
|
|
11
|
+
* 因为会应用展示安全转义,导出文件不是可无损回导的命令输入模板。
|
|
12
|
+
*/
|
|
13
|
+
export function csvCell(value) {
|
|
14
|
+
const text = value == null ? '' : typeof value === 'object' ? JSON.stringify(value) : String(value);
|
|
15
|
+
// 表格软件会执行以这些字符开头的单元格。导出文件用于查看,不能把公式当作数据执行。
|
|
16
|
+
const safe = /^[=+\-@\t\r]/.test(text) ? `'${text}` : text;
|
|
17
|
+
return /[",\r\n]/.test(safe) ? `"${safe.replace(/"/g, '""')}"` : safe;
|
|
18
|
+
}
|
|
19
|
+
function invalid(message) { throw cageError('INVALID_INPUT', message); }
|
|
20
|
+
/** 接受 UTF-8 BOM、CRLF、引号内换行和双引号转义;拒绝歧义输入,不静默修正错列。 */
|
|
21
|
+
export function parseCsv(text) {
|
|
22
|
+
if (Buffer.byteLength(text, 'utf8') > MAX_IMPORT_BYTES)
|
|
23
|
+
invalid('CSV 文件不能超过 1 MiB');
|
|
24
|
+
text = text.replace(/^\uFEFF/, '');
|
|
25
|
+
const rows = [];
|
|
26
|
+
let row = [], cell = '', quoted = false, closed = false;
|
|
27
|
+
const endRow = () => {
|
|
28
|
+
row.push(cell);
|
|
29
|
+
if (row.length !== 1 || row[0] !== '')
|
|
30
|
+
rows.push(row);
|
|
31
|
+
if (rows.length > MAX_IMPORT_ROWS + 1)
|
|
32
|
+
invalid(`一次最多导入 ${MAX_IMPORT_ROWS} 行`);
|
|
33
|
+
row = [];
|
|
34
|
+
cell = '';
|
|
35
|
+
closed = false;
|
|
36
|
+
};
|
|
37
|
+
for (let i = 0; i < text.length; i++) {
|
|
38
|
+
const ch = text[i];
|
|
39
|
+
if (quoted) {
|
|
40
|
+
if (ch !== '"')
|
|
41
|
+
cell += ch;
|
|
42
|
+
else if (text[i + 1] === '"') {
|
|
43
|
+
cell += '"';
|
|
44
|
+
i++;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
quoted = false;
|
|
48
|
+
closed = true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else if (ch === ',') {
|
|
52
|
+
row.push(cell);
|
|
53
|
+
cell = '';
|
|
54
|
+
closed = false;
|
|
55
|
+
}
|
|
56
|
+
else if (ch === '\n' || ch === '\r') {
|
|
57
|
+
if (ch === '\r' && text[i + 1] === '\n')
|
|
58
|
+
i++;
|
|
59
|
+
endRow();
|
|
60
|
+
}
|
|
61
|
+
else if (ch === '"' && !cell && !closed)
|
|
62
|
+
quoted = true;
|
|
63
|
+
else if (closed || ch === '"')
|
|
64
|
+
invalid('CSV 引号格式错误');
|
|
65
|
+
else
|
|
66
|
+
cell += ch;
|
|
67
|
+
}
|
|
68
|
+
if (quoted)
|
|
69
|
+
invalid('CSV 存在未闭合的引号');
|
|
70
|
+
if (cell || row.length || closed)
|
|
71
|
+
endRow();
|
|
72
|
+
if (rows.length < 2)
|
|
73
|
+
invalid('CSV 需要表头和至少一行数据');
|
|
74
|
+
if (new Set(rows[0]).size !== rows[0].length || rows[0].some((v) => !v))
|
|
75
|
+
invalid('CSV 表头不能为空或重复');
|
|
76
|
+
for (let i = 1; i < rows.length; i++)
|
|
77
|
+
if (rows[i].length !== rows[0].length)
|
|
78
|
+
invalid(`第 ${i + 1} 条 CSV 记录的列数与表头不一致`);
|
|
79
|
+
return rows;
|
|
80
|
+
}
|
|
81
|
+
/** 只剥掉输入外壳;复杂联合类型保持字符串,由命令自己的 schema 给出校验结果。 */
|
|
82
|
+
function inner(schema) {
|
|
83
|
+
if (schema instanceof z.ZodOptional || schema instanceof z.ZodNullable || schema instanceof z.ZodDefault)
|
|
84
|
+
return inner(schema.unwrap());
|
|
85
|
+
return schema;
|
|
86
|
+
}
|
|
87
|
+
/** 导入要求对象输入,并额外允许传输层 expectedVersion;不会为 process-only 命令创建调用权限。 */
|
|
88
|
+
export function importShape(schema) {
|
|
89
|
+
if (!(schema instanceof z.ZodObject))
|
|
90
|
+
invalid('CSV 导入要求命令使用 z.object 声明输入');
|
|
91
|
+
return { ...schema.shape, expectedVersion: z.number().int().optional() };
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 表头必须是命令输入字段,空可选单元格表示未提供;数组/对象使用单元格内 JSON 编码。
|
|
95
|
+
* 仅做格式和基础类型转换,不在这里执行字段或业务规则。坏 JSON 保留原值,使该行由命令报告失败。
|
|
96
|
+
*/
|
|
97
|
+
export function csvInputs(text, schema) {
|
|
98
|
+
const [headers, ...rows] = parseCsv(text);
|
|
99
|
+
const shape = importShape(schema);
|
|
100
|
+
for (const header of headers)
|
|
101
|
+
if (!Object.hasOwn(shape, header))
|
|
102
|
+
invalid(`命令输入中没有字段 ${header}`);
|
|
103
|
+
return rows.map((cells) => Object.fromEntries(headers.flatMap((header, index) => {
|
|
104
|
+
const value = cells[index], field = shape[header], type = inner(field);
|
|
105
|
+
// 空的可选列表示不提供,避免把“保持原值”误解释成“清空字段”。
|
|
106
|
+
if (value === '' && field.isOptional())
|
|
107
|
+
return [];
|
|
108
|
+
let parsed = value;
|
|
109
|
+
if (type instanceof z.ZodNumber && value.trim() !== '')
|
|
110
|
+
parsed = Number(value);
|
|
111
|
+
else if (type instanceof z.ZodBoolean && ['true', 'false'].includes(value))
|
|
112
|
+
parsed = value === 'true';
|
|
113
|
+
else if (type instanceof z.ZodArray || type instanceof z.ZodObject || type instanceof z.ZodRecord || (field.isNullable() && value === 'null')) {
|
|
114
|
+
// JSON 错误保留原字符串,让该行在命令校验阶段失败,不丢掉其他正确行。
|
|
115
|
+
try {
|
|
116
|
+
parsed = JSON.parse(value);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
parsed = value;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return [[header, parsed]];
|
|
123
|
+
})));
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=csv.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csv.js","sourceRoot":"","sources":["../../src/transfer/csv.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { Kernel } from '../kernel.js';
|
|
3
|
+
import type { Ctx } from '../types.js';
|
|
4
|
+
import type { EventConsumer } from '../worker/outbox.js';
|
|
5
|
+
export declare const importRequestSchema: z.ZodObject<{
|
|
6
|
+
command: z.ZodString;
|
|
7
|
+
csv: z.ZodString;
|
|
8
|
+
atomic: z.ZodDefault<z.ZodBoolean>;
|
|
9
|
+
}, z.core.$strict>;
|
|
10
|
+
/** 对外任务视图;queued/processing/retrying 只是进度,只有 completed 才能尝试下载结果。 */
|
|
11
|
+
export interface TransferTask {
|
|
12
|
+
id: string;
|
|
13
|
+
kind: 'export' | 'import';
|
|
14
|
+
target: string;
|
|
15
|
+
status: 'queued' | 'processing' | 'retrying' | 'completed' | 'failed' | 'expired';
|
|
16
|
+
createdAt: string;
|
|
17
|
+
expiresAt: string;
|
|
18
|
+
finishedAt: string | null;
|
|
19
|
+
errorCode: string | null;
|
|
20
|
+
/** 只存计数和文件元数据;结果可过期为 null,不包含导入输入或命令返回的业务快照。 */
|
|
21
|
+
result: {
|
|
22
|
+
rows: number;
|
|
23
|
+
succeeded?: number;
|
|
24
|
+
failed?: number;
|
|
25
|
+
filename?: string;
|
|
26
|
+
atomic?: boolean;
|
|
27
|
+
} | null;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 原始文件和文件内容不会出现在列表、审计 diff 或 outbox 中。
|
|
31
|
+
* 队列只携带任务 id;任务查询始终绑定租户和提交人,运维权限也不能下载别人的文件。
|
|
32
|
+
*/
|
|
33
|
+
export declare class DataTransfers {
|
|
34
|
+
private readonly k;
|
|
35
|
+
constructor(k: Kernel);
|
|
36
|
+
/** 校验当前导出能力后入队;返回任务而非文件,实际范围读取由 worker 再次鉴权执行。 */
|
|
37
|
+
export(aggregate: string, params: Record<string, unknown>, ctx: Ctx, key: string): Promise<TransferTask>;
|
|
38
|
+
/** 提交时先拒绝坏 CSV 结构,单行业务错误留在任务报告;二次认证时间随任务保存供执行检查。 */
|
|
39
|
+
import(request: unknown, ctx: Ctx, key: string): Promise<TransferTask>;
|
|
40
|
+
/**
|
|
41
|
+
* 用事务咨询锁串行化同一租户/用户/幂等键,内容摘要不符即 CONFLICT,重复内容返回已有任务。
|
|
42
|
+
* 新任务、outbox 和提交审计共同提交;队列只保存 taskId,CSV/查询输入仅在受控任务记录中保存。
|
|
43
|
+
*/
|
|
44
|
+
private enqueue;
|
|
45
|
+
/** 所有任务查询先固定租户和提交人;系统运维权限不赋予读取他人文件的隐含通道。 */
|
|
46
|
+
private own;
|
|
47
|
+
/** 返回本人最近 100 个匹配任务;聚合筛选覆盖该对象的导出和各命令导入。 */
|
|
48
|
+
list(ctx: Ctx, aggregate?: string): Promise<TransferTask[]>;
|
|
49
|
+
private row;
|
|
50
|
+
get(id: string, ctx: Ctx): Promise<TransferTask>;
|
|
51
|
+
/**
|
|
52
|
+
* 下载前确认终态、实时能力及导出访问指纹;范围/部门树/明文权限变化需要重新生成文件。
|
|
53
|
+
* 必须先完成可靠下载审计,再返回文件内容,防止审计故障时仍泄露既有导出。
|
|
54
|
+
*/
|
|
55
|
+
download(id: string, ctx: Ctx): Promise<{
|
|
56
|
+
body: string;
|
|
57
|
+
filename: string;
|
|
58
|
+
contentType: string;
|
|
59
|
+
}>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 消费者锁定任务并重读用户权限;导入/导出结果与 processed_event 在同一事务提交,崩溃可安全重投。
|
|
63
|
+
* 业务拒绝通过保存点记录 failed;内部故障/并发冲突抛给 outbox 重试,不能伪装成已完成任务。
|
|
64
|
+
*/
|
|
65
|
+
export declare function transferConsumer(k: Kernel): EventConsumer;
|
|
66
|
+
/** 定期清除原文件、结果文件和授权指纹;保留轻量任务记录防止旧幂等键被重复执行。 */
|
|
67
|
+
export declare function expireTransfers(k: Kernel): Promise<void>;
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
// 导入/导出是内核提供的任务服务,不增加领域原语。业务数据仍只经过安全读侧或命令。
|
|
2
|
+
// outbox 消费事务同时提交导入写入、逐行结果和任务状态:进程崩溃时一起回滚,重投不重复导入。
|
|
3
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
4
|
+
import { sql } from 'kysely';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { cageError, toCageError } from '../errors.js';
|
|
7
|
+
import { loadUserGrants, userCtx } from '../org.js';
|
|
8
|
+
import { stableStringify } from '../util/names.js';
|
|
9
|
+
import { traceCarrier } from '../telemetry.js';
|
|
10
|
+
import { csvInputs, importShape } from './csv.js';
|
|
11
|
+
const EVENT = 'cage:DataTransferRequested';
|
|
12
|
+
const hash = (value) => createHash('sha256').update(stableStringify(value)).digest('hex');
|
|
13
|
+
export const importRequestSchema = z.object({ command: z.string(), csv: z.string(), atomic: z.boolean().default(false) }).strict();
|
|
14
|
+
function requireUser(ctx) {
|
|
15
|
+
if (ctx.actor?.kind !== 'user' || !ctx.userId)
|
|
16
|
+
throw cageError('UNAUTHENTICATED');
|
|
17
|
+
return ctx.userId;
|
|
18
|
+
}
|
|
19
|
+
function requireCap(ctx, capability) {
|
|
20
|
+
if (!ctx.capabilities.has(capability))
|
|
21
|
+
throw cageError('FORBIDDEN', { capability });
|
|
22
|
+
}
|
|
23
|
+
/** 排队、执行、下载都重读有效授权,防止任务排队期间撤权后继续沿用旧权限。 */
|
|
24
|
+
async function liveContext(k, userId, tenantId, db = k.app) {
|
|
25
|
+
const grants = await loadUserGrants(db, userId, tenantId);
|
|
26
|
+
if (!grants?.active)
|
|
27
|
+
throw cageError('FORBIDDEN', '用户已被禁用或删除');
|
|
28
|
+
return userCtx(grants);
|
|
29
|
+
}
|
|
30
|
+
/** 只有已注册、可由用户调用且对象输入可映射 CSV 的命令才能导入,仍逐行走完整写流水线。 */
|
|
31
|
+
function importCommand(k, capability, ctx) {
|
|
32
|
+
const command = k.registry.command(capability);
|
|
33
|
+
if (!command || !command.cmd.invokedBy.includes('user'))
|
|
34
|
+
throw cageError('NOT_FOUND');
|
|
35
|
+
requireCap(ctx, capability);
|
|
36
|
+
importShape(command.cmd.input);
|
|
37
|
+
return command;
|
|
38
|
+
}
|
|
39
|
+
/** 下载不能沿用排队时保存的授权。范围、明文权限或部门树变化后,旧文件必须重新生成。 */
|
|
40
|
+
async function accessHash(k, ctx, aggregate, db = k.app) {
|
|
41
|
+
const depts = await db.selectFrom('sys.dept').select(['id', 'ancestors', 'status'])
|
|
42
|
+
.where('tenant_id', '=', ctx.tenantId).orderBy('id').execute();
|
|
43
|
+
return hash({ user: ctx.userId, dept: ctx.deptId, path: ctx.deptPath, scopes: ctx.scopes[aggregate] ?? ctx.scopes['*'], unmask: [...ctx.unmask].sort(), depts });
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 原始文件和文件内容不会出现在列表、审计 diff 或 outbox 中。
|
|
47
|
+
* 队列只携带任务 id;任务查询始终绑定租户和提交人,运维权限也不能下载别人的文件。
|
|
48
|
+
*/
|
|
49
|
+
export class DataTransfers {
|
|
50
|
+
k;
|
|
51
|
+
constructor(k) {
|
|
52
|
+
this.k = k;
|
|
53
|
+
}
|
|
54
|
+
/** 校验当前导出能力后入队;返回任务而非文件,实际范围读取由 worker 再次鉴权执行。 */
|
|
55
|
+
async export(aggregate, params, ctx, key) {
|
|
56
|
+
const user = requireUser(ctx), current = await liveContext(this.k, user, ctx.tenantId);
|
|
57
|
+
if (!this.k.registry.aggregate(aggregate))
|
|
58
|
+
throw cageError('NOT_FOUND');
|
|
59
|
+
requireCap(current, `${aggregate}.export`);
|
|
60
|
+
return this.enqueue('export', aggregate, params, ctx, key);
|
|
61
|
+
}
|
|
62
|
+
/** 提交时先拒绝坏 CSV 结构,单行业务错误留在任务报告;二次认证时间随任务保存供执行检查。 */
|
|
63
|
+
async import(request, ctx, key) {
|
|
64
|
+
const user = requireUser(ctx), current = await liveContext(this.k, user, ctx.tenantId);
|
|
65
|
+
const parsed = importRequestSchema.safeParse(request);
|
|
66
|
+
if (!parsed.success)
|
|
67
|
+
throw cageError('INVALID_INPUT', '需要 command、csv 和可选的 atomic 参数');
|
|
68
|
+
const { command, csv, atomic } = parsed.data;
|
|
69
|
+
const rc = importCommand(this.k, command, current);
|
|
70
|
+
// 文件格式错误立即反馈;单行输入或业务错误留给命令校验,在任务结果中报告。
|
|
71
|
+
csvInputs(csv, rc.cmd.input);
|
|
72
|
+
return this.enqueue('import', command, { csv, atomic, stepUpAt: ctx.stepUpAt ?? null }, ctx, key);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 用事务咨询锁串行化同一租户/用户/幂等键,内容摘要不符即 CONFLICT,重复内容返回已有任务。
|
|
76
|
+
* 新任务、outbox 和提交审计共同提交;队列只保存 taskId,CSV/查询输入仅在受控任务记录中保存。
|
|
77
|
+
*/
|
|
78
|
+
async enqueue(kind, target, input, ctx, key) {
|
|
79
|
+
if (!key || key.length > 200)
|
|
80
|
+
throw cageError('INVALID_INPUT', '请提供不超过 200 字符的 Idempotency-Key');
|
|
81
|
+
// 二次认证时间属于会话上下文,不属于用户提交的文件;重试时不要因此误报冲突。
|
|
82
|
+
const { stepUpAt: _, ...requestInput } = input;
|
|
83
|
+
const requestHash = hash({ kind, target, input: requestInput });
|
|
84
|
+
const id = await this.k.app.transaction().execute(async (tx) => {
|
|
85
|
+
const lockKey = `${ctx.tenantId}:${ctx.userId}:${key}`;
|
|
86
|
+
await sql `select pg_advisory_xact_lock(hashtextextended(${lockKey}, 0))`.execute(tx);
|
|
87
|
+
const previous = await tx.selectFrom('sys.data_transfer').select(['id', 'request_hash'])
|
|
88
|
+
.where('tenant_id', '=', ctx.tenantId).where('user_id', '=', ctx.userId).where('idempotency_key', '=', key).executeTakeFirst();
|
|
89
|
+
if (previous) {
|
|
90
|
+
if (previous.request_hash !== requestHash)
|
|
91
|
+
throw cageError('CONFLICT', '同一幂等键不能提交不同的任务');
|
|
92
|
+
return previous.id;
|
|
93
|
+
}
|
|
94
|
+
const taskId = randomUUID();
|
|
95
|
+
const outbox = await tx.insertInto('sys.outbox').values({
|
|
96
|
+
tenant_id: ctx.tenantId, event: EVENT, aggregate: 'DataTransfer', aggregate_id: taskId,
|
|
97
|
+
payload: JSON.stringify({ taskId }), actor: JSON.stringify(ctx.actor), trace: JSON.stringify(traceCarrier()), request_id: ctx.requestId ?? null,
|
|
98
|
+
}).returning('id').executeTakeFirstOrThrow();
|
|
99
|
+
await tx.insertInto('sys.data_transfer').values({
|
|
100
|
+
id: taskId, tenant_id: ctx.tenantId, user_id: ctx.userId, kind, target,
|
|
101
|
+
idempotency_key: key, request_hash: requestHash, input: JSON.stringify(input), outbox_id: outbox.id,
|
|
102
|
+
}).execute();
|
|
103
|
+
await this.k.writeAudit(tx, {
|
|
104
|
+
tenantId: ctx.tenantId, actor: ctx.userId, actorKind: 'user', source: kind === 'import' ? 'import' : 'api', kind: 'ops',
|
|
105
|
+
capability: kind === 'export' ? `${target}.export` : target, command: `dataTransfer.${kind}`, result: 'queued',
|
|
106
|
+
diff: { taskId }, requestId: ctx.requestId ?? null,
|
|
107
|
+
});
|
|
108
|
+
await sql `select pg_notify('cage_outbox', '')`.execute(tx);
|
|
109
|
+
return taskId;
|
|
110
|
+
});
|
|
111
|
+
return this.get(id, ctx);
|
|
112
|
+
}
|
|
113
|
+
/** 所有任务查询先固定租户和提交人;系统运维权限不赋予读取他人文件的隐含通道。 */
|
|
114
|
+
own(ctx) {
|
|
115
|
+
return this.k.app.selectFrom('sys.data_transfer as t').innerJoin('sys.outbox as o', 'o.id', 't.outbox_id')
|
|
116
|
+
.where('t.tenant_id', '=', ctx.tenantId).where('t.user_id', '=', requireUser(ctx));
|
|
117
|
+
}
|
|
118
|
+
/** 返回本人最近 100 个匹配任务;聚合筛选覆盖该对象的导出和各命令导入。 */
|
|
119
|
+
async list(ctx, aggregate) {
|
|
120
|
+
let query = this.own(ctx);
|
|
121
|
+
// 业务列表只需要当前对象的任务;先在数据库筛选再取最近 100 条,
|
|
122
|
+
// 避免其他页面的任务把当前对象的记录挤出结果集。
|
|
123
|
+
if (aggregate) {
|
|
124
|
+
if (!this.k.registry.aggregate(aggregate))
|
|
125
|
+
throw cageError('NOT_FOUND');
|
|
126
|
+
query = query.where((eb) => eb.or([eb('t.target', '=', aggregate), eb('t.target', 'like', `${aggregate}.%`)]));
|
|
127
|
+
}
|
|
128
|
+
const rows = await query.select(['t.id', 't.kind', 't.target', 't.status', 't.result', 't.error_code', 't.created_at', 't.finished_at', 't.expires_at', 'o.status as delivery', 'o.attempts'])
|
|
129
|
+
.orderBy('t.created_at', 'desc').limit(100).execute();
|
|
130
|
+
return rows.map(taskView);
|
|
131
|
+
}
|
|
132
|
+
async row(id, ctx) {
|
|
133
|
+
if (!z.uuid().safeParse(id).success)
|
|
134
|
+
throw cageError('NOT_FOUND');
|
|
135
|
+
const row = await this.own(ctx).selectAll('t').select(['o.status as delivery', 'o.attempts']).where('t.id', '=', id).executeTakeFirst();
|
|
136
|
+
if (!row)
|
|
137
|
+
throw cageError('NOT_FOUND');
|
|
138
|
+
return row;
|
|
139
|
+
}
|
|
140
|
+
async get(id, ctx) { return taskView(await this.row(id, ctx)); }
|
|
141
|
+
/**
|
|
142
|
+
* 下载前确认终态、实时能力及导出访问指纹;范围/部门树/明文权限变化需要重新生成文件。
|
|
143
|
+
* 必须先完成可靠下载审计,再返回文件内容,防止审计故障时仍泄露既有导出。
|
|
144
|
+
*/
|
|
145
|
+
async download(id, ctx) {
|
|
146
|
+
const row = await this.row(id, ctx);
|
|
147
|
+
if (taskView(row).status !== 'completed')
|
|
148
|
+
throw cageError('INVALID_TRANSITION', '任务尚未完成或文件已过期');
|
|
149
|
+
const current = await liveContext(this.k, requireUser(ctx), ctx.tenantId);
|
|
150
|
+
requireCap(current, row.kind === 'export' ? `${row.target}.export` : row.target);
|
|
151
|
+
if (row.kind === 'export' && row.access_hash !== await accessHash(this.k, current, row.target)) {
|
|
152
|
+
throw cageError('FORBIDDEN', '数据范围或明文权限已经变化,请重新导出');
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
await this.k.writeAudit(this.k.app, {
|
|
156
|
+
tenantId: ctx.tenantId, actor: ctx.userId, actorKind: 'user', source: 'api', kind: 'read',
|
|
157
|
+
capability: row.kind === 'export' ? `${row.target}.export` : row.target, command: 'dataTransfer.download', result: 'ok', diff: { taskId: id },
|
|
158
|
+
requestId: ctx.requestId ?? null, client: ctx.client ?? null,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
this.k.log.error({ code: 'CAGE-AUDIT-001', operation: 'dataTransfer.download', task: id, requestId: ctx.requestId }, '下载审计失败,未交付文件');
|
|
163
|
+
throw cageError('INTERNAL', { diagnostic: 'CAGE-AUDIT-001', phase: 'audit', operation: 'dataTransfer.download', retryable: true });
|
|
164
|
+
}
|
|
165
|
+
return { body: row.file_body, filename: row.result.filename, contentType: row.kind === 'export' ? 'text/csv; charset=utf-8' : 'application/json; charset=utf-8' };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/** 将任务状态与 outbox 投递证据合并;时间已过期时优先标 expired,隐藏已失效的结果元数据。 */
|
|
169
|
+
function taskView(row) {
|
|
170
|
+
const expired = new Date(row.expires_at).getTime() <= Date.now();
|
|
171
|
+
const status = expired ? 'expired' : row.status !== 'queued' ? row.status : row.delivery === 'dead' || row.delivery === 'done' ? 'failed' : row.delivery === 'processing' ? 'processing' : row.attempts > 0 ? 'retrying' : 'queued';
|
|
172
|
+
return {
|
|
173
|
+
id: row.id, kind: row.kind, target: row.target, status, result: expired ? null : row.result,
|
|
174
|
+
createdAt: new Date(row.created_at).toISOString(), expiresAt: new Date(row.expires_at).toISOString(),
|
|
175
|
+
finishedAt: row.finished_at ? new Date(row.finished_at).toISOString() : null, errorCode: row.error_code ?? (status === 'failed' ? 'INTERNAL' : null),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* 保存点让错误行只回滚自己的写入(含审计和同事务反应)。
|
|
180
|
+
* 整批模式再包一层保存点;出错时撤销全部业务写入,但保留每行的失败报告。
|
|
181
|
+
* 不保存命令返回快照或输入值,避免权限日后收回后从结果文件读到敏感内容。
|
|
182
|
+
*/
|
|
183
|
+
async function runImport(k, tx, task, ctx) {
|
|
184
|
+
const rc = importCommand(k, task.target, ctx);
|
|
185
|
+
const inputs = csvInputs(task.input.csv, rc.cmd.input);
|
|
186
|
+
const results = [];
|
|
187
|
+
await sql `savepoint cage_import_batch`.execute(tx);
|
|
188
|
+
for (const [index, input] of inputs.entries()) {
|
|
189
|
+
await sql `savepoint cage_import_row`.execute(tx);
|
|
190
|
+
try {
|
|
191
|
+
await k.executeIn(tx, rc.cmd, input, ctx);
|
|
192
|
+
results.push({ row: index + 2, ok: true });
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
await sql `rollback to savepoint cage_import_row`.execute(tx);
|
|
196
|
+
const error = toCageError(err);
|
|
197
|
+
// 程序错误、连接故障和死锁属于任务失败,交给 outbox 重试,不能伪装成坏数据行。
|
|
198
|
+
if (error.code === 'INTERNAL' || ['40001', '40P01'].includes(err?.code))
|
|
199
|
+
throw err;
|
|
200
|
+
// 只报告出错的输入字段名,不保存原始值、数据库错误文本或完整领域错误 detail。
|
|
201
|
+
const fields = error.code === 'INVALID_INPUT' && Array.isArray(error.detail)
|
|
202
|
+
? error.detail.filter((issue) => typeof issue.path === 'string').map((issue) => issue.path) : [];
|
|
203
|
+
results.push({ row: index + 2, ok: false, code: error.code,
|
|
204
|
+
...(error.domainCode ? { domainCode: error.domainCode } : {}), ...(fields.length ? { fields } : {}) });
|
|
205
|
+
}
|
|
206
|
+
await sql `release savepoint cage_import_row`.execute(tx);
|
|
207
|
+
}
|
|
208
|
+
if (task.input.atomic && results.some((r) => !r.ok)) {
|
|
209
|
+
await sql `rollback to savepoint cage_import_batch`.execute(tx);
|
|
210
|
+
for (const row of results)
|
|
211
|
+
if (row.ok) {
|
|
212
|
+
row.ok = false;
|
|
213
|
+
row.code = 'ABORTED';
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
await sql `release savepoint cage_import_batch`.execute(tx);
|
|
217
|
+
const succeeded = results.filter((r) => r.ok).length;
|
|
218
|
+
const result = { rows: results.length, succeeded, failed: results.length - succeeded, atomic: task.input.atomic, filename: `import-${task.id}.json` };
|
|
219
|
+
return { result, body: JSON.stringify({ ...result, results }, null, 2), access: null };
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* 消费者锁定任务并重读用户权限;导入/导出结果与 processed_event 在同一事务提交,崩溃可安全重投。
|
|
223
|
+
* 业务拒绝通过保存点记录 failed;内部故障/并发冲突抛给 outbox 重试,不能伪装成已完成任务。
|
|
224
|
+
*/
|
|
225
|
+
export function transferConsumer(k) {
|
|
226
|
+
return {
|
|
227
|
+
name: 'cage:data-transfer', event: EVENT,
|
|
228
|
+
async handle(event, tx) {
|
|
229
|
+
const task = await tx.selectFrom('sys.data_transfer').selectAll().where('id', '=', event.aggregate_id)
|
|
230
|
+
.where('tenant_id', '=', event.tenant_id).forUpdate().executeTakeFirstOrThrow();
|
|
231
|
+
if (task.status !== 'queued')
|
|
232
|
+
return;
|
|
233
|
+
if (new Date(task.expires_at).getTime() <= Date.now()) {
|
|
234
|
+
await tx.updateTable('sys.data_transfer').set({ status: 'expired', input: null }).where('id', '=', task.id).execute();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
// 整个任务的业务失败也使用保存点,使 PostgreSQL 错误后仍能可靠记录失败状态。
|
|
238
|
+
await sql `savepoint cage_transfer`.execute(tx);
|
|
239
|
+
try {
|
|
240
|
+
const ctx = await liveContext(k, task.user_id, task.tenant_id, tx);
|
|
241
|
+
ctx.requestId = event.request_id ?? undefined;
|
|
242
|
+
ctx.source = task.kind === 'import' ? 'import' : 'api';
|
|
243
|
+
ctx.stepUpAt = task.input.stepUpAt;
|
|
244
|
+
let output;
|
|
245
|
+
if (task.kind === 'export') {
|
|
246
|
+
const access = await accessHash(k, ctx, task.target, tx);
|
|
247
|
+
const file = await k.read.export(task.target, task.input, ctx, tx);
|
|
248
|
+
output = { result: { rows: file.rows, filename: file.filename }, body: file.body, access };
|
|
249
|
+
}
|
|
250
|
+
else
|
|
251
|
+
output = await runImport(k, tx, task, ctx);
|
|
252
|
+
await tx.updateTable('sys.data_transfer').set({ status: 'completed', result: JSON.stringify(output.result), file_body: output.body,
|
|
253
|
+
access_hash: output.access, input: null, finished_at: sql `now()` }).where('id', '=', task.id).execute();
|
|
254
|
+
}
|
|
255
|
+
catch (err) {
|
|
256
|
+
await sql `rollback to savepoint cage_transfer`.execute(tx);
|
|
257
|
+
const error = toCageError(err);
|
|
258
|
+
if (error.code === 'INTERNAL' || error.code === 'CONFLICT')
|
|
259
|
+
throw err;
|
|
260
|
+
await tx.updateTable('sys.data_transfer').set({ status: 'failed', error_code: error.code, input: null, finished_at: sql `now()` }).where('id', '=', task.id).execute();
|
|
261
|
+
}
|
|
262
|
+
await sql `release savepoint cage_transfer`.execute(tx);
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
/** 定期清除原文件、结果文件和授权指纹;保留轻量任务记录防止旧幂等键被重复执行。 */
|
|
267
|
+
export async function expireTransfers(k) {
|
|
268
|
+
await k.app.updateTable('sys.data_transfer').set({ status: 'expired', input: null, file_body: null, result: null, access_hash: null })
|
|
269
|
+
.where('expires_at', '<=', new Date()).where('status', '<>', 'expired').execute();
|
|
270
|
+
}
|
|
271
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/transfer/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// 独立的前向系统迁移;不改动已发布的 cage:0001_system 校验和。
|
|
2
|
+
// 文件与任务存在同一 PostgreSQL 中,API/worker 不需要共享本地磁盘。
|
|
3
|
+
// 原始文件和结果仅保留 24 小时;索引记录保留用于幂等及操作追踪。
|
|
4
|
+
/**
|
|
5
|
+
* 任务用租户/用户/幂等键去重,并关联 outbox 以复用投递与死信机制。
|
|
6
|
+
* access_hash 绑定生成结果时的访问条件,下载时还需核对当前权限;过期清理内容但保留任务索引。
|
|
7
|
+
* 此处 CSV 内容存数据库,与附件模块的字节存储策略不同;保持已发布 SQL 文本不变。
|
|
8
|
+
*/
|
|
9
|
+
export const TRANSFER_MIGRATION = {
|
|
10
|
+
name: 'cage:0002_data_transfer',
|
|
11
|
+
sql: `
|
|
12
|
+
CREATE TABLE sys.data_transfer (
|
|
13
|
+
id uuid PRIMARY KEY,
|
|
14
|
+
tenant_id uuid NOT NULL,
|
|
15
|
+
user_id uuid NOT NULL,
|
|
16
|
+
kind text NOT NULL CHECK (kind IN ('export', 'import')),
|
|
17
|
+
target text NOT NULL,
|
|
18
|
+
idempotency_key text NOT NULL,
|
|
19
|
+
request_hash text NOT NULL,
|
|
20
|
+
input jsonb,
|
|
21
|
+
status text NOT NULL DEFAULT 'queued',
|
|
22
|
+
outbox_id bigint NOT NULL REFERENCES sys.outbox(id),
|
|
23
|
+
result jsonb,
|
|
24
|
+
file_body text,
|
|
25
|
+
access_hash text,
|
|
26
|
+
error_code text,
|
|
27
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
28
|
+
finished_at timestamptz,
|
|
29
|
+
expires_at timestamptz NOT NULL DEFAULT now() + interval '24 hours',
|
|
30
|
+
UNIQUE (tenant_id, user_id, idempotency_key)
|
|
31
|
+
);
|
|
32
|
+
CREATE INDEX data_transfer_owner_idx ON sys.data_transfer (tenant_id, user_id, created_at DESC);
|
|
33
|
+
CREATE INDEX data_transfer_expiry_idx ON sys.data_transfer (expires_at) WHERE status <> 'expired';
|
|
34
|
+
`,
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/transfer/schema.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,+CAA+C;AAC/C,oCAAoC;AACpC;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,IAAI,EAAE,yBAAyB;IAC/B,GAAG,EAAE;;;;;;;;;;;;;;;;;;;;;;;CAuBN;CACA,CAAC"}
|