@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.
Files changed (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +20 -2
  3. package/dist/brands.d.ts +32 -0
  4. package/dist/brands.js +35 -0
  5. package/dist/brands.js.map +1 -0
  6. package/dist/db/connect.d.ts +28 -0
  7. package/dist/db/connect.js +57 -0
  8. package/dist/db/connect.js.map +1 -0
  9. package/dist/db/ddl.d.ts +69 -0
  10. package/dist/db/ddl.js +522 -0
  11. package/dist/db/ddl.js.map +1 -0
  12. package/dist/db/index.d.ts +5 -0
  13. package/dist/db/index.js +7 -0
  14. package/dist/db/index.js.map +1 -0
  15. package/dist/db/migrate.d.ts +22 -0
  16. package/dist/db/migrate.js +87 -0
  17. package/dist/db/migrate.js.map +1 -0
  18. package/dist/db/rows.d.ts +10 -0
  19. package/dist/db/rows.js +59 -0
  20. package/dist/db/rows.js.map +1 -0
  21. package/dist/db/session.d.ts +4 -0
  22. package/dist/db/session.js +20 -0
  23. package/dist/db/session.js.map +1 -0
  24. package/dist/errors.d.ts +44 -0
  25. package/dist/errors.js +83 -0
  26. package/dist/errors.js.map +1 -0
  27. package/dist/index.d.ts +23 -0
  28. package/dist/index.js +25 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/kernel.d.ts +99 -0
  31. package/dist/kernel.js +456 -0
  32. package/dist/kernel.js.map +1 -0
  33. package/dist/mask.d.ts +13 -0
  34. package/dist/mask.js +67 -0
  35. package/dist/mask.js.map +1 -0
  36. package/dist/org.d.ts +46 -0
  37. package/dist/org.js +127 -0
  38. package/dist/org.js.map +1 -0
  39. package/dist/pipeline/idempotency.d.ts +10 -0
  40. package/dist/pipeline/idempotency.js +29 -0
  41. package/dist/pipeline/idempotency.js.map +1 -0
  42. package/dist/pipeline/load.d.ts +15 -0
  43. package/dist/pipeline/load.js +87 -0
  44. package/dist/pipeline/load.js.map +1 -0
  45. package/dist/primitives/aggregate.d.ts +75 -0
  46. package/dist/primitives/aggregate.js +69 -0
  47. package/dist/primitives/aggregate.js.map +1 -0
  48. package/dist/primitives/command.d.ts +101 -0
  49. package/dist/primitives/command.js +49 -0
  50. package/dist/primitives/command.js.map +1 -0
  51. package/dist/primitives/constraints.d.ts +36 -0
  52. package/dist/primitives/constraints.js +41 -0
  53. package/dist/primitives/constraints.js.map +1 -0
  54. package/dist/primitives/event.d.ts +31 -0
  55. package/dist/primitives/event.js +44 -0
  56. package/dist/primitives/event.js.map +1 -0
  57. package/dist/primitives/fields.d.ts +127 -0
  58. package/dist/primitives/fields.js +151 -0
  59. package/dist/primitives/fields.js.map +1 -0
  60. package/dist/primitives/flow.d.ts +126 -0
  61. package/dist/primitives/flow.js +90 -0
  62. package/dist/primitives/flow.js.map +1 -0
  63. package/dist/primitives/states.d.ts +19 -0
  64. package/dist/primitives/states.js +37 -0
  65. package/dist/primitives/states.js.map +1 -0
  66. package/dist/read.d.ts +53 -0
  67. package/dist/read.js +217 -0
  68. package/dist/read.js.map +1 -0
  69. package/dist/registry.d.ts +71 -0
  70. package/dist/registry.js +177 -0
  71. package/dist/registry.js.map +1 -0
  72. package/dist/relations.d.ts +14 -0
  73. package/dist/relations.js +58 -0
  74. package/dist/relations.js.map +1 -0
  75. package/dist/scope/index.d.ts +42 -0
  76. package/dist/scope/index.js +90 -0
  77. package/dist/scope/index.js.map +1 -0
  78. package/dist/telemetry.d.ts +11 -0
  79. package/dist/telemetry.js +35 -0
  80. package/dist/telemetry.js.map +1 -0
  81. package/dist/transfer/csv.d.ts +8 -0
  82. package/dist/transfer/csv.js +114 -0
  83. package/dist/transfer/csv.js.map +1 -0
  84. package/dist/transfer/index.d.ts +49 -0
  85. package/dist/transfer/index.js +245 -0
  86. package/dist/transfer/index.js.map +1 -0
  87. package/dist/transfer/schema.d.ts +4 -0
  88. package/dist/transfer/schema.js +31 -0
  89. package/dist/transfer/schema.js.map +1 -0
  90. package/dist/types.d.ts +52 -0
  91. package/dist/types.js +5 -0
  92. package/dist/types.js.map +1 -0
  93. package/dist/util/names.d.ts +9 -0
  94. package/dist/util/names.js +38 -0
  95. package/dist/util/names.js.map +1 -0
  96. package/dist/worker/consumers.d.ts +29 -0
  97. package/dist/worker/consumers.js +79 -0
  98. package/dist/worker/consumers.js.map +1 -0
  99. package/dist/worker/index.d.ts +36 -0
  100. package/dist/worker/index.js +122 -0
  101. package/dist/worker/index.js.map +1 -0
  102. package/dist/worker/outbox.d.ts +38 -0
  103. package/dist/worker/outbox.js +121 -0
  104. package/dist/worker/outbox.js.map +1 -0
  105. package/dist/worker/scheduler.d.ts +7 -0
  106. package/dist/worker/scheduler.js +77 -0
  107. package/dist/worker/scheduler.js.map +1 -0
  108. package/package.json +58 -3
@@ -0,0 +1,177 @@
1
+ // 注册表:遍历 domain 包的导出,靠品牌标记识别原语,用导出名给命令和反应命名。
2
+ import { CONSUMERS, CONNECTOR, INBOUND, OUTBOUND, ROUTE, SYSTEM_DDL, brandOf } from './brands.js';
3
+ import { declError } from './errors.js';
4
+ import { sensitiveFields } from './primitives/aggregate.js';
5
+ import { relationTable } from './relations.js';
6
+ import { isRelation } from './primitives/fields.js';
7
+ /** 遍历一个或多个 domain 模块的导出,建立注册表。 */
8
+ export function buildRegistry(domains, opts = {}) {
9
+ const mods = Array.isArray(domains) ? domains : [domains];
10
+ const names = new Map();
11
+ const aggregates = [];
12
+ const commands = [];
13
+ const events = [];
14
+ const reactions = [];
15
+ const queries = [];
16
+ const approvals = [];
17
+ const jobs = [];
18
+ const extensions = [];
19
+ const systemDdl = [];
20
+ for (const mod of mods) {
21
+ for (const [exportName, v] of Object.entries(mod)) {
22
+ if (!v || (typeof v !== 'object' && typeof v !== 'function'))
23
+ continue;
24
+ if (names.has(v))
25
+ continue; // 同一个对象被重复导出:保留第一个名字
26
+ const brand = brandOf(v);
27
+ if (!brand)
28
+ continue;
29
+ names.set(v, exportName);
30
+ switch (brand) {
31
+ case 'AGGREGATE':
32
+ if (aggregates.some((a) => a.name === v.name)) {
33
+ throw declError('CAGE-REG-002', `聚合名重复:${v.name}`);
34
+ }
35
+ aggregates.push(v);
36
+ break;
37
+ case 'COMMAND': {
38
+ const cmd = v;
39
+ const name = cmd.name ?? exportName;
40
+ commands.push({ cmd, name, capability: `${cmd.agg.name}.${name}`, exportName });
41
+ break;
42
+ }
43
+ case 'EVENT':
44
+ events.push(v);
45
+ break;
46
+ case 'REACTION':
47
+ reactions.push({ r: v, name: v.name ?? exportName });
48
+ break;
49
+ case 'QUERY':
50
+ queries.push(v);
51
+ break;
52
+ case 'APPROVAL':
53
+ approvals.push({ a: v, name: exportName });
54
+ break;
55
+ case 'JOB':
56
+ jobs.push(v);
57
+ break;
58
+ case 'CONNECTOR':
59
+ case 'OUTBOUND':
60
+ case 'INBOUND':
61
+ case 'ROUTE':
62
+ extensions.push({ kind: brand, name: exportName, value: v });
63
+ break;
64
+ default:
65
+ break;
66
+ }
67
+ const ddl = v[SYSTEM_DDL];
68
+ if (typeof ddl === 'function') {
69
+ const text = String(ddl());
70
+ if (!systemDdl.includes(text))
71
+ systemDdl.push(text);
72
+ }
73
+ }
74
+ }
75
+ // ---- 一致性校验:引用的东西都必须被导出,否则抽取器和内核看不到 ----
76
+ const aggSet = new Set(aggregates);
77
+ // 注册后解析字符串引用,支持循环引用,并在连接数据库前发现声明错误。
78
+ const tables = new Set(aggregates.map((a) => `${a.table.schema}.${a.table.name}`));
79
+ for (const a of aggregates)
80
+ for (const [key, field] of Object.entries(a.fields)) {
81
+ if (!field.ref)
82
+ continue;
83
+ const target = aggregates.find((item) => item.name === field.ref);
84
+ if (!target)
85
+ throw declError('CAGE-REL-001', `${a.name}.${key} 引用的 ${field.ref} 未从 domain 导出`);
86
+ if (field.kind === 'hasMany') {
87
+ const inverse = target.fields[field.mappedBy];
88
+ if (!inverse || inverse.kind !== 'ref' || inverse.ref !== a.name || inverse.isHidden || inverse.sensitiveKind)
89
+ throw declError('CAGE-REL-001', `${a.name}.${key} 的 mappedBy 必须指向 ${target.name} 上引用 ${a.name} 的 ref 字段`);
90
+ }
91
+ if (field.kind === 'manyToMany') {
92
+ const table = relationTable(a, key);
93
+ const name = `${table.schema}.${table.name}`;
94
+ if (tables.has(name) || table.name.length > 63)
95
+ throw declError('CAGE-REL-001', `${a.name}.${key} 的关系表名冲突或超过 PostgreSQL 63 字符限制,请缩短或调整名称`);
96
+ tables.add(name);
97
+ }
98
+ }
99
+ const caps = new Set();
100
+ for (const c of commands) {
101
+ if (!aggSet.has(c.cmd.agg))
102
+ throw declError('CAGE-REG-001', `命令 ${c.capability} 所属的聚合 ${c.cmd.agg.name} 没有从 domain 导出`);
103
+ if (c.name === 'related' && c.cmd.invokedBy.includes('user') && Object.values(c.cmd.agg.fields).some(isRelation))
104
+ throw declError('CAGE-REL-001', `${c.cmd.agg.name}.related 是生成客户端的关联读取入口;请为业务命令使用其他 name`);
105
+ if (caps.has(c.capability))
106
+ throw declError('CAGE-REG-003', `能力点重复:${c.capability},可用 command({ name }) 区分`);
107
+ caps.add(c.capability);
108
+ for (const [alias, a] of Object.entries(c.cmd.reads)) {
109
+ if (!aggSet.has(a))
110
+ throw declError('CAGE-REG-001', `命令 ${c.capability} 的 reads.${alias} 引用的聚合没有导出`);
111
+ }
112
+ }
113
+ const cmdSet = new Set(commands.map((c) => c.cmd));
114
+ for (const { r, name } of reactions) {
115
+ if (!cmdSet.has(r.command))
116
+ throw declError('CAGE-REG-001', `反应 ${name} 调用的命令没有从 domain 导出`);
117
+ }
118
+ for (const { a, name } of approvals) {
119
+ if (!cmdSet.has(a.onApproved) || !cmdSet.has(a.onRejected)) {
120
+ throw declError('CAGE-REG-001', `审批绑定 ${name} 的结论命令没有从 domain 导出`);
121
+ }
122
+ }
123
+ for (const j of jobs) {
124
+ for (const c of j.calls)
125
+ if (!cmdSet.has(c))
126
+ throw declError('CAGE-REG-001', `任务 ${j.name} 调用的命令没有导出`);
127
+ }
128
+ const queryNames = new Set();
129
+ for (const q of queries) {
130
+ if (queryNames.has(q.name))
131
+ throw declError('CAGE-REG-002', `查询名重复:${q.name}`);
132
+ queryNames.add(q.name);
133
+ }
134
+ const byCap = new Map(commands.map((c) => [c.capability, c]));
135
+ const byCmd = new Map(commands.map((c) => [c.cmd, c]));
136
+ const byAgg = new Map(aggregates.map((a) => [a.name, a]));
137
+ const registry = {
138
+ aggregates, commands, events, reactions, queries, approvals, jobs, extensions, systemDdl,
139
+ aggregate: (name) => byAgg.get(name),
140
+ command: (ref) => (typeof ref === 'string' ? byCap.get(ref) : byCmd.get(ref)),
141
+ capabilityOf: (cmd) => {
142
+ const c = byCmd.get(cmd);
143
+ if (!c)
144
+ throw declError('CAGE-REG-001', `命令(${cmd.agg.name})没有从 domain 导出,无法确定能力点`);
145
+ return c.capability;
146
+ },
147
+ nameOf: (v) => names.get(v),
148
+ reactionsFor: (eventName) => reactions.filter((x) => x.r.event.name === eventName),
149
+ approvalFor: (aggName) => approvals.find((x) => x.a.agg.name === aggName),
150
+ approvalByProcess: (process) => approvals.find((x) => x.a.process === process),
151
+ capabilityCatalog: () => {
152
+ const out = [];
153
+ for (const a of aggregates) {
154
+ out.push({ capability: `${a.name}.read`, kind: 'read', aggregate: a.name, description: `查看${a.description ?? a.name}` });
155
+ out.push({ capability: `${a.name}.export`, kind: 'export', aggregate: a.name, description: `导出${a.description ?? a.name}` });
156
+ for (const kind of new Set(Object.values(sensitiveFields(a)))) {
157
+ out.push({ capability: `${a.name}.unmask.${kind}`, kind: 'unmask', aggregate: a.name, description: `查看 ${a.name} 的 ${kind} 明文` });
158
+ }
159
+ }
160
+ for (const c of commands) {
161
+ out.push({
162
+ capability: c.capability, kind: 'command', aggregate: c.cmd.agg.name,
163
+ description: c.cmd.description, invokedBy: c.cmd.invokedBy,
164
+ });
165
+ }
166
+ // 框架系统能力:审计查询、异步任务运维(内核拥有审计表和 outbox)
167
+ out.push({ capability: 'Sys.audit.read', kind: 'system', description: '查询审计记录' }, { capability: 'Sys.ops.read', kind: 'system', description: '查看异步任务、死信、第三方同步记录' }, { capability: 'Sys.ops.manage', kind: 'system', description: '重放或丢弃死信、启停定时任务' });
168
+ for (const e of opts.extraCapabilities ?? [])
169
+ out.push(e);
170
+ return out.sort((x, y) => x.capability.localeCompare(y.capability));
171
+ },
172
+ consumerProviders: () => extensions.map((e) => e.value).filter((v) => typeof v?.[CONSUMERS] === 'function'),
173
+ };
174
+ return Object.freeze(registry);
175
+ }
176
+ export { CONNECTOR, INBOUND, OUTBOUND, ROUTE };
177
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { Kysely, Transaction } from 'kysely';
2
+ import type { AnyAggregate } from './primitives/aggregate.js';
3
+ import type { Registry } from './registry.js';
4
+ import type { Ctx } from './types.js';
5
+ export declare const relationTable: (owner: AnyAggregate, field: string) => {
6
+ schema: "core" | "sys";
7
+ name: string;
8
+ };
9
+ /** 命令/已声明读依赖的内部快照包含完整关联 ID,才能保留未被本次命令修改的关系。
10
+ * 对外读侧不使用此函数,而通过安全视图分页读取目标对象。 */
11
+ export declare function loadRelationIds(tx: Kysely<any> | Transaction<any>, owner: AnyAggregate, rows: Record<string, any>[], ctx: Ctx): Promise<Record<string, any>[]>;
12
+ /** 只校验新增的目标:无关字段更新不应因旧目标当前不可见而失败或丢失旧关系。
13
+ * 新增目标必须可选择且属于同一租户;数据库的复合外键再保证持久化一致性。 */
14
+ export declare function writeRelations(tx: Transaction<any>, reg: Registry, owner: AnyAggregate, previous: Record<string, any>, next: Record<string, any>, ctx: Ctx): Promise<void>;
@@ -0,0 +1,58 @@
1
+ import { cageError } from './errors.js';
2
+ import { compileScope } from './scope/index.js';
3
+ import { snake } from './util/names.js';
4
+ export const relationTable = (owner, field) => ({
5
+ schema: owner.table.schema,
6
+ name: `${owner.table.name}__${snake(field)}`,
7
+ });
8
+ /** 命令/已声明读依赖的内部快照包含完整关联 ID,才能保留未被本次命令修改的关系。
9
+ * 对外读侧不使用此函数,而通过安全视图分页读取目标对象。 */
10
+ export async function loadRelationIds(tx, owner, rows, ctx) {
11
+ if (!rows.length)
12
+ return rows;
13
+ for (const [key, field] of Object.entries(owner.fields)) {
14
+ if (field.kind !== 'manyToMany')
15
+ continue;
16
+ const table = relationTable(owner, key);
17
+ const links = await tx.selectFrom(`${table.schema}.${table.name}`)
18
+ .select(['source_id', 'target_id']).where('tenant_id', '=', ctx.tenantId)
19
+ .where('source_id', 'in', rows.map((row) => row.id)).orderBy('target_id').execute();
20
+ const byId = new Map(rows.map((row) => [row.id, row]));
21
+ for (const row of rows)
22
+ row[key] = [];
23
+ for (const link of links)
24
+ byId.get(link.source_id)[key].push(link.target_id);
25
+ }
26
+ return rows;
27
+ }
28
+ /** 只校验新增的目标:无关字段更新不应因旧目标当前不可见而失败或丢失旧关系。
29
+ * 新增目标必须可选择且属于同一租户;数据库的复合外键再保证持久化一致性。 */
30
+ export async function writeRelations(tx, reg, owner, previous, next, ctx) {
31
+ for (const [key, field] of Object.entries(owner.fields)) {
32
+ if (field.kind !== 'manyToMany')
33
+ continue;
34
+ const before = previous[key] ?? [];
35
+ const after = next[key];
36
+ if (JSON.stringify(before) === JSON.stringify(after))
37
+ continue;
38
+ const added = after.filter((id) => !before.includes(id));
39
+ if (added.length) {
40
+ const target = reg.aggregate(field.ref);
41
+ if (ctx.actor?.kind === 'user' && !ctx.capabilities.has(`${target.name}.read`))
42
+ throw cageError('FORBIDDEN', { capability: `${target.name}.read` });
43
+ const visible = await tx.selectFrom(`${target.table.schema}.${target.table.name} as target`)
44
+ .select('target.id').where('target.id', 'in', added)
45
+ .where('target.status', '<>', 'deleted')
46
+ .where(compileScope(target.scope, target.name, ctx, 'target')).forKeyShare().execute();
47
+ if (visible.length !== added.length)
48
+ throw cageError('NOT_FOUND');
49
+ }
50
+ const table = relationTable(owner, key);
51
+ await tx.deleteFrom(`${table.schema}.${table.name}`)
52
+ .where('tenant_id', '=', ctx.tenantId).where('source_id', '=', next.id).execute();
53
+ if (after.length)
54
+ await tx.insertInto(`${table.schema}.${table.name}`)
55
+ .values(after.map((id) => ({ tenant_id: ctx.tenantId, source_id: next.id, target_id: id }))).execute();
56
+ }
57
+ }
58
+ //# sourceMappingURL=relations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relations.js","sourceRoot":"","sources":["../src/relations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import { type RawBuilder } from 'kysely';
2
+ import type { Combinator, Ctx, ScopeGrant } from '../types.js';
3
+ export interface ScopeSpec {
4
+ /** 部门字段(dept、deptSubtree、deptSet 依赖它) */
5
+ readonly deptField?: string;
6
+ /** 创建人字段(self 依赖它) */
7
+ readonly ownerField?: string;
8
+ /** 声明时给出的建议组合子,仅用于文档与状态图 */
9
+ readonly hint?: Combinator;
10
+ }
11
+ /**
12
+ * 范围声明:
13
+ * scope.deptSubtree('deptId') 部门字段是 deptId,创建人字段自动识别 createdBy
14
+ * scope.self('createdBy') 只有创建人字段
15
+ * scope.of({ dept: 'deptId', owner: 'ownerId' })
16
+ * scope.none() 没有部门和创建人字段(只能授予 all)
17
+ */
18
+ export declare const scope: {
19
+ self: (ownerField?: string) => ScopeSpec;
20
+ dept: (deptField: string, ownerField?: string) => ScopeSpec;
21
+ deptSubtree: (deptField: string, ownerField?: string) => ScopeSpec;
22
+ deptSet: (deptField: string, ownerField?: string) => ScopeSpec;
23
+ all: () => ScopeSpec;
24
+ of: (f: {
25
+ dept?: string;
26
+ owner?: string;
27
+ }) => ScopeSpec;
28
+ none: () => ScopeSpec;
29
+ };
30
+ /** 没有任何授权时按「仅本人」处理(默认拒绝的一部分) */
31
+ export declare const DEFAULT_GRANTS: ScopeGrant[];
32
+ export declare function grantsFor(ctx: Ctx, aggName: string): ScopeGrant[];
33
+ /** 把一个组合子翻译成参数化 SQL 条件。字段名用 sql.ref 引用,值全部参数化,没有字符串拼接。 */
34
+ export declare function compileGrant(s: ScopeSpec, g: ScopeGrant, ctx: Ctx, alias?: string): RawBuilder<boolean>;
35
+ /** 多个授权取并集:用 OR 连接;外层始终加租户条件。 */
36
+ export declare function compileScope(s: ScopeSpec, aggName: string, ctx: Ctx, alias?: string): RawBuilder<boolean>;
37
+ /**
38
+ * 内存判定:与 SQL 翻译语义一致。deptSubtree 需要知道记录所在部门的祖先路径,由调用方提供。
39
+ */
40
+ export declare function scopeMatches(s: ScopeSpec, aggName: string, ctx: Ctx, row: Record<string, unknown> & {
41
+ tenantId?: string;
42
+ }, deptAncestorsOf?: (deptId: string) => string | undefined): boolean;
@@ -0,0 +1,90 @@
1
+ // 数据范围:聚合声明「哪个字段是部门、哪个字段是创建人」,授权数据决定用哪个组合子。
2
+ // 每个组合子都有 SQL 翻译(compileScope)和内存判定(scopeMatches),两者语义一致。
3
+ import { sql } from 'kysely';
4
+ import { snake } from '../util/names.js';
5
+ function spec(hint, deptField, ownerField) {
6
+ return Object.freeze({ deptField, ownerField, hint });
7
+ }
8
+ /**
9
+ * 范围声明:
10
+ * scope.deptSubtree('deptId') 部门字段是 deptId,创建人字段自动识别 createdBy
11
+ * scope.self('createdBy') 只有创建人字段
12
+ * scope.of({ dept: 'deptId', owner: 'ownerId' })
13
+ * scope.none() 没有部门和创建人字段(只能授予 all)
14
+ */
15
+ export const scope = {
16
+ self: (ownerField = 'createdBy') => spec('self', undefined, ownerField),
17
+ dept: (deptField, ownerField) => spec('dept', deptField, ownerField),
18
+ deptSubtree: (deptField, ownerField) => spec('deptSubtree', deptField, ownerField),
19
+ deptSet: (deptField, ownerField) => spec('deptSet', deptField, ownerField),
20
+ all: () => spec('all'),
21
+ of: (f) => spec(undefined, f.dept, f.owner),
22
+ none: () => spec(undefined),
23
+ };
24
+ /** 没有任何授权时按「仅本人」处理(默认拒绝的一部分) */
25
+ export const DEFAULT_GRANTS = [{ combinator: 'self' }];
26
+ export function grantsFor(ctx, aggName) {
27
+ const g = ctx.scopes[aggName] ?? ctx.scopes['*'];
28
+ return g && g.length > 0 ? g : DEFAULT_GRANTS;
29
+ }
30
+ /** 把一个组合子翻译成参数化 SQL 条件。字段名用 sql.ref 引用,值全部参数化,没有字符串拼接。 */
31
+ export function compileGrant(s, g, ctx, alias) {
32
+ const col = (field) => sql.ref(alias ? `${alias}.${snake(field)}` : snake(field));
33
+ switch (g.combinator) {
34
+ case 'all':
35
+ return sql `true`;
36
+ case 'self': {
37
+ const who = ctx.userId ?? ctx.onBehalfOf ?? null;
38
+ return s.ownerField && who ? sql `${col(s.ownerField)} = ${who}` : sql `false`;
39
+ }
40
+ case 'dept':
41
+ return s.deptField && ctx.deptId ? sql `${col(s.deptField)} = ${ctx.deptId}` : sql `false`;
42
+ case 'deptSubtree':
43
+ return s.deptField && ctx.deptPath
44
+ ? sql `${col(s.deptField)} in (select d.id from sys.dept d where d.tenant_id = ${ctx.tenantId} and d.ancestors like ${ctx.deptPath + '%'})`
45
+ : sql `false`;
46
+ case 'deptSet':
47
+ return s.deptField && g.deptIds && g.deptIds.length > 0
48
+ ? sql `${col(s.deptField)} = any(${g.deptIds}::uuid[])`
49
+ : sql `false`;
50
+ default:
51
+ throw new Error('[CAGE-SCOPE-001] 未知的范围组合子');
52
+ }
53
+ }
54
+ /** 多个授权取并集:用 OR 连接;外层始终加租户条件。 */
55
+ export function compileScope(s, aggName, ctx, alias) {
56
+ const grants = grantsFor(ctx, aggName);
57
+ const parts = grants.map((g) => compileGrant(s, g, ctx, alias));
58
+ const tenantCol = sql.ref(alias ? `${alias}.tenant_id` : 'tenant_id');
59
+ return sql `(${tenantCol} = ${ctx.tenantId} and (${sql.join(parts, sql ` or `)}))`;
60
+ }
61
+ /**
62
+ * 内存判定:与 SQL 翻译语义一致。deptSubtree 需要知道记录所在部门的祖先路径,由调用方提供。
63
+ */
64
+ export function scopeMatches(s, aggName, ctx, row, deptAncestorsOf) {
65
+ if (row.tenantId !== undefined && row.tenantId !== ctx.tenantId)
66
+ return false;
67
+ return grantsFor(ctx, aggName).some((g) => {
68
+ switch (g.combinator) {
69
+ case 'all':
70
+ return true;
71
+ case 'self': {
72
+ const who = ctx.userId ?? ctx.onBehalfOf ?? null;
73
+ return !!s.ownerField && !!who && row[s.ownerField] === who;
74
+ }
75
+ case 'dept':
76
+ return !!s.deptField && !!ctx.deptId && row[s.deptField] === ctx.deptId;
77
+ case 'deptSubtree': {
78
+ if (!s.deptField || !ctx.deptPath)
79
+ return false;
80
+ const anc = deptAncestorsOf?.(String(row[s.deptField]));
81
+ return !!anc && anc.startsWith(ctx.deptPath);
82
+ }
83
+ case 'deptSet':
84
+ return !!s.deptField && !!g.deptIds?.includes(String(row[s.deptField]));
85
+ default:
86
+ return false;
87
+ }
88
+ });
89
+ }
90
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/scope/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export declare function step<T>(name: string, fn: () => Promise<T>): Promise<T>;
2
+ export declare function traceCarrier(): Record<string, string>;
3
+ export declare function withCarrier<T>(carrier: Record<string, string> | null | undefined, fn: () => Promise<T>): Promise<T>;
4
+ export interface Logger {
5
+ info(obj: object | string, msg?: string): void;
6
+ warn(obj: object | string, msg?: string): void;
7
+ error(obj: object | string, msg?: string): void;
8
+ debug?(obj: object | string, msg?: string): void;
9
+ }
10
+ export declare const consoleLogger: Logger;
11
+ export declare const silentLogger: Logger;
@@ -0,0 +1,35 @@
1
+ // 链路追踪:流水线每一关一个 span;写 outbox 时把链路上下文带上,worker 处理时接上。
2
+ import { context, propagation, trace } from '@opentelemetry/api';
3
+ const tracer = trace.getTracer('cage');
4
+ export async function step(name, fn) {
5
+ return tracer.startActiveSpan(`cage.${name}`, async (span) => {
6
+ try {
7
+ return await fn();
8
+ }
9
+ catch (e) {
10
+ span.recordException(e);
11
+ throw e;
12
+ }
13
+ finally {
14
+ span.end();
15
+ }
16
+ });
17
+ }
18
+ export function traceCarrier() {
19
+ const carrier = {};
20
+ propagation.inject(context.active(), carrier);
21
+ return carrier;
22
+ }
23
+ export function withCarrier(carrier, fn) {
24
+ if (!carrier)
25
+ return fn();
26
+ const ctx = propagation.extract(context.active(), carrier);
27
+ return context.with(ctx, fn);
28
+ }
29
+ export const consoleLogger = {
30
+ info: (o, m) => console.log(m ?? '', typeof o === 'string' ? o : JSON.stringify(o)),
31
+ warn: (o, m) => console.warn(m ?? '', typeof o === 'string' ? o : JSON.stringify(o)),
32
+ error: (o, m) => console.error(m ?? '', typeof o === 'string' ? o : JSON.stringify(o)),
33
+ };
34
+ export const silentLogger = { info() { }, warn() { }, error() { } };
35
+ //# sourceMappingURL=telemetry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAEjE,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;AAEvC,MAAM,CAAC,KAAK,UAAU,IAAI,CAAI,IAAY,EAAE,EAAoB;IAC9D,OAAO,MAAM,CAAC,eAAe,CAAC,QAAQ,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QAC3D,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,EAAE,CAAC;QACpB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,CAAC,eAAe,CAAC,CAAU,CAAC,CAAC;YACjC,MAAM,CAAC,CAAC;QACV,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,GAAG,EAAE,CAAC;QACb,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,OAAO,CAAC,CAAC;IAC9C,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,WAAW,CAAI,OAAkD,EAAE,EAAoB;IACrG,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,EAAE,CAAC;IAC1B,MAAM,GAAG,GAAG,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,OAAO,CAAC,CAAC;IAC3D,OAAO,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;AAC/B,CAAC;AASD,MAAM,CAAC,MAAM,aAAa,GAAW;IACnC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IACnF,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IACpF,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;CACvF,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAW,EAAE,IAAI,KAAI,CAAC,EAAE,IAAI,KAAI,CAAC,EAAE,KAAK,KAAI,CAAC,EAAE,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { z } from 'zod';
2
+ export declare const MAX_IMPORT_BYTES = 1048576;
3
+ export declare const MAX_IMPORT_ROWS = 1000;
4
+ export declare function csvCell(value: unknown): string;
5
+ /** 接受 UTF-8 BOM、CRLF、引号内换行和双引号转义;拒绝歧义输入,不静默修正错列。 */
6
+ export declare function parseCsv(text: string): string[][];
7
+ export declare function importShape(schema: z.ZodType): Record<string, z.ZodType>;
8
+ export declare function csvInputs(text: string, schema: z.ZodType): Record<string, unknown>[];
@@ -0,0 +1,114 @@
1
+ // CSV 是传输格式,不是一条新的写入通道:这里只拆分单元格、按命令输入类型转换,
2
+ // 必填、长度、业务约束仍由 worker 调用原命令时统一检查。
3
+ import { z } from 'zod';
4
+ import { cageError } from '../errors.js';
5
+ export const MAX_IMPORT_BYTES = 1_048_576;
6
+ export const MAX_IMPORT_ROWS = 1000;
7
+ export function csvCell(value) {
8
+ const text = value == null ? '' : typeof value === 'object' ? JSON.stringify(value) : String(value);
9
+ // 表格软件会执行以这些字符开头的单元格。导出文件用于查看,不能把公式当作数据执行。
10
+ const safe = /^[=+\-@\t\r]/.test(text) ? `'${text}` : text;
11
+ return /[",\r\n]/.test(safe) ? `"${safe.replace(/"/g, '""')}"` : safe;
12
+ }
13
+ function invalid(message) { throw cageError('INVALID_INPUT', message); }
14
+ /** 接受 UTF-8 BOM、CRLF、引号内换行和双引号转义;拒绝歧义输入,不静默修正错列。 */
15
+ export function parseCsv(text) {
16
+ if (Buffer.byteLength(text, 'utf8') > MAX_IMPORT_BYTES)
17
+ invalid('CSV 文件不能超过 1 MiB');
18
+ text = text.replace(/^\uFEFF/, '');
19
+ const rows = [];
20
+ let row = [], cell = '', quoted = false, closed = false;
21
+ const endRow = () => {
22
+ row.push(cell);
23
+ if (row.length !== 1 || row[0] !== '')
24
+ rows.push(row);
25
+ if (rows.length > MAX_IMPORT_ROWS + 1)
26
+ invalid(`一次最多导入 ${MAX_IMPORT_ROWS} 行`);
27
+ row = [];
28
+ cell = '';
29
+ closed = false;
30
+ };
31
+ for (let i = 0; i < text.length; i++) {
32
+ const ch = text[i];
33
+ if (quoted) {
34
+ if (ch !== '"')
35
+ cell += ch;
36
+ else if (text[i + 1] === '"') {
37
+ cell += '"';
38
+ i++;
39
+ }
40
+ else {
41
+ quoted = false;
42
+ closed = true;
43
+ }
44
+ }
45
+ else if (ch === ',') {
46
+ row.push(cell);
47
+ cell = '';
48
+ closed = false;
49
+ }
50
+ else if (ch === '\n' || ch === '\r') {
51
+ if (ch === '\r' && text[i + 1] === '\n')
52
+ i++;
53
+ endRow();
54
+ }
55
+ else if (ch === '"' && !cell && !closed)
56
+ quoted = true;
57
+ else if (closed || ch === '"')
58
+ invalid('CSV 引号格式错误');
59
+ else
60
+ cell += ch;
61
+ }
62
+ if (quoted)
63
+ invalid('CSV 存在未闭合的引号');
64
+ if (cell || row.length || closed)
65
+ endRow();
66
+ if (rows.length < 2)
67
+ invalid('CSV 需要表头和至少一行数据');
68
+ if (new Set(rows[0]).size !== rows[0].length || rows[0].some((v) => !v))
69
+ invalid('CSV 表头不能为空或重复');
70
+ for (let i = 1; i < rows.length; i++)
71
+ if (rows[i].length !== rows[0].length)
72
+ invalid(`第 ${i + 1} 条 CSV 记录的列数与表头不一致`);
73
+ return rows;
74
+ }
75
+ /** 只剥掉输入外壳;复杂联合类型保持字符串,由命令自己的 schema 给出校验结果。 */
76
+ function inner(schema) {
77
+ if (schema instanceof z.ZodOptional || schema instanceof z.ZodNullable || schema instanceof z.ZodDefault)
78
+ return inner(schema.unwrap());
79
+ return schema;
80
+ }
81
+ export function importShape(schema) {
82
+ if (!(schema instanceof z.ZodObject))
83
+ invalid('CSV 导入要求命令使用 z.object 声明输入');
84
+ return { ...schema.shape, expectedVersion: z.number().int().optional() };
85
+ }
86
+ export function csvInputs(text, schema) {
87
+ const [headers, ...rows] = parseCsv(text);
88
+ const shape = importShape(schema);
89
+ for (const header of headers)
90
+ if (!Object.hasOwn(shape, header))
91
+ invalid(`命令输入中没有字段 ${header}`);
92
+ return rows.map((cells) => Object.fromEntries(headers.flatMap((header, index) => {
93
+ const value = cells[index], field = shape[header], type = inner(field);
94
+ // 空的可选列表示不提供,避免把“保持原值”误解释成“清空字段”。
95
+ if (value === '' && field.isOptional())
96
+ return [];
97
+ let parsed = value;
98
+ if (type instanceof z.ZodNumber && value.trim() !== '')
99
+ parsed = Number(value);
100
+ else if (type instanceof z.ZodBoolean && ['true', 'false'].includes(value))
101
+ parsed = value === 'true';
102
+ else if (type instanceof z.ZodArray || type instanceof z.ZodObject || type instanceof z.ZodRecord || (field.isNullable() && value === 'null')) {
103
+ // JSON 错误保留原字符串,让该行在命令校验阶段失败,不丢掉其他正确行。
104
+ try {
105
+ parsed = JSON.parse(value);
106
+ }
107
+ catch {
108
+ parsed = value;
109
+ }
110
+ }
111
+ return [[header, parsed]];
112
+ })));
113
+ }
114
+ //# 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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"}