@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 shunshunyue and Cage contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @yss123/cage-kernel
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
笼子内核:声明原语、写入流水线、范围组合子、读侧、worker 运行时
|
|
4
|
+
|
|
5
|
+
Cage 是通过 TypeScript 声明业务、由框架统一执行权限和数据规则的中后台框架。此包是框架的一部分。
|
|
6
|
+
|
|
7
|
+
## 使用
|
|
8
|
+
|
|
9
|
+
```powershell
|
|
10
|
+
pnpm add @yss123/cage-kernel
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
首次使用建议通过脚手架创建完整项目,所需依赖由模板统一配置。需要 Node.js 24 或更新版本。
|
|
14
|
+
|
|
15
|
+
从 `0.2.0` 起,支持把 `ref()`、`hasMany()`、`manyToMany()` 统一声明在聚合 `fields` 中。多对多中间表由框架生成,关联通过命令修改、通过安全接口分页读取。详见[对象关系指南](https://github.com/shunshunyue/cage/blob/main/docs/ai/relations.md);脚手架产物也自带这份指南。
|
|
16
|
+
|
|
17
|
+
- [完整使用说明](https://github.com/shunshunyue/cage/blob/main/docs/使用说明.md)
|
|
18
|
+
- [框架源码与范例](https://github.com/shunshunyue/cage)
|
|
19
|
+
- [问题反馈](https://github.com/shunshunyue/cage/issues)
|
|
20
|
+
|
|
21
|
+
许可证:[MIT](https://github.com/shunshunyue/cage/blob/main/LICENSE)。
|
package/dist/brands.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export declare const AGGREGATE: unique symbol;
|
|
2
|
+
export declare const STATES: unique symbol;
|
|
3
|
+
export declare const COMMAND: unique symbol;
|
|
4
|
+
export declare const EVENT: unique symbol;
|
|
5
|
+
export declare const REACTION: unique symbol;
|
|
6
|
+
export declare const QUERY: unique symbol;
|
|
7
|
+
export declare const APPROVAL: unique symbol;
|
|
8
|
+
export declare const JOB: unique symbol;
|
|
9
|
+
export declare const UNSAFE: unique symbol;
|
|
10
|
+
export declare const CONNECTOR: unique symbol;
|
|
11
|
+
export declare const OUTBOUND: unique symbol;
|
|
12
|
+
export declare const INBOUND: unique symbol;
|
|
13
|
+
export declare const ROUTE: unique symbol;
|
|
14
|
+
export declare const CONSUMERS: unique symbol;
|
|
15
|
+
export declare const SYSTEM_DDL: unique symbol;
|
|
16
|
+
export declare const ALL_BRANDS: {
|
|
17
|
+
readonly AGGREGATE: typeof AGGREGATE;
|
|
18
|
+
readonly STATES: typeof STATES;
|
|
19
|
+
readonly COMMAND: typeof COMMAND;
|
|
20
|
+
readonly EVENT: typeof EVENT;
|
|
21
|
+
readonly REACTION: typeof REACTION;
|
|
22
|
+
readonly QUERY: typeof QUERY;
|
|
23
|
+
readonly APPROVAL: typeof APPROVAL;
|
|
24
|
+
readonly JOB: typeof JOB;
|
|
25
|
+
readonly UNSAFE: typeof UNSAFE;
|
|
26
|
+
readonly CONNECTOR: typeof CONNECTOR;
|
|
27
|
+
readonly OUTBOUND: typeof OUTBOUND;
|
|
28
|
+
readonly INBOUND: typeof INBOUND;
|
|
29
|
+
readonly ROUTE: typeof ROUTE;
|
|
30
|
+
};
|
|
31
|
+
/** 判断一个值属于哪种原语;不是原语返回 null。 */
|
|
32
|
+
export declare function brandOf(v: unknown): keyof typeof ALL_BRANDS | null;
|
package/dist/brands.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// 品牌标记:抽取器、注册表和内核靠它们识别原语返回的描述对象。
|
|
2
|
+
// 使用 Symbol.for,即使同一个包被加载两份,标记也相同。
|
|
3
|
+
export const AGGREGATE = Symbol.for('cage.aggregate');
|
|
4
|
+
export const STATES = Symbol.for('cage.states');
|
|
5
|
+
export const COMMAND = Symbol.for('cage.command');
|
|
6
|
+
export const EVENT = Symbol.for('cage.event');
|
|
7
|
+
export const REACTION = Symbol.for('cage.reaction');
|
|
8
|
+
export const QUERY = Symbol.for('cage.query');
|
|
9
|
+
export const APPROVAL = Symbol.for('cage.approval');
|
|
10
|
+
export const JOB = Symbol.for('cage.job');
|
|
11
|
+
export const UNSAFE = Symbol.for('cage.unsafe');
|
|
12
|
+
// 以下原语由 @yss123/cage-connectors、@yss123/cage-approval 实现,标记放在这里统一管理
|
|
13
|
+
export const CONNECTOR = Symbol.for('cage.connector');
|
|
14
|
+
export const OUTBOUND = Symbol.for('cage.outbound');
|
|
15
|
+
export const INBOUND = Symbol.for('cage.inbound');
|
|
16
|
+
export const ROUTE = Symbol.for('cage.route');
|
|
17
|
+
// 运行时扩展点:带这个标记的声明对象可以向 worker 提供事件消费者
|
|
18
|
+
export const CONSUMERS = Symbol.for('cage.consumers');
|
|
19
|
+
// 运行时扩展点:带这个标记的声明对象可以提供框架自己的系统表 DDL
|
|
20
|
+
export const SYSTEM_DDL = Symbol.for('cage.systemDdl');
|
|
21
|
+
export const ALL_BRANDS = {
|
|
22
|
+
AGGREGATE, STATES, COMMAND, EVENT, REACTION, QUERY, APPROVAL, JOB, UNSAFE,
|
|
23
|
+
CONNECTOR, OUTBOUND, INBOUND, ROUTE,
|
|
24
|
+
};
|
|
25
|
+
/** 判断一个值属于哪种原语;不是原语返回 null。 */
|
|
26
|
+
export function brandOf(v) {
|
|
27
|
+
if (!v || (typeof v !== 'object' && typeof v !== 'function'))
|
|
28
|
+
return null;
|
|
29
|
+
for (const [k, s] of Object.entries(ALL_BRANDS)) {
|
|
30
|
+
if (v[s])
|
|
31
|
+
return k;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=brands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brands.js","sourceRoot":"","sources":["../src/brands.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,mCAAmC;AAEnC,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;AACtD,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;AAChD,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;AAClD,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;AAC9C,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;AACpD,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;AAC9C,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;AACpD,MAAM,CAAC,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;AAC1C,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;AAChD,oEAAoE;AACpE,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;AACtD,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;AACpD,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;AAClD,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;AAE9C,sCAAsC;AACtC,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;AACtD,oCAAoC;AACpC,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;AAEvD,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM;IACzE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK;CAC3B,CAAC;AAEX,+BAA+B;AAC/B,MAAM,UAAU,OAAO,CAAC,CAAU;IAChC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1E,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QAChD,IAAK,CAAS,CAAC,CAAC,CAAC;YAAE,OAAO,CAA4B,CAAC;IACzD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Kysely } from 'kysely';
|
|
2
|
+
import pg from 'pg';
|
|
3
|
+
export interface DbHandle {
|
|
4
|
+
db: Kysely<any>;
|
|
5
|
+
pool: pg.Pool;
|
|
6
|
+
close(): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export declare function connect(url: string, opts?: {
|
|
9
|
+
max?: number;
|
|
10
|
+
onConnect?: (c: pg.PoolClient) => Promise<void>;
|
|
11
|
+
applicationName?: string;
|
|
12
|
+
}): DbHandle;
|
|
13
|
+
/** 单独的长连接(LISTEN、咨询锁用),不进连接池 */
|
|
14
|
+
export declare function dedicatedClient(url: string, applicationName?: string): Promise<pg.Client>;
|
|
15
|
+
export interface DatabaseUrls {
|
|
16
|
+
/** 内核账号 cage_app */
|
|
17
|
+
app: string;
|
|
18
|
+
/** 读侧账号 cage_read */
|
|
19
|
+
read: string;
|
|
20
|
+
/** 迁移账号 cage_migrate(只在 migrate 时使用) */
|
|
21
|
+
migrate?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 从环境变量读取连接串:
|
|
25
|
+
* CAGE_APP_DATABASE_URL / CAGE_READ_DATABASE_URL / CAGE_MIGRATE_DATABASE_URL
|
|
26
|
+
* 或只给一个 DATABASE_URL(主机、端口、库名),各账号用默认用户名和 CAGE_DB_PASSWORD。
|
|
27
|
+
*/
|
|
28
|
+
export declare function urlsFromEnv(env?: NodeJS.ProcessEnv): DatabaseUrls;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// 数据库连接:内核账号(写)、读侧账号(只能看安全视图)、迁移账号(DDL)。
|
|
2
|
+
import { Kysely, PostgresDialect } from 'kysely';
|
|
3
|
+
import pg from 'pg';
|
|
4
|
+
// 类型解析:bigint 转数字(计数用),date 保持字符串(避免时区换算),numeric 保持字符串(金额)
|
|
5
|
+
const PARSERS = {
|
|
6
|
+
20: (v) => Number(v), // int8
|
|
7
|
+
1082: (v) => v, // date
|
|
8
|
+
};
|
|
9
|
+
const types = {
|
|
10
|
+
getTypeParser(oid, format) {
|
|
11
|
+
const p = PARSERS[oid];
|
|
12
|
+
return p ?? pg.types.getTypeParser(oid, format);
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
export function connect(url, opts = {}) {
|
|
16
|
+
const pool = new pg.Pool({ connectionString: url, max: opts.max ?? 10, types: types, application_name: opts.applicationName ?? 'cage' });
|
|
17
|
+
if (opts.onConnect)
|
|
18
|
+
pool.on('connect', (c) => void opts.onConnect(c));
|
|
19
|
+
const db = new Kysely({ dialect: new PostgresDialect({ pool }) });
|
|
20
|
+
return {
|
|
21
|
+
db,
|
|
22
|
+
pool,
|
|
23
|
+
close: async () => {
|
|
24
|
+
await db.destroy();
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** 单独的长连接(LISTEN、咨询锁用),不进连接池 */
|
|
29
|
+
export async function dedicatedClient(url, applicationName = 'cage-listener') {
|
|
30
|
+
const c = new pg.Client({ connectionString: url, types: types, application_name: applicationName });
|
|
31
|
+
await c.connect();
|
|
32
|
+
return c;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 从环境变量读取连接串:
|
|
36
|
+
* CAGE_APP_DATABASE_URL / CAGE_READ_DATABASE_URL / CAGE_MIGRATE_DATABASE_URL
|
|
37
|
+
* 或只给一个 DATABASE_URL(主机、端口、库名),各账号用默认用户名和 CAGE_DB_PASSWORD。
|
|
38
|
+
*/
|
|
39
|
+
export function urlsFromEnv(env = process.env) {
|
|
40
|
+
const base = env.DATABASE_URL;
|
|
41
|
+
const derive = (user, pwVar) => {
|
|
42
|
+
if (!base)
|
|
43
|
+
return undefined;
|
|
44
|
+
const u = new URL(base);
|
|
45
|
+
u.username = user;
|
|
46
|
+
u.password = env[pwVar] ?? env.CAGE_DB_PASSWORD ?? user;
|
|
47
|
+
return u.toString();
|
|
48
|
+
};
|
|
49
|
+
const app = env.CAGE_APP_DATABASE_URL ?? derive('cage_app', 'CAGE_APP_PASSWORD');
|
|
50
|
+
const read = env.CAGE_READ_DATABASE_URL ?? derive('cage_read', 'CAGE_READ_PASSWORD');
|
|
51
|
+
const migrate = env.CAGE_MIGRATE_DATABASE_URL ?? derive('cage_migrate', 'CAGE_MIGRATE_PASSWORD');
|
|
52
|
+
if (!app || !read) {
|
|
53
|
+
throw new Error('[CAGE-ENV-001] 缺少数据库连接:设置 DATABASE_URL,或 CAGE_APP_DATABASE_URL 与 CAGE_READ_DATABASE_URL');
|
|
54
|
+
}
|
|
55
|
+
return { app, read, migrate };
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=connect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connect.js","sourceRoot":"","sources":["../../src/db/connect.ts"],"names":[],"mappings":"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"}
|
package/dist/db/ddl.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { AnyAggregate } from '../primitives/aggregate.js';
|
|
2
|
+
import type { Registry } from '../registry.js';
|
|
3
|
+
export declare const q: (ident: string) => string;
|
|
4
|
+
export declare const qt: (t: {
|
|
5
|
+
schema: string;
|
|
6
|
+
name: string;
|
|
7
|
+
}) => string;
|
|
8
|
+
export interface ColumnModel {
|
|
9
|
+
name: string;
|
|
10
|
+
type: string;
|
|
11
|
+
notNull: boolean;
|
|
12
|
+
/** 外键目标表,例如 sys.dept */
|
|
13
|
+
references?: string;
|
|
14
|
+
unique?: boolean;
|
|
15
|
+
/** 枚举取值(CHECK 约束) */
|
|
16
|
+
values?: string[];
|
|
17
|
+
}
|
|
18
|
+
export interface TableModel {
|
|
19
|
+
schema: string;
|
|
20
|
+
name: string;
|
|
21
|
+
aggregate: string;
|
|
22
|
+
columns: ColumnModel[];
|
|
23
|
+
statusValues: string[];
|
|
24
|
+
/** 索引也表达租户复合唯一键,供自动关系表的复合外键引用。 */
|
|
25
|
+
indexes: {
|
|
26
|
+
name: string;
|
|
27
|
+
columns: string[];
|
|
28
|
+
unique?: boolean;
|
|
29
|
+
}[];
|
|
30
|
+
/** 物理关联表不是聚合:没有状态、版本、独立命令和能力点。 */
|
|
31
|
+
relation?: {
|
|
32
|
+
field: string;
|
|
33
|
+
source: string;
|
|
34
|
+
target: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export interface SchemaModel {
|
|
38
|
+
version: 1;
|
|
39
|
+
tables: TableModel[];
|
|
40
|
+
}
|
|
41
|
+
export declare function tableModel(agg: AnyAggregate, reg: Registry): TableModel;
|
|
42
|
+
/** 按外键依赖排序:被引用的表在前 */
|
|
43
|
+
export declare function schemaModel(reg: Registry): SchemaModel;
|
|
44
|
+
export declare function createTableSql(t: TableModel): string;
|
|
45
|
+
/**
|
|
46
|
+
* 比较两份结构,生成迁移草稿语句。破坏性变更(删表、删列)只生成注释,按「先扩展再收缩」由人决定。
|
|
47
|
+
*/
|
|
48
|
+
export declare function diffSchemas(prev: SchemaModel | null, next: SchemaModel): string[];
|
|
49
|
+
export declare const IN_SCOPE_SQL = "\n-- \u751F\u6210\u7269\uFF1A\u8303\u56F4\u5224\u65AD\u3002\u8BFB\u53D6\u4F1A\u8BDD\u53D8\u91CF app.scopes\uFF08{\"\u805A\u5408\":[{\"c\":\"\u7EC4\u5408\u5B50\",\"deptIds\":[...]}]}\uFF09\uFF0C\u591A\u4E2A\u6388\u6743\u53D6\u5E76\u96C6\u3002\n-- SECURITY DEFINER\uFF1A\u4EE5\u8868\u5C5E\u4E3B\u8EAB\u4EFD\u8BFB\u90E8\u95E8\u8868\uFF0C\u907F\u514D\u90E8\u95E8\u8868\u81EA\u5DF1\u7684\u884C\u7EA7\u7B56\u7565\u9012\u5F52\u3002\nCREATE OR REPLACE FUNCTION sys.in_scope(agg text, dept uuid, owner uuid)\nRETURNS boolean LANGUAGE sql STABLE SECURITY DEFINER SET search_path = pg_catalog, sys AS $$\n SELECT coalesce(bool_or(\n CASE g->>'c'\n WHEN 'all' THEN true\n WHEN 'self' THEN owner IS NOT NULL AND owner = nullif(current_setting('app.user_id', true), '')::uuid\n WHEN 'dept' THEN dept IS NOT NULL AND dept = nullif(current_setting('app.dept_id', true), '')::uuid\n WHEN 'deptSubtree' THEN dept IN (\n SELECT d.id FROM sys.dept d\n WHERE d.ancestors LIKE nullif(current_setting('app.dept_path', true), '') || '%')\n WHEN 'deptSet' THEN dept = ANY (ARRAY(SELECT jsonb_array_elements_text(coalesce(g->'deptIds', '[]'::jsonb)))::uuid[])\n ELSE false\n END), false)\n FROM jsonb_array_elements(coalesce(\n nullif(current_setting('app.scopes', true), '')::jsonb -> agg,\n nullif(current_setting('app.scopes', true), '')::jsonb -> '*',\n '[{\"c\":\"self\"}]'::jsonb)) AS g\n$$;";
|
|
50
|
+
export declare const MASK_SQL = "\n-- \u751F\u6210\u7269\uFF1A\u6309\u654F\u611F\u79CD\u7C7B\u63A9\u7801\u3002\u6301\u6709\u300C\u805A\u5408.\u79CD\u7C7B\u300D\u660E\u6587\u6743\u9650\u65F6\u539F\u6837\u8FD4\u56DE\u3002\nCREATE OR REPLACE FUNCTION sys.mask(agg text, kind text, val text)\nRETURNS text LANGUAGE sql STABLE AS $$\n SELECT CASE\n WHEN val IS NULL THEN NULL\n WHEN (agg || '.' || kind) = ANY (string_to_array(coalesce(current_setting('app.unmask', true), ''), ','))\n OR ('*.' || kind) = ANY (string_to_array(coalesce(current_setting('app.unmask', true), ''), ',')) THEN val\n WHEN kind = 'phone' THEN CASE WHEN length(val) >= 7 THEN left(val, 3) || '****' || right(val, 4) ELSE '****' END\n WHEN kind = 'idCard' THEN CASE WHEN length(val) >= 8 THEN left(val, 4) || '**********' || right(val, 4) ELSE '****' END\n WHEN kind = 'bankCard' THEN '**** **** **** ' || right(val, 4)\n WHEN kind = 'email' THEN CASE WHEN strpos(val, '@') > 1 THEN left(val, 1) || '***' || substr(val, strpos(val, '@')) ELSE '******' END\n WHEN kind = 'name' THEN left(val, 1) || '**'\n WHEN kind = 'address' THEN left(val, 6) || '******'\n ELSE '******'\n END\n$$;";
|
|
51
|
+
export declare function aggregateSafeSql(agg: AnyAggregate): string;
|
|
52
|
+
export declare const SYSTEM_SAFE_SQL = "\n-- \u5BA1\u8BA1\u67E5\u8BE2\u89C6\u56FE\uFF1A\u8BFB\u4FA7\u8D26\u53F7\u7ECF\u7CFB\u7EDF\u7BA1\u7406\u63A5\u53E3\u4F7F\u7528\uFF0C\u6309\u79DF\u6237\u8FC7\u6EE4\nDROP VIEW IF EXISTS safe.audit_log;\nCREATE VIEW safe.audit_log WITH (security_barrier) AS\n SELECT id, at, actor, actor_kind, on_behalf_of, source, kind, capability, command, aggregate, aggregate_id,\n result, diff, request_id, idempotency_key, client\n FROM sys.audit_log\n WHERE tenant_id = nullif(current_setting('app.tenant_id', true), '')::uuid;\nALTER VIEW safe.audit_log OWNER TO cage_view_owner;\nGRANT SELECT ON safe.audit_log TO cage_read;";
|
|
53
|
+
/** 整个安全层:先删掉旧的 safe 视图,再按声明重建(幂等) */
|
|
54
|
+
export declare function safeLayerSql(reg: Registry): string;
|
|
55
|
+
export interface BootstrapPasswords {
|
|
56
|
+
app: string;
|
|
57
|
+
read: string;
|
|
58
|
+
migrate: string;
|
|
59
|
+
}
|
|
60
|
+
/** 建库时执行一次(超级用户):模式、账号、默认权限 */
|
|
61
|
+
export declare function bootstrapSql(pw: BootstrapPasswords, dbName?: string): string;
|
|
62
|
+
/** 单租户部署使用的默认租户(启用多租户时每个租户一行 sys.tenant) */
|
|
63
|
+
export declare const DEFAULT_TENANT = "00000000-0000-4000-8000-000000000001";
|
|
64
|
+
/** 框架系统表(框架自己的迁移流,按名字记入 sys.schema_migrations) */
|
|
65
|
+
export declare const SYSTEM_MIGRATIONS: {
|
|
66
|
+
name: string;
|
|
67
|
+
sql: string;
|
|
68
|
+
}[];
|
|
69
|
+
export declare const SCHEMA_MIGRATIONS_SQL = "\nCREATE TABLE IF NOT EXISTS sys.schema_migrations (\n name text PRIMARY KEY,\n checksum text,\n applied_at timestamptz NOT NULL DEFAULT now()\n);";
|