@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,87 @@
|
|
|
1
|
+
// 迁移执行器:框架系统表 → 项目迁移(db/migrations)→ 重建安全层。用迁移账号执行,带咨询锁防止并发。
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { readdirSync, readFileSync, existsSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import pg from 'pg';
|
|
6
|
+
import { SCHEMA_MIGRATIONS_SQL, SYSTEM_MIGRATIONS, safeLayerSql } from './ddl.js';
|
|
7
|
+
const MIGRATION_LOCK = 727274; // 固定的咨询锁编号
|
|
8
|
+
/** 读取 db/migrations 下的 .sql 文件,按文件名排序 */
|
|
9
|
+
export function readMigrations(dir) {
|
|
10
|
+
if (!existsSync(dir))
|
|
11
|
+
return [];
|
|
12
|
+
return readdirSync(dir)
|
|
13
|
+
.filter((f) => f.endsWith('.sql'))
|
|
14
|
+
.sort()
|
|
15
|
+
.map((f) => ({ name: f.replace(/\.sql$/, ''), sql: readFileSync(join(dir, f), 'utf8') }));
|
|
16
|
+
}
|
|
17
|
+
const SNAPSHOT_RE = /\/\* cage:schema\n([\s\S]*?)\n\*\//;
|
|
18
|
+
/** 迁移文件末尾内嵌的结构快照(cage gen 写入),用于下一次比较 */
|
|
19
|
+
export function snapshotOf(m) {
|
|
20
|
+
const hit = SNAPSHOT_RE.exec(m.sql);
|
|
21
|
+
return hit ? JSON.parse(hit[1]) : null;
|
|
22
|
+
}
|
|
23
|
+
export function latestSnapshot(files) {
|
|
24
|
+
for (let i = files.length - 1; i >= 0; i--) {
|
|
25
|
+
const s = snapshotOf(files[i]);
|
|
26
|
+
if (s)
|
|
27
|
+
return s;
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
export function embedSnapshot(sql, model) {
|
|
32
|
+
return `${sql.trimEnd()}\n\n/* cage:schema\n${JSON.stringify(model)}\n*/\n`;
|
|
33
|
+
}
|
|
34
|
+
const checksum = (s) => createHash('sha256').update(s).digest('hex').slice(0, 16);
|
|
35
|
+
export async function runMigrations(opts) {
|
|
36
|
+
const log = opts.log ?? (() => { });
|
|
37
|
+
const client = new pg.Client({ connectionString: opts.migrateUrl, application_name: 'cage-migrate' });
|
|
38
|
+
await client.connect();
|
|
39
|
+
const applied = [];
|
|
40
|
+
try {
|
|
41
|
+
await client.query('select pg_advisory_lock($1)', [MIGRATION_LOCK]);
|
|
42
|
+
await client.query(SCHEMA_MIGRATIONS_SQL);
|
|
43
|
+
const done = new Map((await client.query('select name, checksum from sys.schema_migrations')).rows.map((r) => [r.name, r.checksum]));
|
|
44
|
+
const all = [...SYSTEM_MIGRATIONS, ...opts.registry.systemDdl.map((sql, i) => ({ name: `cage:ext_${checksum(sql)}_${i}`, sql })), ...opts.migrations];
|
|
45
|
+
// 在执行任何新迁移之前核对历史内容。否则同名文件被改写后会被直接跳过,
|
|
46
|
+
// 本地结构快照看起来已更新,真实数据库却仍保持旧结构,下一次发布才会出错。
|
|
47
|
+
for (const migration of all) {
|
|
48
|
+
if (done.has(migration.name) && done.get(migration.name) !== checksum(migration.sql)) {
|
|
49
|
+
throw new Error(`[CAGE-MIG-005] 已执行迁移 ${migration.name} 的内容被修改,请恢复原文件并新增迁移`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
for (const m of all) {
|
|
53
|
+
if (done.has(m.name))
|
|
54
|
+
continue;
|
|
55
|
+
log(`迁移 ${m.name}`);
|
|
56
|
+
await client.query('begin');
|
|
57
|
+
try {
|
|
58
|
+
await client.query(m.sql);
|
|
59
|
+
await client.query('insert into sys.schema_migrations (name, checksum) values ($1, $2)', [m.name, checksum(m.sql)]);
|
|
60
|
+
await client.query('commit');
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
await client.query('rollback');
|
|
64
|
+
throw new Error(`[CAGE-MIG-001] 迁移 ${m.name} 失败:${e.message}`);
|
|
65
|
+
}
|
|
66
|
+
applied.push(m.name);
|
|
67
|
+
}
|
|
68
|
+
// 安全层每次整体重建
|
|
69
|
+
await client.query('begin');
|
|
70
|
+
try {
|
|
71
|
+
await client.query(safeLayerSql(opts.registry));
|
|
72
|
+
await client.query('select sys.ensure_audit_partitions(3)');
|
|
73
|
+
await client.query('commit');
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
await client.query('rollback');
|
|
77
|
+
throw new Error(`[CAGE-MIG-002] 重建安全层失败:${e.message}`);
|
|
78
|
+
}
|
|
79
|
+
log('安全层已重建');
|
|
80
|
+
return { applied, safeLayerRebuilt: true };
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
await client.query('select pg_advisory_unlock($1)', [MIGRATION_LOCK]).catch(() => { });
|
|
84
|
+
await client.end();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=migrate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../src/db/migrate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AnyAggregate } from '../primitives/aggregate.js';
|
|
2
|
+
export declare const SYSTEM_COLUMNS: string[];
|
|
3
|
+
/** 行 → 快照(不含 tenantId;hidden 字段可选保留) */
|
|
4
|
+
export declare function rowToSnapshot(agg: AnyAggregate, row: Record<string, unknown>, keepHidden?: boolean): Record<string, any>;
|
|
5
|
+
/** 视图行(列名 snake_case)→ camelCase 对象 */
|
|
6
|
+
export declare function viewRowToObject(row: Record<string, unknown>): Record<string, any>;
|
|
7
|
+
/** 快照字段 → 列值(jsonb 字段序列化) */
|
|
8
|
+
export declare function fieldsToColumns(agg: AnyAggregate, values: Record<string, unknown>): Record<string, unknown>;
|
|
9
|
+
/** 只比较业务字段,找出变化的字段 */
|
|
10
|
+
export declare function diffFields(agg: AnyAggregate, cur: Record<string, unknown>, next: Record<string, unknown>): string[];
|
package/dist/db/rows.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { isRelation } from '../primitives/fields.js';
|
|
2
|
+
import { camel, snake } from '../util/names.js';
|
|
3
|
+
export const SYSTEM_COLUMNS = ['id', 'tenant_id', 'status', 'version', 'created_at', 'updated_at'];
|
|
4
|
+
function normalize(v) {
|
|
5
|
+
if (v instanceof Date)
|
|
6
|
+
return v.toISOString();
|
|
7
|
+
return v;
|
|
8
|
+
}
|
|
9
|
+
/** 行 → 快照(不含 tenantId;hidden 字段可选保留) */
|
|
10
|
+
export function rowToSnapshot(agg, row, keepHidden = true) {
|
|
11
|
+
const out = {
|
|
12
|
+
id: row.id,
|
|
13
|
+
status: row.status,
|
|
14
|
+
version: row.version,
|
|
15
|
+
createdAt: normalize(row.created_at),
|
|
16
|
+
updatedAt: normalize(row.updated_at),
|
|
17
|
+
};
|
|
18
|
+
for (const [k, f] of Object.entries(agg.fields)) {
|
|
19
|
+
if (f.isHidden && !keepHidden)
|
|
20
|
+
continue;
|
|
21
|
+
const col = snake(k);
|
|
22
|
+
if (col in row)
|
|
23
|
+
out[k] = normalize(row[col]) ?? null;
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
/** 视图行(列名 snake_case)→ camelCase 对象 */
|
|
28
|
+
export function viewRowToObject(row) {
|
|
29
|
+
const out = {};
|
|
30
|
+
for (const [k, v] of Object.entries(row)) {
|
|
31
|
+
if (k === 'tenant_id')
|
|
32
|
+
continue;
|
|
33
|
+
out[camel(k)] = normalize(v);
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
/** 快照字段 → 列值(jsonb 字段序列化) */
|
|
38
|
+
export function fieldsToColumns(agg, values) {
|
|
39
|
+
const out = {};
|
|
40
|
+
for (const [k, f] of Object.entries(agg.fields)) {
|
|
41
|
+
if (isRelation(f))
|
|
42
|
+
continue;
|
|
43
|
+
if (!(k in values))
|
|
44
|
+
continue;
|
|
45
|
+
const v = values[k];
|
|
46
|
+
out[snake(k)] = f.sql === 'jsonb' && v !== null && v !== undefined ? JSON.stringify(v) : v ?? null;
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
/** 只比较业务字段,找出变化的字段 */
|
|
51
|
+
export function diffFields(agg, cur, next) {
|
|
52
|
+
const changed = [];
|
|
53
|
+
for (const k of Object.keys(agg.fields)) {
|
|
54
|
+
if (JSON.stringify(cur[k] ?? null) !== JSON.stringify(next[k] ?? null))
|
|
55
|
+
changed.push(k);
|
|
56
|
+
}
|
|
57
|
+
return changed;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=rows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rows.js","sourceRoot":"","sources":["../../src/db/rows.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// 会话变量:内核在事务开始时写入当前主体,安全视图和行级策略读取它们。
|
|
2
|
+
// set_config 的第三个参数 true 表示只在本事务内有效,事务结束自动清空;忘了设置时一条都查不到。
|
|
3
|
+
import { sql } from 'kysely';
|
|
4
|
+
export function scopesJson(ctx) {
|
|
5
|
+
const out = {};
|
|
6
|
+
for (const [agg, grants] of Object.entries(ctx.scopes)) {
|
|
7
|
+
out[agg] = grants.map((g) => (g.deptIds ? { c: g.combinator, deptIds: g.deptIds } : { c: g.combinator }));
|
|
8
|
+
}
|
|
9
|
+
return JSON.stringify(out);
|
|
10
|
+
}
|
|
11
|
+
export async function setSessionVars(tx, ctx, local = true) {
|
|
12
|
+
await sql `select
|
|
13
|
+
set_config('app.user_id', ${ctx.userId ?? ctx.onBehalfOf ?? ''}, ${local}),
|
|
14
|
+
set_config('app.tenant_id', ${ctx.tenantId}, ${local}),
|
|
15
|
+
set_config('app.dept_id', ${ctx.deptId ?? ''}, ${local}),
|
|
16
|
+
set_config('app.dept_path', ${ctx.deptPath ?? ''}, ${local}),
|
|
17
|
+
set_config('app.scopes', ${scopesJson(ctx)}, ${local}),
|
|
18
|
+
set_config('app.unmask', ${ctx.unmask.join(',')}, ${local})`.execute(tx);
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/db/session.ts"],"names":[],"mappings":"AAAA,qCAAqC;AACrC,0DAA0D;AAC1D,OAAO,EAAe,GAAG,EAAoB,MAAM,QAAQ,CAAC;AAG5D,MAAM,UAAU,UAAU,CAAC,GAAQ;IACjC,MAAM,GAAG,GAAwD,EAAE,CAAC;IACpE,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QACvD,GAAG,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;IAC5G,CAAC;IACD,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,EAAkC,EAAE,GAAQ,EAAE,KAAK,GAAG,IAAI;IAC7F,MAAM,GAAG,CAAA;kCACuB,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,IAAI,EAAE,KAAK,KAAK;kCAC5C,GAAG,CAAC,QAAQ,KAAK,KAAK;kCACtB,GAAG,CAAC,MAAM,IAAI,EAAE,KAAK,KAAK;kCAC1B,GAAG,CAAC,QAAQ,IAAI,EAAE,KAAK,KAAK;kCAC5B,UAAU,CAAC,GAAG,CAAC,KAAK,KAAK;kCACzB,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,KAAK,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;AAChF,CAAC"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export declare const ERROR_HTTP: {
|
|
2
|
+
readonly UNAUTHENTICATED: 401;
|
|
3
|
+
readonly FORBIDDEN: 403;
|
|
4
|
+
readonly NOT_FOUND: 404;
|
|
5
|
+
readonly INVALID_INPUT: 400;
|
|
6
|
+
readonly INVALID_TRANSITION: 409;
|
|
7
|
+
readonly CONSTRAINT_VIOLATED: 403;
|
|
8
|
+
readonly INVARIANT_VIOLATED: 409;
|
|
9
|
+
readonly CONFLICT: 409;
|
|
10
|
+
readonly DOMAIN_ERROR: 422;
|
|
11
|
+
readonly RATE_LIMITED: 429;
|
|
12
|
+
readonly INTERNAL: 500;
|
|
13
|
+
};
|
|
14
|
+
export type CageErrorCode = keyof typeof ERROR_HTTP;
|
|
15
|
+
export declare class CageError extends Error {
|
|
16
|
+
readonly code: CageErrorCode;
|
|
17
|
+
readonly http: number;
|
|
18
|
+
readonly detail?: unknown;
|
|
19
|
+
/** 业务错误码,只有 DOMAIN_ERROR 才有 */
|
|
20
|
+
readonly domainCode?: string;
|
|
21
|
+
constructor(code: CageErrorCode, detail?: unknown, domainCode?: string);
|
|
22
|
+
toJSON(): {
|
|
23
|
+
detail: unknown;
|
|
24
|
+
domainCode?: string | undefined;
|
|
25
|
+
code: "UNAUTHENTICATED" | "FORBIDDEN" | "NOT_FOUND" | "INVALID_INPUT" | "INVALID_TRANSITION" | "CONSTRAINT_VIOLATED" | "INVARIANT_VIOLATED" | "CONFLICT" | "DOMAIN_ERROR" | "RATE_LIMITED" | "INTERNAL";
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export declare function cageError(code: CageErrorCode, detail?: unknown): CageError;
|
|
29
|
+
export declare function isCageError(e: unknown): e is CageError;
|
|
30
|
+
declare const DOMAIN: unique symbol;
|
|
31
|
+
/** handler 返回的业务错误。整个事务回滚,HTTP 返回 422 + 业务码。 */
|
|
32
|
+
export interface DomainError<C extends string = string> {
|
|
33
|
+
readonly [DOMAIN]: true;
|
|
34
|
+
readonly code: C;
|
|
35
|
+
readonly detail?: unknown;
|
|
36
|
+
}
|
|
37
|
+
/** 在 handler 里返回业务错误:return fail('STOCK_SHORT', short) */
|
|
38
|
+
export declare function fail<C extends string>(code: C, detail?: unknown): DomainError<C>;
|
|
39
|
+
export declare function isDomainError(v: unknown): v is DomainError;
|
|
40
|
+
/** 把任何异常转换成 CageError,不泄露堆栈。 */
|
|
41
|
+
export declare function toCageError(err: unknown): CageError;
|
|
42
|
+
/** 声明期错误(构建时抛出),带错误码,agent 可以查 docs/errors。 */
|
|
43
|
+
export declare function declError(code: string, message: string): Error;
|
|
44
|
+
export {};
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// 内核错误码:机器可读,前端与 agent 都按码处理(设计文档第 5 节)。
|
|
2
|
+
export const ERROR_HTTP = {
|
|
3
|
+
UNAUTHENTICATED: 401,
|
|
4
|
+
FORBIDDEN: 403,
|
|
5
|
+
NOT_FOUND: 404,
|
|
6
|
+
INVALID_INPUT: 400,
|
|
7
|
+
INVALID_TRANSITION: 409,
|
|
8
|
+
CONSTRAINT_VIOLATED: 403,
|
|
9
|
+
INVARIANT_VIOLATED: 409,
|
|
10
|
+
CONFLICT: 409,
|
|
11
|
+
DOMAIN_ERROR: 422,
|
|
12
|
+
RATE_LIMITED: 429,
|
|
13
|
+
INTERNAL: 500,
|
|
14
|
+
};
|
|
15
|
+
export class CageError extends Error {
|
|
16
|
+
code;
|
|
17
|
+
http;
|
|
18
|
+
detail;
|
|
19
|
+
/** 业务错误码,只有 DOMAIN_ERROR 才有 */
|
|
20
|
+
domainCode;
|
|
21
|
+
constructor(code, detail, domainCode) {
|
|
22
|
+
super(domainCode ? `${code}:${domainCode}` : code);
|
|
23
|
+
this.name = 'CageError';
|
|
24
|
+
this.code = code;
|
|
25
|
+
this.http = ERROR_HTTP[code];
|
|
26
|
+
this.detail = detail;
|
|
27
|
+
this.domainCode = domainCode;
|
|
28
|
+
}
|
|
29
|
+
toJSON() {
|
|
30
|
+
return { code: this.code, ...(this.domainCode ? { domainCode: this.domainCode } : {}), detail: this.detail };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function cageError(code, detail) {
|
|
34
|
+
return new CageError(code, detail);
|
|
35
|
+
}
|
|
36
|
+
export function isCageError(e) {
|
|
37
|
+
return e instanceof CageError || (!!e && e.name === 'CageError' && typeof e.code === 'string');
|
|
38
|
+
}
|
|
39
|
+
const DOMAIN = Symbol.for('cage.domainError');
|
|
40
|
+
/** 在 handler 里返回业务错误:return fail('STOCK_SHORT', short) */
|
|
41
|
+
export function fail(code, detail) {
|
|
42
|
+
return Object.freeze({ [DOMAIN]: true, code, detail });
|
|
43
|
+
}
|
|
44
|
+
export function isDomainError(v) {
|
|
45
|
+
return !!v && typeof v === 'object' && v[DOMAIN] === true;
|
|
46
|
+
}
|
|
47
|
+
/** 把任何异常转换成 CageError,不泄露堆栈。 */
|
|
48
|
+
export function toCageError(err) {
|
|
49
|
+
if (isCageError(err))
|
|
50
|
+
return err;
|
|
51
|
+
if (isDomainError(err))
|
|
52
|
+
return new CageError('DOMAIN_ERROR', err.detail, err.code);
|
|
53
|
+
const anyErr = err;
|
|
54
|
+
if (anyErr?.validation || anyErr?.code === 'FST_ERR_VALIDATION')
|
|
55
|
+
return new CageError('INVALID_INPUT', anyErr.message);
|
|
56
|
+
if (anyErr?.statusCode === 429)
|
|
57
|
+
return new CageError('RATE_LIMITED');
|
|
58
|
+
if (anyErr?.statusCode === 413)
|
|
59
|
+
return new CageError('INVALID_INPUT', '请求体超过大小限制');
|
|
60
|
+
if (anyErr?.statusCode === 403 && /csrf/i.test(String(anyErr?.message)))
|
|
61
|
+
return new CageError('FORBIDDEN', 'CSRF');
|
|
62
|
+
if (anyErr?.statusCode === 404)
|
|
63
|
+
return new CageError('NOT_FOUND');
|
|
64
|
+
if (anyErr?.statusCode === 415 || anyErr?.statusCode === 400)
|
|
65
|
+
return new CageError('INVALID_INPUT', anyErr.message);
|
|
66
|
+
// 数据库序列化失败与死锁:作为冲突返回,调用方重试
|
|
67
|
+
if (anyErr?.code === '40001' || anyErr?.code === '40P01')
|
|
68
|
+
return new CageError('CONFLICT');
|
|
69
|
+
// 唯一约束冲突(例如确定性主键重复、唯一字段重复)
|
|
70
|
+
if (anyErr?.code === '23505')
|
|
71
|
+
return new CageError('CONFLICT', { constraint: anyErr.constraint });
|
|
72
|
+
// 检查约束、外键:数据库层兜底拦下的非法数据
|
|
73
|
+
if (anyErr?.code === '23514' || anyErr?.code === '23503')
|
|
74
|
+
return new CageError('INVALID_INPUT', { constraint: anyErr.constraint });
|
|
75
|
+
return new CageError('INTERNAL');
|
|
76
|
+
}
|
|
77
|
+
/** 声明期错误(构建时抛出),带错误码,agent 可以查 docs/errors。 */
|
|
78
|
+
export function declError(code, message) {
|
|
79
|
+
const e = new Error(`[${code}] ${message}(详见 docs/errors/${code}.md)`);
|
|
80
|
+
e.name = 'CageDeclarationError';
|
|
81
|
+
return e;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export * from './brands.js';
|
|
2
|
+
export * from './errors.js';
|
|
3
|
+
export * from './types.js';
|
|
4
|
+
export { Field, text, int, number, money, id, date, datetime, bool, enumOf, ref, hasMany, manyToMany, isRelation, list, json, isField, objectSchema, SENSITIVE_KINDS, type SensitiveKind, type FieldKind, type FieldMeta, type AnyField, type Fields, type FieldValues, type ValueOf, } from './primitives/fields.js';
|
|
5
|
+
export { states, isStates, DEFAULT_STATES, type StatesDef } from './primitives/states.js';
|
|
6
|
+
export { aggregate, isAggregate, hasStates, hiddenFields, sensitiveFields, RESERVED_FIELDS, type Aggregate, type AnyAggregate, type Snapshot, type SnapshotOf, type StateOf, type FieldsOf, type FieldValuesOf, type SystemCols, } from './primitives/aggregate.js';
|
|
7
|
+
export { command, isCommand, idOnly, empty, type Command, type AnyCommand, type Reads, type ReadRepo, type DepsOf, type Draft, type HandlerCtx, type HandlerResult, type Invoker, type FindOptions, } from './primitives/command.js';
|
|
8
|
+
export { event, emit, isEvent, defaultPayload, type Event, type AnyEvent, type PayloadOf, type EmitSpec } from './primitives/event.js';
|
|
9
|
+
export { sod, ceiling, stepUp, ok, violation, type Constraint, type ConstraintResult, type ConstraintMeta, } from './primitives/constraints.js';
|
|
10
|
+
export { reaction, query, approval, job, unsafe, daysAgo, type Reaction, type AnyReaction, type Query, type AnyQuery, type Approval, type Job, type JobApi, type JobQueryApi, type TxMode, type UnsafeTag, } from './primitives/flow.js';
|
|
11
|
+
export { scope, compileScope, compileGrant, scopeMatches, grantsFor, DEFAULT_GRANTS, type ScopeSpec } from './scope/index.js';
|
|
12
|
+
export { buildRegistry, type Registry, type RegisteredCommand, type CatalogEntry, type RegistryOptions, type RuntimeExtension } from './registry.js';
|
|
13
|
+
export { Kernel, createKernel, type KernelOptions, type AuditEntry, type BatchResult, type EmittedEvent } from './kernel.js';
|
|
14
|
+
export { ReadSide, compileNamedSql, type ListParams, type Page } from './read.js';
|
|
15
|
+
export { DataTransfers, importRequestSchema, type TransferTask } from './transfer/index.js';
|
|
16
|
+
export { maskValue, present, canUnmask, auditDiff } from './mask.js';
|
|
17
|
+
export { mergeCaps, mergeScopes, unmaskFrom, loadUserGrants, userCtx, anonymousCtx, systemCtx, type UserGrants, type RoleGrantRow } from './org.js';
|
|
18
|
+
export { step, traceCarrier, withCarrier, consoleLogger, silentLogger, type Logger } from './telemetry.js';
|
|
19
|
+
export { snake, kebab, camel, lowerFirst, upperFirst, hash32, stableStringify } from './util/names.js';
|
|
20
|
+
export { loadScoped, bindReads, scopedFind, tableRef } from './pipeline/load.js';
|
|
21
|
+
export { setSessionVars } from './db/session.js';
|
|
22
|
+
export { urlsFromEnv, type DatabaseUrls } from './db/connect.js';
|
|
23
|
+
export { DEFAULT_TENANT } from './db/ddl.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// @yss123/cage-kernel:声明原语、写入流水线、范围组合子、读侧上下文。不含任何业务名词。
|
|
2
|
+
export * from './brands.js';
|
|
3
|
+
export * from './errors.js';
|
|
4
|
+
export * from './types.js';
|
|
5
|
+
export { Field, text, int, number, money, id, date, datetime, bool, enumOf, ref, hasMany, manyToMany, isRelation, list, json, isField, objectSchema, SENSITIVE_KINDS, } from './primitives/fields.js';
|
|
6
|
+
export { states, isStates, DEFAULT_STATES } from './primitives/states.js';
|
|
7
|
+
export { aggregate, isAggregate, hasStates, hiddenFields, sensitiveFields, RESERVED_FIELDS, } from './primitives/aggregate.js';
|
|
8
|
+
export { command, isCommand, idOnly, empty, } from './primitives/command.js';
|
|
9
|
+
export { event, emit, isEvent, defaultPayload } from './primitives/event.js';
|
|
10
|
+
export { sod, ceiling, stepUp, ok, violation, } from './primitives/constraints.js';
|
|
11
|
+
export { reaction, query, approval, job, unsafe, daysAgo, } from './primitives/flow.js';
|
|
12
|
+
export { scope, compileScope, compileGrant, scopeMatches, grantsFor, DEFAULT_GRANTS } from './scope/index.js';
|
|
13
|
+
export { buildRegistry } from './registry.js';
|
|
14
|
+
export { Kernel, createKernel } from './kernel.js';
|
|
15
|
+
export { ReadSide, compileNamedSql } from './read.js';
|
|
16
|
+
export { DataTransfers, importRequestSchema } from './transfer/index.js';
|
|
17
|
+
export { maskValue, present, canUnmask, auditDiff } from './mask.js';
|
|
18
|
+
export { mergeCaps, mergeScopes, unmaskFrom, loadUserGrants, userCtx, anonymousCtx, systemCtx } from './org.js';
|
|
19
|
+
export { step, traceCarrier, withCarrier, consoleLogger, silentLogger } from './telemetry.js';
|
|
20
|
+
export { snake, kebab, camel, lowerFirst, upperFirst, hash32, stableStringify } from './util/names.js';
|
|
21
|
+
export { loadScoped, bindReads, scopedFind, tableRef } from './pipeline/load.js';
|
|
22
|
+
export { setSessionVars } from './db/session.js';
|
|
23
|
+
export { urlsFromEnv } from './db/connect.js';
|
|
24
|
+
export { DEFAULT_TENANT } from './db/ddl.js';
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,OAAO,EACL,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAC1I,eAAe,GAChB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAkB,MAAM,wBAAwB,CAAC;AAC1F,OAAO,EACL,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,GAElF,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,GAElC,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,EAA4D,MAAM,uBAAuB,CAAC;AACvI,OAAO,EACL,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,GACpC,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,GAEhD,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,cAAc,EAAkB,MAAM,kBAAkB,CAAC;AAC9H,OAAO,EAAE,aAAa,EAAyG,MAAM,eAAe,CAAC;AACrJ,OAAO,EAAE,MAAM,EAAE,YAAY,EAA4E,MAAM,aAAa,CAAC;AAC7H,OAAO,EAAE,QAAQ,EAAE,eAAe,EAA8B,MAAM,WAAW,CAAC;AAClF,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAqB,MAAM,qBAAqB,CAAC;AAC5F,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAsC,MAAM,UAAU,CAAC;AACpJ,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,YAAY,EAAe,MAAM,gBAAgB,CAAC;AAC3G,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvG,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAqB,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/kernel.d.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { Kysely, Transaction } from 'kysely';
|
|
2
|
+
import { type DatabaseUrls } from './db/connect.js';
|
|
3
|
+
import { CageError } from './errors.js';
|
|
4
|
+
import type { AnyAggregate } from './primitives/aggregate.js';
|
|
5
|
+
import type { AnyCommand } from './primitives/command.js';
|
|
6
|
+
import { ReadSide } from './read.js';
|
|
7
|
+
import { type RegisteredCommand, type Registry, type RegistryOptions } from './registry.js';
|
|
8
|
+
import { type Logger } from './telemetry.js';
|
|
9
|
+
import type { CapabilityParams, Ctx } from './types.js';
|
|
10
|
+
import { DataTransfers } from './transfer/index.js';
|
|
11
|
+
export interface KernelOptions {
|
|
12
|
+
/** domain 包的导出(可以是多个模块,例如框架自带的组织模型 + 业务声明) */
|
|
13
|
+
domain: object | object[];
|
|
14
|
+
/** 数据库连接串;或者直接传入已有的连接 */
|
|
15
|
+
urls?: DatabaseUrls;
|
|
16
|
+
db?: {
|
|
17
|
+
app: Kysely<any>;
|
|
18
|
+
read: Kysely<any>;
|
|
19
|
+
};
|
|
20
|
+
registryOptions?: RegistryOptions;
|
|
21
|
+
logger?: Logger;
|
|
22
|
+
/** 同事务反应的最大嵌套深度 */
|
|
23
|
+
maxReactionDepth?: number;
|
|
24
|
+
/** 连接池大小 */
|
|
25
|
+
poolSize?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface EmittedEvent {
|
|
28
|
+
outboxId: number;
|
|
29
|
+
event: string;
|
|
30
|
+
payload: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
export interface BatchResult {
|
|
33
|
+
index: number;
|
|
34
|
+
ok: boolean;
|
|
35
|
+
data?: Record<string, unknown>;
|
|
36
|
+
error?: {
|
|
37
|
+
code: string;
|
|
38
|
+
domainCode?: string;
|
|
39
|
+
detail?: unknown;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export interface AuditEntry {
|
|
43
|
+
tenantId: string;
|
|
44
|
+
actor: string;
|
|
45
|
+
actorKind: string;
|
|
46
|
+
onBehalfOf?: string | null;
|
|
47
|
+
source: string;
|
|
48
|
+
kind: 'write' | 'read' | 'denied' | 'ops';
|
|
49
|
+
capability?: string | null;
|
|
50
|
+
command?: string | null;
|
|
51
|
+
aggregate?: string | null;
|
|
52
|
+
aggregateId?: string | null;
|
|
53
|
+
result: string;
|
|
54
|
+
diff?: unknown;
|
|
55
|
+
requestId?: string | null;
|
|
56
|
+
idempotencyKey?: string | null;
|
|
57
|
+
client?: unknown;
|
|
58
|
+
}
|
|
59
|
+
export declare class Kernel {
|
|
60
|
+
readonly registry: Registry;
|
|
61
|
+
readonly app: Kysely<any>;
|
|
62
|
+
readonly readDb: Kysely<any>;
|
|
63
|
+
readonly read: ReadSide;
|
|
64
|
+
readonly transfers: DataTransfers;
|
|
65
|
+
readonly log: Logger;
|
|
66
|
+
private readonly handles;
|
|
67
|
+
private readonly maxDepth;
|
|
68
|
+
constructor(opts: KernelOptions);
|
|
69
|
+
close(): Promise<void>;
|
|
70
|
+
resolve(ref: string | AnyCommand): RegisteredCommand;
|
|
71
|
+
/** 执行一个命令(自己开事务)。返回按当前主体掩码后的新快照。 */
|
|
72
|
+
execute(ref: string | AnyCommand, raw: unknown, ctx: Ctx): Promise<Record<string, any>>;
|
|
73
|
+
/** 在已有事务里执行(同事务反应、审批桥接、批量整批原子模式用)。返回未掩码的快照。 */
|
|
74
|
+
executeIn(tx: Transaction<any>, ref: string | AnyCommand, raw: unknown, ctx: Ctx, depth?: number): Promise<Record<string, any>>;
|
|
75
|
+
/** 批量写入:每一行都完整走 12 关;默认逐行独立提交,可选整批原子。批量没有快捷路径。 */
|
|
76
|
+
executeBatch(ref: string | AnyCommand, rows: unknown[], ctx: Ctx, opts?: {
|
|
77
|
+
atomic?: boolean;
|
|
78
|
+
}): Promise<BatchResult[]>;
|
|
79
|
+
private run;
|
|
80
|
+
private enqueueEvents;
|
|
81
|
+
writeAudit(db: Transaction<any> | Kysely<any>, e: AuditEntry): Promise<void>;
|
|
82
|
+
/** 被拒绝的尝试异步记录,用于发现探测和配置问题;记录失败不影响请求 */
|
|
83
|
+
auditDenied(rc: RegisteredCommand, raw: unknown, ctx: Ctx, err: CageError): Promise<void>;
|
|
84
|
+
private catalogCache?;
|
|
85
|
+
catalogCaps(): string[];
|
|
86
|
+
capabilitiesOf(userId: string, tenantId: string): Promise<Map<string, CapabilityParams>>;
|
|
87
|
+
/** 把一个用户解析成上下文(HTTP 层会加缓存) */
|
|
88
|
+
contextFor(userId: string, tenantId: string, extra?: Partial<Ctx>): Promise<Ctx | null>;
|
|
89
|
+
/** 审批流程身份:代表最后一位审批人调用结论命令 */
|
|
90
|
+
processCtx(opts: {
|
|
91
|
+
process: string;
|
|
92
|
+
tenantId: string;
|
|
93
|
+
onBehalfOf: string;
|
|
94
|
+
capabilities: string[];
|
|
95
|
+
requestId?: string;
|
|
96
|
+
}): Ctx;
|
|
97
|
+
}
|
|
98
|
+
export declare function createKernel(opts: KernelOptions): Kernel;
|
|
99
|
+
export type { AnyAggregate };
|