@yss123/cage-kernel 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +20 -2
- package/dist/brands.d.ts +32 -0
- package/dist/brands.js +35 -0
- package/dist/brands.js.map +1 -0
- package/dist/db/connect.d.ts +28 -0
- package/dist/db/connect.js +57 -0
- package/dist/db/connect.js.map +1 -0
- package/dist/db/ddl.d.ts +69 -0
- package/dist/db/ddl.js +522 -0
- package/dist/db/ddl.js.map +1 -0
- package/dist/db/index.d.ts +5 -0
- package/dist/db/index.js +7 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/migrate.d.ts +22 -0
- package/dist/db/migrate.js +87 -0
- package/dist/db/migrate.js.map +1 -0
- package/dist/db/rows.d.ts +10 -0
- package/dist/db/rows.js +59 -0
- package/dist/db/rows.js.map +1 -0
- package/dist/db/session.d.ts +4 -0
- package/dist/db/session.js +20 -0
- package/dist/db/session.js.map +1 -0
- package/dist/errors.d.ts +44 -0
- package/dist/errors.js +83 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel.d.ts +99 -0
- package/dist/kernel.js +456 -0
- package/dist/kernel.js.map +1 -0
- package/dist/mask.d.ts +13 -0
- package/dist/mask.js +67 -0
- package/dist/mask.js.map +1 -0
- package/dist/org.d.ts +46 -0
- package/dist/org.js +127 -0
- package/dist/org.js.map +1 -0
- package/dist/pipeline/idempotency.d.ts +10 -0
- package/dist/pipeline/idempotency.js +29 -0
- package/dist/pipeline/idempotency.js.map +1 -0
- package/dist/pipeline/load.d.ts +15 -0
- package/dist/pipeline/load.js +87 -0
- package/dist/pipeline/load.js.map +1 -0
- package/dist/primitives/aggregate.d.ts +75 -0
- package/dist/primitives/aggregate.js +69 -0
- package/dist/primitives/aggregate.js.map +1 -0
- package/dist/primitives/command.d.ts +101 -0
- package/dist/primitives/command.js +49 -0
- package/dist/primitives/command.js.map +1 -0
- package/dist/primitives/constraints.d.ts +36 -0
- package/dist/primitives/constraints.js +41 -0
- package/dist/primitives/constraints.js.map +1 -0
- package/dist/primitives/event.d.ts +31 -0
- package/dist/primitives/event.js +44 -0
- package/dist/primitives/event.js.map +1 -0
- package/dist/primitives/fields.d.ts +127 -0
- package/dist/primitives/fields.js +151 -0
- package/dist/primitives/fields.js.map +1 -0
- package/dist/primitives/flow.d.ts +126 -0
- package/dist/primitives/flow.js +90 -0
- package/dist/primitives/flow.js.map +1 -0
- package/dist/primitives/states.d.ts +19 -0
- package/dist/primitives/states.js +37 -0
- package/dist/primitives/states.js.map +1 -0
- package/dist/read.d.ts +53 -0
- package/dist/read.js +217 -0
- package/dist/read.js.map +1 -0
- package/dist/registry.d.ts +71 -0
- package/dist/registry.js +177 -0
- package/dist/registry.js.map +1 -0
- package/dist/relations.d.ts +14 -0
- package/dist/relations.js +58 -0
- package/dist/relations.js.map +1 -0
- package/dist/scope/index.d.ts +42 -0
- package/dist/scope/index.js +90 -0
- package/dist/scope/index.js.map +1 -0
- package/dist/telemetry.d.ts +11 -0
- package/dist/telemetry.js +35 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/transfer/csv.d.ts +8 -0
- package/dist/transfer/csv.js +114 -0
- package/dist/transfer/csv.js.map +1 -0
- package/dist/transfer/index.d.ts +49 -0
- package/dist/transfer/index.js +245 -0
- package/dist/transfer/index.js.map +1 -0
- package/dist/transfer/schema.d.ts +4 -0
- package/dist/transfer/schema.js +31 -0
- package/dist/transfer/schema.js.map +1 -0
- package/dist/types.d.ts +52 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/util/names.d.ts +9 -0
- package/dist/util/names.js +38 -0
- package/dist/util/names.js.map +1 -0
- package/dist/worker/consumers.d.ts +29 -0
- package/dist/worker/consumers.js +79 -0
- package/dist/worker/consumers.js.map +1 -0
- package/dist/worker/index.d.ts +36 -0
- package/dist/worker/index.js +122 -0
- package/dist/worker/index.js.map +1 -0
- package/dist/worker/outbox.d.ts +38 -0
- package/dist/worker/outbox.js +121 -0
- package/dist/worker/outbox.js.map +1 -0
- package/dist/worker/scheduler.d.ts +7 -0
- package/dist/worker/scheduler.js +77 -0
- package/dist/worker/scheduler.js.map +1 -0
- package/package.json +58 -3
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
export interface TransferTask {
|
|
11
|
+
id: string;
|
|
12
|
+
kind: 'export' | 'import';
|
|
13
|
+
target: string;
|
|
14
|
+
status: 'queued' | 'processing' | 'retrying' | 'completed' | 'failed' | 'expired';
|
|
15
|
+
createdAt: string;
|
|
16
|
+
expiresAt: string;
|
|
17
|
+
finishedAt: string | null;
|
|
18
|
+
errorCode: string | null;
|
|
19
|
+
result: {
|
|
20
|
+
rows: number;
|
|
21
|
+
succeeded?: number;
|
|
22
|
+
failed?: number;
|
|
23
|
+
filename?: string;
|
|
24
|
+
atomic?: boolean;
|
|
25
|
+
} | null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 原始文件和文件内容不会出现在列表、审计 diff 或 outbox 中。
|
|
29
|
+
* 队列只携带任务 id;任务查询始终绑定租户和提交人,运维权限也不能下载别人的文件。
|
|
30
|
+
*/
|
|
31
|
+
export declare class DataTransfers {
|
|
32
|
+
private readonly k;
|
|
33
|
+
constructor(k: Kernel);
|
|
34
|
+
export(aggregate: string, params: Record<string, unknown>, ctx: Ctx, key: string): Promise<TransferTask>;
|
|
35
|
+
import(request: unknown, ctx: Ctx, key: string): Promise<TransferTask>;
|
|
36
|
+
private enqueue;
|
|
37
|
+
private own;
|
|
38
|
+
list(ctx: Ctx, aggregate?: string): Promise<TransferTask[]>;
|
|
39
|
+
private row;
|
|
40
|
+
get(id: string, ctx: Ctx): Promise<TransferTask>;
|
|
41
|
+
download(id: string, ctx: Ctx): Promise<{
|
|
42
|
+
body: string;
|
|
43
|
+
filename: string;
|
|
44
|
+
contentType: string;
|
|
45
|
+
}>;
|
|
46
|
+
}
|
|
47
|
+
export declare function transferConsumer(k: Kernel): EventConsumer;
|
|
48
|
+
/** 定期清除原文件、结果文件和授权指纹;保留轻量任务记录防止旧幂等键被重复执行。 */
|
|
49
|
+
export declare function expireTransfers(k: Kernel): Promise<void>;
|
|
@@ -0,0 +1,245 @@
|
|
|
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
|
+
async function liveContext(k, userId, tenantId, db = k.app) {
|
|
24
|
+
const grants = await loadUserGrants(db, userId, tenantId);
|
|
25
|
+
if (!grants?.active)
|
|
26
|
+
throw cageError('FORBIDDEN', '用户已被禁用或删除');
|
|
27
|
+
return userCtx(grants);
|
|
28
|
+
}
|
|
29
|
+
function importCommand(k, capability, ctx) {
|
|
30
|
+
const command = k.registry.command(capability);
|
|
31
|
+
if (!command || !command.cmd.invokedBy.includes('user'))
|
|
32
|
+
throw cageError('NOT_FOUND');
|
|
33
|
+
requireCap(ctx, capability);
|
|
34
|
+
importShape(command.cmd.input);
|
|
35
|
+
return command;
|
|
36
|
+
}
|
|
37
|
+
/** 下载不能沿用排队时保存的授权。范围、明文权限或部门树变化后,旧文件必须重新生成。 */
|
|
38
|
+
async function accessHash(k, ctx, aggregate) {
|
|
39
|
+
const depts = await k.app.selectFrom('sys.dept').select(['id', 'ancestors', 'status'])
|
|
40
|
+
.where('tenant_id', '=', ctx.tenantId).orderBy('id').execute();
|
|
41
|
+
return hash({ user: ctx.userId, dept: ctx.deptId, path: ctx.deptPath, scopes: ctx.scopes[aggregate] ?? ctx.scopes['*'], unmask: [...ctx.unmask].sort(), depts });
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 原始文件和文件内容不会出现在列表、审计 diff 或 outbox 中。
|
|
45
|
+
* 队列只携带任务 id;任务查询始终绑定租户和提交人,运维权限也不能下载别人的文件。
|
|
46
|
+
*/
|
|
47
|
+
export class DataTransfers {
|
|
48
|
+
k;
|
|
49
|
+
constructor(k) {
|
|
50
|
+
this.k = k;
|
|
51
|
+
}
|
|
52
|
+
async export(aggregate, params, ctx, key) {
|
|
53
|
+
const user = requireUser(ctx), current = await liveContext(this.k, user, ctx.tenantId);
|
|
54
|
+
if (!this.k.registry.aggregate(aggregate))
|
|
55
|
+
throw cageError('NOT_FOUND');
|
|
56
|
+
requireCap(current, `${aggregate}.export`);
|
|
57
|
+
return this.enqueue('export', aggregate, params, ctx, key);
|
|
58
|
+
}
|
|
59
|
+
async import(request, ctx, key) {
|
|
60
|
+
const user = requireUser(ctx), current = await liveContext(this.k, user, ctx.tenantId);
|
|
61
|
+
const parsed = importRequestSchema.safeParse(request);
|
|
62
|
+
if (!parsed.success)
|
|
63
|
+
throw cageError('INVALID_INPUT', '需要 command、csv 和可选的 atomic 参数');
|
|
64
|
+
const { command, csv, atomic } = parsed.data;
|
|
65
|
+
const rc = importCommand(this.k, command, current);
|
|
66
|
+
// 文件格式错误立即反馈;单行输入或业务错误留给命令校验,在任务结果中报告。
|
|
67
|
+
csvInputs(csv, rc.cmd.input);
|
|
68
|
+
return this.enqueue('import', command, { csv, atomic, stepUpAt: ctx.stepUpAt ?? null }, ctx, key);
|
|
69
|
+
}
|
|
70
|
+
async enqueue(kind, target, input, ctx, key) {
|
|
71
|
+
if (!key || key.length > 200)
|
|
72
|
+
throw cageError('INVALID_INPUT', '请提供不超过 200 字符的 Idempotency-Key');
|
|
73
|
+
// 二次认证时间属于会话上下文,不属于用户提交的文件;重试时不要因此误报冲突。
|
|
74
|
+
const { stepUpAt: _, ...requestInput } = input;
|
|
75
|
+
const requestHash = hash({ kind, target, input: requestInput });
|
|
76
|
+
const id = await this.k.app.transaction().execute(async (tx) => {
|
|
77
|
+
const lockKey = `${ctx.tenantId}:${ctx.userId}:${key}`;
|
|
78
|
+
await sql `select pg_advisory_xact_lock(hashtextextended(${lockKey}, 0))`.execute(tx);
|
|
79
|
+
const previous = await tx.selectFrom('sys.data_transfer').select(['id', 'request_hash'])
|
|
80
|
+
.where('tenant_id', '=', ctx.tenantId).where('user_id', '=', ctx.userId).where('idempotency_key', '=', key).executeTakeFirst();
|
|
81
|
+
if (previous) {
|
|
82
|
+
if (previous.request_hash !== requestHash)
|
|
83
|
+
throw cageError('CONFLICT', '同一幂等键不能提交不同的任务');
|
|
84
|
+
return previous.id;
|
|
85
|
+
}
|
|
86
|
+
const taskId = randomUUID();
|
|
87
|
+
const outbox = await tx.insertInto('sys.outbox').values({
|
|
88
|
+
tenant_id: ctx.tenantId, event: EVENT, aggregate: 'DataTransfer', aggregate_id: taskId,
|
|
89
|
+
payload: JSON.stringify({ taskId }), actor: JSON.stringify(ctx.actor), trace: JSON.stringify(traceCarrier()), request_id: ctx.requestId ?? null,
|
|
90
|
+
}).returning('id').executeTakeFirstOrThrow();
|
|
91
|
+
await tx.insertInto('sys.data_transfer').values({
|
|
92
|
+
id: taskId, tenant_id: ctx.tenantId, user_id: ctx.userId, kind, target,
|
|
93
|
+
idempotency_key: key, request_hash: requestHash, input: JSON.stringify(input), outbox_id: outbox.id,
|
|
94
|
+
}).execute();
|
|
95
|
+
await this.k.writeAudit(tx, {
|
|
96
|
+
tenantId: ctx.tenantId, actor: ctx.userId, actorKind: 'user', source: kind === 'import' ? 'import' : 'api', kind: 'ops',
|
|
97
|
+
capability: kind === 'export' ? `${target}.export` : target, command: `dataTransfer.${kind}`, result: 'queued',
|
|
98
|
+
diff: { taskId }, requestId: ctx.requestId ?? null,
|
|
99
|
+
});
|
|
100
|
+
await sql `select pg_notify('cage_outbox', '')`.execute(tx);
|
|
101
|
+
return taskId;
|
|
102
|
+
});
|
|
103
|
+
return this.get(id, ctx);
|
|
104
|
+
}
|
|
105
|
+
own(ctx) {
|
|
106
|
+
return this.k.app.selectFrom('sys.data_transfer as t').innerJoin('sys.outbox as o', 'o.id', 't.outbox_id')
|
|
107
|
+
.where('t.tenant_id', '=', ctx.tenantId).where('t.user_id', '=', requireUser(ctx));
|
|
108
|
+
}
|
|
109
|
+
async list(ctx, aggregate) {
|
|
110
|
+
let query = this.own(ctx);
|
|
111
|
+
// 业务列表只需要当前对象的任务;先在数据库筛选再取最近 100 条,
|
|
112
|
+
// 避免其他页面的任务把当前对象的记录挤出结果集。
|
|
113
|
+
if (aggregate) {
|
|
114
|
+
if (!this.k.registry.aggregate(aggregate))
|
|
115
|
+
throw cageError('NOT_FOUND');
|
|
116
|
+
query = query.where((eb) => eb.or([eb('t.target', '=', aggregate), eb('t.target', 'like', `${aggregate}.%`)]));
|
|
117
|
+
}
|
|
118
|
+
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'])
|
|
119
|
+
.orderBy('t.created_at', 'desc').limit(100).execute();
|
|
120
|
+
return rows.map(taskView);
|
|
121
|
+
}
|
|
122
|
+
async row(id, ctx) {
|
|
123
|
+
if (!z.uuid().safeParse(id).success)
|
|
124
|
+
throw cageError('NOT_FOUND');
|
|
125
|
+
const row = await this.own(ctx).selectAll('t').select(['o.status as delivery', 'o.attempts']).where('t.id', '=', id).executeTakeFirst();
|
|
126
|
+
if (!row)
|
|
127
|
+
throw cageError('NOT_FOUND');
|
|
128
|
+
return row;
|
|
129
|
+
}
|
|
130
|
+
async get(id, ctx) { return taskView(await this.row(id, ctx)); }
|
|
131
|
+
async download(id, ctx) {
|
|
132
|
+
const row = await this.row(id, ctx);
|
|
133
|
+
if (taskView(row).status !== 'completed')
|
|
134
|
+
throw cageError('INVALID_TRANSITION', '任务尚未完成或文件已过期');
|
|
135
|
+
const current = await liveContext(this.k, requireUser(ctx), ctx.tenantId);
|
|
136
|
+
requireCap(current, row.kind === 'export' ? `${row.target}.export` : row.target);
|
|
137
|
+
if (row.kind === 'export' && row.access_hash !== await accessHash(this.k, current, row.target)) {
|
|
138
|
+
throw cageError('FORBIDDEN', '数据范围或明文权限已经变化,请重新导出');
|
|
139
|
+
}
|
|
140
|
+
await this.k.writeAudit(this.k.app, {
|
|
141
|
+
tenantId: ctx.tenantId, actor: ctx.userId, actorKind: 'user', source: 'api', kind: 'read',
|
|
142
|
+
capability: row.kind === 'export' ? `${row.target}.export` : row.target, command: 'dataTransfer.download', result: 'ok', diff: { taskId: id },
|
|
143
|
+
});
|
|
144
|
+
return { body: row.file_body, filename: row.result.filename, contentType: row.kind === 'export' ? 'text/csv; charset=utf-8' : 'application/json; charset=utf-8' };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function taskView(row) {
|
|
148
|
+
const expired = new Date(row.expires_at).getTime() <= Date.now();
|
|
149
|
+
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';
|
|
150
|
+
return {
|
|
151
|
+
id: row.id, kind: row.kind, target: row.target, status, result: expired ? null : row.result,
|
|
152
|
+
createdAt: new Date(row.created_at).toISOString(), expiresAt: new Date(row.expires_at).toISOString(),
|
|
153
|
+
finishedAt: row.finished_at ? new Date(row.finished_at).toISOString() : null, errorCode: row.error_code ?? (status === 'failed' ? 'INTERNAL' : null),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* 保存点让错误行只回滚自己的写入(含审计和同事务反应)。
|
|
158
|
+
* 整批模式再包一层保存点;出错时撤销全部业务写入,但保留每行的失败报告。
|
|
159
|
+
* 不保存命令返回快照或输入值,避免权限日后收回后从结果文件读到敏感内容。
|
|
160
|
+
*/
|
|
161
|
+
async function runImport(k, tx, task, ctx) {
|
|
162
|
+
const rc = importCommand(k, task.target, ctx);
|
|
163
|
+
const inputs = csvInputs(task.input.csv, rc.cmd.input);
|
|
164
|
+
const results = [];
|
|
165
|
+
await sql `savepoint cage_import_batch`.execute(tx);
|
|
166
|
+
for (const [index, input] of inputs.entries()) {
|
|
167
|
+
await sql `savepoint cage_import_row`.execute(tx);
|
|
168
|
+
try {
|
|
169
|
+
await k.executeIn(tx, rc.cmd, input, ctx);
|
|
170
|
+
results.push({ row: index + 2, ok: true });
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
await sql `rollback to savepoint cage_import_row`.execute(tx);
|
|
174
|
+
const error = toCageError(err);
|
|
175
|
+
// 程序错误、连接故障和死锁属于任务失败,交给 outbox 重试,不能伪装成坏数据行。
|
|
176
|
+
if (error.code === 'INTERNAL' || ['40001', '40P01'].includes(err?.code))
|
|
177
|
+
throw err;
|
|
178
|
+
// 只报告出错的输入字段名,不保存原始值、数据库错误文本或完整领域错误 detail。
|
|
179
|
+
const fields = error.code === 'INVALID_INPUT' && Array.isArray(error.detail)
|
|
180
|
+
? error.detail.filter((issue) => typeof issue.path === 'string').map((issue) => issue.path) : [];
|
|
181
|
+
results.push({ row: index + 2, ok: false, code: error.code,
|
|
182
|
+
...(error.domainCode ? { domainCode: error.domainCode } : {}), ...(fields.length ? { fields } : {}) });
|
|
183
|
+
}
|
|
184
|
+
await sql `release savepoint cage_import_row`.execute(tx);
|
|
185
|
+
}
|
|
186
|
+
if (task.input.atomic && results.some((r) => !r.ok)) {
|
|
187
|
+
await sql `rollback to savepoint cage_import_batch`.execute(tx);
|
|
188
|
+
for (const row of results)
|
|
189
|
+
if (row.ok) {
|
|
190
|
+
row.ok = false;
|
|
191
|
+
row.code = 'ABORTED';
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
await sql `release savepoint cage_import_batch`.execute(tx);
|
|
195
|
+
const succeeded = results.filter((r) => r.ok).length;
|
|
196
|
+
const result = { rows: results.length, succeeded, failed: results.length - succeeded, atomic: task.input.atomic, filename: `import-${task.id}.json` };
|
|
197
|
+
return { result, body: JSON.stringify({ ...result, results }, null, 2), access: null };
|
|
198
|
+
}
|
|
199
|
+
export function transferConsumer(k) {
|
|
200
|
+
return {
|
|
201
|
+
name: 'cage:data-transfer', event: EVENT,
|
|
202
|
+
async handle(event, tx) {
|
|
203
|
+
const task = await tx.selectFrom('sys.data_transfer').selectAll().where('id', '=', event.aggregate_id)
|
|
204
|
+
.where('tenant_id', '=', event.tenant_id).forUpdate().executeTakeFirstOrThrow();
|
|
205
|
+
if (task.status !== 'queued')
|
|
206
|
+
return;
|
|
207
|
+
if (new Date(task.expires_at).getTime() <= Date.now()) {
|
|
208
|
+
await tx.updateTable('sys.data_transfer').set({ status: 'expired', input: null }).where('id', '=', task.id).execute();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
// 整个任务的业务失败也使用保存点,使 PostgreSQL 错误后仍能可靠记录失败状态。
|
|
212
|
+
await sql `savepoint cage_transfer`.execute(tx);
|
|
213
|
+
try {
|
|
214
|
+
const ctx = await liveContext(k, task.user_id, task.tenant_id, tx);
|
|
215
|
+
ctx.requestId = event.request_id ?? undefined;
|
|
216
|
+
ctx.source = task.kind === 'import' ? 'import' : 'api';
|
|
217
|
+
ctx.stepUpAt = task.input.stepUpAt;
|
|
218
|
+
let output;
|
|
219
|
+
if (task.kind === 'export') {
|
|
220
|
+
const access = await accessHash(k, ctx, task.target);
|
|
221
|
+
const file = await k.read.export(task.target, task.input, ctx);
|
|
222
|
+
output = { result: { rows: file.rows, filename: file.filename }, body: file.body, access };
|
|
223
|
+
}
|
|
224
|
+
else
|
|
225
|
+
output = await runImport(k, tx, task, ctx);
|
|
226
|
+
await tx.updateTable('sys.data_transfer').set({ status: 'completed', result: JSON.stringify(output.result), file_body: output.body,
|
|
227
|
+
access_hash: output.access, input: null, finished_at: sql `now()` }).where('id', '=', task.id).execute();
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
await sql `rollback to savepoint cage_transfer`.execute(tx);
|
|
231
|
+
const error = toCageError(err);
|
|
232
|
+
if (error.code === 'INTERNAL' || error.code === 'CONFLICT')
|
|
233
|
+
throw err;
|
|
234
|
+
await tx.updateTable('sys.data_transfer').set({ status: 'failed', error_code: error.code, input: null, finished_at: sql `now()` }).where('id', '=', task.id).execute();
|
|
235
|
+
}
|
|
236
|
+
await sql `release savepoint cage_transfer`.execute(tx);
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
/** 定期清除原文件、结果文件和授权指纹;保留轻量任务记录防止旧幂等键被重复执行。 */
|
|
241
|
+
export async function expireTransfers(k) {
|
|
242
|
+
await k.app.updateTable('sys.data_transfer').set({ status: 'expired', input: null, file_body: null, result: null, access_hash: null })
|
|
243
|
+
.where('expires_at', '<=', new Date()).where('status', '<>', 'expired').execute();
|
|
244
|
+
}
|
|
245
|
+
//# 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,31 @@
|
|
|
1
|
+
// 独立的前向系统迁移;不改动已发布的 cage:0001_system 校验和。
|
|
2
|
+
// 文件与任务存在同一 PostgreSQL 中,API/worker 不需要共享本地磁盘。
|
|
3
|
+
// 原始文件和结果仅保留 24 小时;索引记录保留用于幂等及操作追踪。
|
|
4
|
+
export const TRANSFER_MIGRATION = {
|
|
5
|
+
name: 'cage:0002_data_transfer',
|
|
6
|
+
sql: `
|
|
7
|
+
CREATE TABLE sys.data_transfer (
|
|
8
|
+
id uuid PRIMARY KEY,
|
|
9
|
+
tenant_id uuid NOT NULL,
|
|
10
|
+
user_id uuid NOT NULL,
|
|
11
|
+
kind text NOT NULL CHECK (kind IN ('export', 'import')),
|
|
12
|
+
target text NOT NULL,
|
|
13
|
+
idempotency_key text NOT NULL,
|
|
14
|
+
request_hash text NOT NULL,
|
|
15
|
+
input jsonb,
|
|
16
|
+
status text NOT NULL DEFAULT 'queued',
|
|
17
|
+
outbox_id bigint NOT NULL REFERENCES sys.outbox(id),
|
|
18
|
+
result jsonb,
|
|
19
|
+
file_body text,
|
|
20
|
+
access_hash text,
|
|
21
|
+
error_code text,
|
|
22
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
23
|
+
finished_at timestamptz,
|
|
24
|
+
expires_at timestamptz NOT NULL DEFAULT now() + interval '24 hours',
|
|
25
|
+
UNIQUE (tenant_id, user_id, idempotency_key)
|
|
26
|
+
);
|
|
27
|
+
CREATE INDEX data_transfer_owner_idx ON sys.data_transfer (tenant_id, user_id, created_at DESC);
|
|
28
|
+
CREATE INDEX data_transfer_expiry_idx ON sys.data_transfer (expires_at) WHERE status <> 'expired';
|
|
29
|
+
`,
|
|
30
|
+
};
|
|
31
|
+
//# 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,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,IAAI,EAAE,yBAAyB;IAC/B,GAAG,EAAE;;;;;;;;;;;;;;;;;;;;;;;CAuBN;CACA,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** 主体种类。系统身份(process/job/external/reaction)只能调用声明了对应 invokedBy 的命令,或自己声明过的命令。 */
|
|
2
|
+
export type ActorKind = 'user' | 'process' | 'job' | 'external' | 'reaction' | 'system' | 'anonymous';
|
|
3
|
+
export interface Actor {
|
|
4
|
+
kind: ActorKind;
|
|
5
|
+
/** 用户 id,或系统身份名,例如 job:expireStaleOrders */
|
|
6
|
+
id: string;
|
|
7
|
+
}
|
|
8
|
+
/** 写入来源,记入审计 */
|
|
9
|
+
export type Source = 'api' | 'process' | 'job' | 'import' | 'external' | 'reaction' | 'system';
|
|
10
|
+
/** 范围组合子:封闭集合,每个都同时有内存语义和 SQL 语义 */
|
|
11
|
+
export type Combinator = 'all' | 'self' | 'dept' | 'deptSubtree' | 'deptSet';
|
|
12
|
+
export declare const COMBINATORS: readonly Combinator[];
|
|
13
|
+
/** 组合子由窄到宽的顺序,用于「不能授出比自己更宽的范围」 */
|
|
14
|
+
export declare const COMBINATOR_WIDTH: Record<Combinator, number>;
|
|
15
|
+
/** 角色在某个聚合上的数据范围授权 */
|
|
16
|
+
export interface ScopeGrant {
|
|
17
|
+
combinator: Combinator;
|
|
18
|
+
/** deptSet 用:角色配置的部门集合 */
|
|
19
|
+
deptIds?: string[];
|
|
20
|
+
}
|
|
21
|
+
/** 能力点参数,例如 { ceiling: 50000 } */
|
|
22
|
+
export type CapabilityParams = Record<string, unknown>;
|
|
23
|
+
/** 当前主体的上下文。HTTP 认证、审批桥接、调度器、反应各自构造它,交给内核。 */
|
|
24
|
+
export interface Ctx {
|
|
25
|
+
actor: Actor | null;
|
|
26
|
+
/** 当前真人用户;系统身份时为 null(真人在 onBehalfOf) */
|
|
27
|
+
userId: string | null;
|
|
28
|
+
/** 系统身份代表的真人,用于职责分离和审计 */
|
|
29
|
+
onBehalfOf?: string | null;
|
|
30
|
+
tenantId: string;
|
|
31
|
+
deptId: string | null;
|
|
32
|
+
/** 当前部门的祖先路径(含自己),例如 /root/purchase/ */
|
|
33
|
+
deptPath: string | null;
|
|
34
|
+
/** 能力集合及参数;多角色取并集,额度取最大值 */
|
|
35
|
+
capabilities: Map<string, CapabilityParams>;
|
|
36
|
+
/** 各聚合上的范围授权,多角色取并集(OR);未配置的聚合按「仅本人」 */
|
|
37
|
+
scopes: Record<string, ScopeGrant[]>;
|
|
38
|
+
/** 可以看明文的「聚合.种类」,例如 PurchaseOrder.phone */
|
|
39
|
+
unmask: string[];
|
|
40
|
+
source: Source;
|
|
41
|
+
requestId?: string;
|
|
42
|
+
idempotencyKey?: string;
|
|
43
|
+
/** 二次认证时间(毫秒时间戳) */
|
|
44
|
+
stepUpAt?: number | null;
|
|
45
|
+
client?: {
|
|
46
|
+
ip?: string;
|
|
47
|
+
userAgent?: string;
|
|
48
|
+
};
|
|
49
|
+
/** onBehalfOf 那个人的能力(额度类约束按他判断);内核会按需加载 */
|
|
50
|
+
onBehalfOfCapabilities?: Map<string, CapabilityParams>;
|
|
51
|
+
}
|
|
52
|
+
export type MaybePromise<T> = T | Promise<T>;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,0BAA0B;AAgB1B,MAAM,CAAC,MAAM,WAAW,GAA0B,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC;AACpG,kCAAkC;AAClC,MAAM,CAAC,MAAM,gBAAgB,GAA+B,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function snake(s: string): string;
|
|
2
|
+
export declare function kebab(s: string): string;
|
|
3
|
+
export declare function camel(s: string): string;
|
|
4
|
+
export declare function lowerFirst(s: string): string;
|
|
5
|
+
export declare function upperFirst(s: string): string;
|
|
6
|
+
/** 32 位哈希(FNV-1a),用于咨询锁编号 */
|
|
7
|
+
export declare function hash32(s: string): number;
|
|
8
|
+
/** 稳定的 JSON 序列化(键排序),用于本体图比较 */
|
|
9
|
+
export declare function stableStringify(v: unknown): string;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// 命名转换:聚合名大驼峰 → 表名 snake_case、路由 kebab-case;字段名 camelCase ↔ 列名 snake_case。
|
|
2
|
+
export function snake(s) {
|
|
3
|
+
return s
|
|
4
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
5
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
|
|
6
|
+
.toLowerCase();
|
|
7
|
+
}
|
|
8
|
+
export function kebab(s) {
|
|
9
|
+
return snake(s).replace(/_/g, '-');
|
|
10
|
+
}
|
|
11
|
+
export function camel(s) {
|
|
12
|
+
return s.replace(/_([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
13
|
+
}
|
|
14
|
+
export function lowerFirst(s) {
|
|
15
|
+
return s.charAt(0).toLowerCase() + s.slice(1);
|
|
16
|
+
}
|
|
17
|
+
export function upperFirst(s) {
|
|
18
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
19
|
+
}
|
|
20
|
+
/** 32 位哈希(FNV-1a),用于咨询锁编号 */
|
|
21
|
+
export function hash32(s) {
|
|
22
|
+
let h = 0x811c9dc5;
|
|
23
|
+
for (let i = 0; i < s.length; i++) {
|
|
24
|
+
h ^= s.charCodeAt(i);
|
|
25
|
+
h = Math.imul(h, 0x01000193);
|
|
26
|
+
}
|
|
27
|
+
return h | 0;
|
|
28
|
+
}
|
|
29
|
+
/** 稳定的 JSON 序列化(键排序),用于本体图比较 */
|
|
30
|
+
export function stableStringify(v) {
|
|
31
|
+
if (v === null || typeof v !== 'object')
|
|
32
|
+
return JSON.stringify(v);
|
|
33
|
+
if (Array.isArray(v))
|
|
34
|
+
return `[${v.map(stableStringify).join(',')}]`;
|
|
35
|
+
const keys = Object.keys(v).filter((k) => v[k] !== undefined).sort();
|
|
36
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(v[k])}`).join(',')}}`;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=names.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"names.js","sourceRoot":"","sources":["../../src/util/names.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAE3E,MAAM,UAAU,KAAK,CAAC,CAAS;IAC7B,OAAO,CAAC;SACL,OAAO,CAAC,oBAAoB,EAAE,OAAO,CAAC;SACtC,OAAO,CAAC,uBAAuB,EAAE,OAAO,CAAC;SACzC,WAAW,EAAE,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,CAAS;IAC7B,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,CAAS;IAC7B,OAAO,CAAC,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC,EAAE,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,6BAA6B;AAC7B,MAAM,UAAU,MAAM,CAAC,CAAS;IAC9B,IAAI,CAAC,GAAG,UAAU,CAAC;IACnB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QACrB,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,CAAC;AACf,CAAC;AAED,gCAAgC;AAChC,MAAM,UAAU,eAAe,CAAC,CAAU;IACxC,IAAI,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAClE,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IACrE,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAW,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAE,CAAS,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC;IACxF,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,eAAe,CAAE,CAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;AACpG,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Transaction } from 'kysely';
|
|
2
|
+
import type { Kernel } from '../kernel.js';
|
|
3
|
+
import type { EventConsumer } from './outbox.js';
|
|
4
|
+
/** 审批引擎的最小接口(完整接口见 @yss123/cage-approval)。引擎只给结论,结论由桥接代码转成命令。 */
|
|
5
|
+
export interface ApprovalEngine {
|
|
6
|
+
start(input: {
|
|
7
|
+
process: string;
|
|
8
|
+
businessKey: string;
|
|
9
|
+
tenantId: string;
|
|
10
|
+
/** 只包含声明过的 routable 字段 */
|
|
11
|
+
vars: Record<string, unknown>;
|
|
12
|
+
tx: Transaction<any>;
|
|
13
|
+
}): Promise<{
|
|
14
|
+
instanceId: string;
|
|
15
|
+
}>;
|
|
16
|
+
cancel(input: {
|
|
17
|
+
process: string;
|
|
18
|
+
businessKey: string;
|
|
19
|
+
tenantId: string;
|
|
20
|
+
tx: Transaction<any>;
|
|
21
|
+
}): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
export declare function reactionConsumers(k: Kernel): EventConsumer[];
|
|
24
|
+
/** 审批启动:只把 routable 字段作为流程变量传给引擎 */
|
|
25
|
+
export declare function approvalConsumers(k: Kernel, engines: Record<string, ApprovalEngine>): EventConsumer[];
|
|
26
|
+
/** 声明对象通过 CONSUMERS 扩展点提供的消费者(例如 @yss123/cage-connectors 的出站映射) */
|
|
27
|
+
export declare function extensionConsumers(k: Kernel): EventConsumer[];
|
|
28
|
+
/** 从 ROUTE 扩展(代码内封闭路由)里找审批引擎 */
|
|
29
|
+
export declare function routeEngines(k: Kernel): Record<string, ApprovalEngine>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { CONSUMERS } from '../brands.js';
|
|
2
|
+
import { systemCtx } from '../org.js';
|
|
3
|
+
import { loadScoped } from '../pipeline/load.js';
|
|
4
|
+
function actorUser(row) {
|
|
5
|
+
return row.actor?.userId ?? row.actor?.onBehalfOf ?? null;
|
|
6
|
+
}
|
|
7
|
+
export function reactionConsumers(k) {
|
|
8
|
+
return k.registry.reactions
|
|
9
|
+
.filter(({ r }) => r.tx === 'outbox')
|
|
10
|
+
.map(({ r, name }) => ({
|
|
11
|
+
name: `reaction:${name}`,
|
|
12
|
+
event: r.event.name,
|
|
13
|
+
async handle(row, tx) {
|
|
14
|
+
const input = r.map(row.payload);
|
|
15
|
+
if (input === null || input === undefined)
|
|
16
|
+
return;
|
|
17
|
+
const ctx = systemCtx({
|
|
18
|
+
kind: 'reaction',
|
|
19
|
+
name,
|
|
20
|
+
tenantId: row.tenant_id,
|
|
21
|
+
capabilities: [k.registry.capabilityOf(r.command)],
|
|
22
|
+
onBehalfOf: actorUser(row),
|
|
23
|
+
requestId: row.request_id ?? undefined,
|
|
24
|
+
});
|
|
25
|
+
await k.executeIn(tx, r.command, input, ctx, 0);
|
|
26
|
+
},
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
/** 审批启动:只把 routable 字段作为流程变量传给引擎 */
|
|
30
|
+
export function approvalConsumers(k, engines) {
|
|
31
|
+
const out = [];
|
|
32
|
+
for (const { a, name } of k.registry.approvals) {
|
|
33
|
+
const engine = engines[a.process];
|
|
34
|
+
if (!engine) {
|
|
35
|
+
k.log.warn({ process: a.process }, `审批绑定 ${name} 没有可用的审批引擎,流程不会启动`);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
out.push({
|
|
39
|
+
name: `approval:${name}:start`,
|
|
40
|
+
event: a.start.name,
|
|
41
|
+
async handle(row, tx) {
|
|
42
|
+
const vars = await routableVars(k, a, row, tx);
|
|
43
|
+
await engine.start({ process: a.process, businessKey: row.aggregate_id, tenantId: row.tenant_id, vars, tx });
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
for (const ev of a.cancelOn) {
|
|
47
|
+
out.push({
|
|
48
|
+
name: `approval:${name}:cancel:${ev.name}`,
|
|
49
|
+
event: ev.name,
|
|
50
|
+
async handle(row, tx) {
|
|
51
|
+
await engine.cancel({ process: a.process, businessKey: row.aggregate_id, tenantId: row.tenant_id, tx });
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
async function routableVars(k, a, row, tx) {
|
|
59
|
+
const ctx = systemCtx({ kind: 'process', name: a.process, tenantId: row.tenant_id, capabilities: [] });
|
|
60
|
+
const snap = await loadScoped(tx, a.agg, row.aggregate_id, ctx, false);
|
|
61
|
+
const vars = {};
|
|
62
|
+
for (const f of a.routable)
|
|
63
|
+
vars[f] = snap?.[f] ?? row.payload?.[f] ?? null;
|
|
64
|
+
return vars;
|
|
65
|
+
}
|
|
66
|
+
/** 声明对象通过 CONSUMERS 扩展点提供的消费者(例如 @yss123/cage-connectors 的出站映射) */
|
|
67
|
+
export function extensionConsumers(k) {
|
|
68
|
+
return k.registry.consumerProviders().flatMap((p) => p[CONSUMERS](k) ?? []);
|
|
69
|
+
}
|
|
70
|
+
/** 从 ROUTE 扩展(代码内封闭路由)里找审批引擎 */
|
|
71
|
+
export function routeEngines(k) {
|
|
72
|
+
const out = {};
|
|
73
|
+
for (const e of k.registry.extensions) {
|
|
74
|
+
if (e.kind === 'ROUTE' && typeof e.value?.engine === 'function')
|
|
75
|
+
out[e.value.process] = e.value.engine(k);
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=consumers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consumers.js","sourceRoot":"","sources":["../../src/worker/consumers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type DatabaseUrls } from '../db/connect.js';
|
|
2
|
+
import { type Kernel } from '../kernel.js';
|
|
3
|
+
import { type Logger } from '../telemetry.js';
|
|
4
|
+
import { type ApprovalEngine } from './consumers.js';
|
|
5
|
+
import { type EventConsumer } from './outbox.js';
|
|
6
|
+
export * from './outbox.js';
|
|
7
|
+
export * from './consumers.js';
|
|
8
|
+
export * from './scheduler.js';
|
|
9
|
+
export interface WorkerOptions {
|
|
10
|
+
domain?: object | object[];
|
|
11
|
+
kernel?: Kernel;
|
|
12
|
+
urls?: DatabaseUrls;
|
|
13
|
+
/** 同时处理的事件数 */
|
|
14
|
+
concurrency?: number;
|
|
15
|
+
/** 没收到 NOTIFY 时的轮询间隔 */
|
|
16
|
+
pollIntervalMs?: number;
|
|
17
|
+
maxAttempts?: number;
|
|
18
|
+
/** 外部审批引擎(按流程名);不传时使用代码内封闭路由 */
|
|
19
|
+
approvalEngines?: Record<string, ApprovalEngine>;
|
|
20
|
+
/** 额外的事件消费者 */
|
|
21
|
+
consumers?: EventConsumer[];
|
|
22
|
+
/** 是否运行定时任务,默认 true */
|
|
23
|
+
jobs?: boolean;
|
|
24
|
+
logger?: Logger;
|
|
25
|
+
}
|
|
26
|
+
export interface WorkerHandle {
|
|
27
|
+
kernel: Kernel;
|
|
28
|
+
consumers: EventConsumer[];
|
|
29
|
+
/** 处理到 outbox 为空为止(测试和 cage dev 用) */
|
|
30
|
+
drain(maxRounds?: number): Promise<number>;
|
|
31
|
+
stop(): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
export declare function buildConsumers(k: Kernel, opts?: Pick<WorkerOptions, 'approvalEngines' | 'consumers'>): EventConsumer[];
|
|
34
|
+
/** 不启动循环,只创建可手动驱动的 worker(测试用) */
|
|
35
|
+
export declare function createWorker(k: Kernel, opts?: WorkerOptions): Omit<WorkerHandle, 'stop'>;
|
|
36
|
+
export declare function startWorker(opts?: WorkerOptions): Promise<WorkerHandle>;
|