@yss123/cage-kernel 0.1.0 → 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/README.md +2 -0
- package/dist/db/ddl.d.ts +8 -1
- package/dist/db/ddl.js +75 -7
- package/dist/db/ddl.js.map +1 -1
- package/dist/db/rows.js +3 -0
- package/dist/db/rows.js.map +1 -1
- package/dist/errors.js +2 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/kernel.d.ts +2 -0
- package/dist/kernel.js +18 -2
- package/dist/kernel.js.map +1 -1
- package/dist/mask.d.ts +1 -1
- package/dist/mask.js +5 -0
- package/dist/mask.js.map +1 -1
- package/dist/pipeline/load.js +9 -6
- package/dist/pipeline/load.js.map +1 -1
- package/dist/primitives/aggregate.js +3 -1
- package/dist/primitives/aggregate.js.map +1 -1
- package/dist/primitives/event.js +3 -1
- package/dist/primitives/event.js.map +1 -1
- package/dist/primitives/fields.d.ts +43 -23
- package/dist/primitives/fields.js +30 -1
- package/dist/primitives/fields.js.map +1 -1
- package/dist/read.d.ts +8 -2
- package/dist/read.js +66 -19
- package/dist/read.js.map +1 -1
- package/dist/registry.js +26 -0
- package/dist/registry.js.map +1 -1
- package/dist/relations.d.ts +14 -0
- package/dist/relations.js +58 -0
- package/dist/relations.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/worker/index.js +3 -1
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
package/dist/mask.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mask.js","sourceRoot":"","sources":["../src/mask.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,OAAO,EAAqB,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"mask.js","sourceRoot":"","sources":["../src/mask.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,OAAO,EAAqB,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC7F,OAAO,EAAE,UAAU,EAAmC,MAAM,wBAAwB,CAAC;AAGrF,MAAM,UAAU,SAAS,CAAC,IAA4B,EAAE,GAAY;IAClE,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,GAAG,CAAC;IAClD,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACtB,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,OAAO;YACV,OAAO,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;QACvE,KAAK,QAAQ;YACX,OAAO,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;QAC7E,KAAK,UAAU;YACb,OAAO,kBAAkB,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACzC,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAC1B,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;QACjE,CAAC;QACD,KAAK,MAAM;YACT,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC;QAC9B,KAAK,SAAS;YACZ,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC;QAClC;YACE,OAAO,QAAQ,CAAC;IACpB,CAAC;AACH,CAAC;AAED,oBAAoB;AACpB,MAAM,UAAU,SAAS,CAAC,GAAe,EAAE,OAAe,EAAE,IAAY;IACtE,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,OAAO,IAAI,IAAI,EAAE,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC;AAClG,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,OAAO,CAAC,GAAiB,EAAE,IAAyB,EAAE,GAAe;IACnF,MAAM,GAAG,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;IACxB,2CAA2C;IAC3C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,MAAgB,CAAC;QAAE,IAAI,UAAU,CAAC,KAAK,CAAC;YAAE,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;IACxG,KAAK,MAAM,CAAC,IAAI,YAAY,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC;IACjD,KAAK,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QAC7D,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC;YAAE,GAAG,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACpF,CAAC;IACD,OAAO,GAAG,CAAC,QAAQ,CAAC;IACpB,OAAO,GAAG,CAAC;AACb,CAAC;AAED,kCAAkC;AAClC,MAAM,UAAU,SAAS,CAAC,GAAiB,EAAE,GAA+B,EAAE,IAAyB,EAAE,OAAiB;IACxH,MAAM,IAAI,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAmD,EAAE,CAAC;IAChE,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAClB,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC;YAC/C,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACrB,IAAI,CAAC,CAAC,CAAC,GAAG;YACR,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI;YACzD,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI;SACtD,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;QAAE,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,IAAI,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;IACrG,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/pipeline/load.js
CHANGED
|
@@ -4,6 +4,8 @@ import { cageError } from '../errors.js';
|
|
|
4
4
|
import { rowToSnapshot } from '../db/rows.js';
|
|
5
5
|
import { compileScope } from '../scope/index.js';
|
|
6
6
|
import { snake } from '../util/names.js';
|
|
7
|
+
import { loadRelationIds } from '../relations.js';
|
|
8
|
+
import { isRelation } from '../primitives/fields.js';
|
|
7
9
|
export const tableRef = (agg, alias = 't') => `${agg.table.schema}.${agg.table.name} as ${alias}`;
|
|
8
10
|
export async function loadScoped(tx, agg, id, ctx, forUpdate) {
|
|
9
11
|
let q = tx
|
|
@@ -14,7 +16,7 @@ export async function loadScoped(tx, agg, id, ctx, forUpdate) {
|
|
|
14
16
|
if (forUpdate)
|
|
15
17
|
q = q.forUpdate();
|
|
16
18
|
const row = await q.executeTakeFirst();
|
|
17
|
-
return row ? rowToSnapshot(agg, row) : null;
|
|
19
|
+
return row ? (await loadRelationIds(tx, agg, [rowToSnapshot(agg, row)], ctx))[0] : null;
|
|
18
20
|
}
|
|
19
21
|
const SYSTEM_KEYS = {
|
|
20
22
|
id: 'id', status: 'status', version: 'version', createdAt: 'created_at', updatedAt: 'updated_at',
|
|
@@ -29,8 +31,8 @@ export function whereColumns(agg, where) {
|
|
|
29
31
|
if (!col)
|
|
30
32
|
throw cageError('INVALID_INPUT', { field: k, message: `${agg.name} 没有字段 ${k}` });
|
|
31
33
|
const f = agg.fields[k];
|
|
32
|
-
if (f
|
|
33
|
-
throw cageError('INVALID_INPUT', { field: k, message: '
|
|
34
|
+
if (f && (f.sql === 'jsonb' || isRelation(f)))
|
|
35
|
+
throw cageError('INVALID_INPUT', { field: k, message: '明细或集合关系不能作为主表查询条件' });
|
|
34
36
|
out.push([col, v]);
|
|
35
37
|
}
|
|
36
38
|
return out;
|
|
@@ -42,7 +44,8 @@ export function scopedFind(tx, agg, ctx, where, opts = {}) {
|
|
|
42
44
|
}
|
|
43
45
|
if (opts.orderBy) {
|
|
44
46
|
const col = SYSTEM_KEYS[opts.orderBy] ?? (opts.orderBy in agg.fields ? snake(opts.orderBy) : undefined);
|
|
45
|
-
|
|
47
|
+
const field = agg.fields[opts.orderBy];
|
|
48
|
+
if (!col || (field && isRelation(field)))
|
|
46
49
|
throw cageError('INVALID_INPUT', { field: opts.orderBy });
|
|
47
50
|
q = q.orderBy(sql.ref(`t.${col}`), opts.desc ? 'desc' : 'asc');
|
|
48
51
|
}
|
|
@@ -57,10 +60,10 @@ export function bindReads(tx, reads, ctx) {
|
|
|
57
60
|
for (const [alias, agg] of Object.entries(reads)) {
|
|
58
61
|
deps[alias] = Object.freeze({
|
|
59
62
|
get: async (id) => (typeof id === 'string' && /^[0-9a-f-]{36}$/i.test(id) ? loadScoped(tx, agg, id, ctx, false) : null),
|
|
60
|
-
find: async (where, opts) => (await scopedFind(tx, agg, ctx, where, opts).execute()).map((r) => rowToSnapshot(agg, r)),
|
|
63
|
+
find: async (where, opts) => loadRelationIds(tx, agg, (await scopedFind(tx, agg, ctx, where, opts).execute()).map((r) => rowToSnapshot(agg, r)), ctx),
|
|
61
64
|
findOne: async (where) => {
|
|
62
65
|
const rows = await scopedFind(tx, agg, ctx, where, { limit: 1 }).execute();
|
|
63
|
-
return rows[0] ? rowToSnapshot(agg, rows[0]) : null;
|
|
66
|
+
return rows[0] ? (await loadRelationIds(tx, agg, [rowToSnapshot(agg, rows[0])], ctx))[0] : null;
|
|
64
67
|
},
|
|
65
68
|
count: async (where) => {
|
|
66
69
|
let q = tx.selectFrom(tableRef(agg)).select(sql `count(*)::int`.as('n')).where(compileScope(agg.scope, agg.name, ctx, 't'));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load.js","sourceRoot":"","sources":["../../src/pipeline/load.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,OAAO,EAAE,GAAG,EAAiC,MAAM,QAAQ,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGzC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"load.js","sourceRoot":"","sources":["../../src/pipeline/load.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AGGREGATE } from '../brands.js';
|
|
2
2
|
import { declError } from '../errors.js';
|
|
3
3
|
import { snake } from '../util/names.js';
|
|
4
|
-
import { isField, objectSchema } from './fields.js';
|
|
4
|
+
import { isField, isRelation, objectSchema } from './fields.js';
|
|
5
5
|
import { DEFAULT_STATES, isStates } from './states.js';
|
|
6
6
|
export const RESERVED_FIELDS = ['id', 'version', 'status', 'createdAt', 'updatedAt', 'tenantId'];
|
|
7
7
|
export function aggregate(name, def) {
|
|
@@ -35,6 +35,8 @@ export function aggregate(name, def) {
|
|
|
35
35
|
for (const f of [sc.deptField, ownerField]) {
|
|
36
36
|
if (f && f !== 'id' && !(f in fields))
|
|
37
37
|
throw declError('CAGE-SCOPE-002', `${name} 的范围字段 ${f} 不存在`);
|
|
38
|
+
if (f && f !== 'id' && isRelation(fields[f]))
|
|
39
|
+
throw declError('CAGE-REL-001', `${name} 的范围字段必须是主表列,不能使用集合关系 ${f}`);
|
|
38
40
|
}
|
|
39
41
|
const scope = Object.freeze({ ...sc, ownerField });
|
|
40
42
|
return Object.freeze({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aggregate.js","sourceRoot":"","sources":["../../src/primitives/aggregate.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAgD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"aggregate.js","sourceRoot":"","sources":["../../src/primitives/aggregate.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAgD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC9G,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAkB,MAAM,aAAa,CAAC;AAavE,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,CAAU,CAAC;AA8C1G,MAAM,UAAU,SAAS,CACvB,IAAO,EACP,GAAyB;IAEzB,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACtC,MAAM,SAAS,CAAC,eAAe,EAAE,OAAO,IAAI,SAAS,CAAC,CAAC;IACzD,CAAC;IACD,IAAI,EAAE,GAA+B,GAAG,CAAC,MAAM,CAAC;IAChD,MAAM,MAAM,GAA6B,EAAE,CAAC;IAC5C,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QAChD,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;YAChB,IAAI,CAAC,KAAK,QAAQ;gBAAE,MAAM,SAAS,CAAC,gBAAgB,EAAE,yBAAyB,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;YAC7F,IAAI,EAAE;gBAAE,MAAM,SAAS,CAAC,gBAAgB,EAAE,GAAG,IAAI,oCAAoC,CAAC,CAAC;YACvF,EAAE,GAAG,CAAC,CAAC;YACP,SAAS;QACX,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;YAAE,MAAM,SAAS,CAAC,gBAAgB,EAAE,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC;QAC1E,IAAK,eAAqC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;YACvD,MAAM,SAAS,CAAC,gBAAgB,EAAE,GAAG,IAAI,IAAI,CAAC,eAAe,CAAC,CAAC;QACjE,CAAC;QACD,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC;YAAE,MAAM,SAAS,CAAC,eAAe,EAAE,OAAO,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC;QAChG,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAChB,CAAC;IACD,MAAM,MAAM,GAAG,EAAE,IAAI,cAAc,CAAC;IAEpC,6BAA6B;IAC7B,MAAM,EAAE,GAAc,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;IACtC,MAAM,UAAU,GAAG,EAAE,CAAC,UAAU,IAAI,CAAC,WAAW,IAAI,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACtF,KAAK,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC,EAAE,CAAC;QAC3C,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC;YAAE,MAAM,SAAS,CAAC,gBAAgB,EAAE,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;QACnG,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YAAE,MAAM,SAAS,CAAC,cAAc,EAAE,GAAG,IAAI,yBAAyB,CAAC,EAAE,CAAC,CAAC;IACrH,CAAC;IACD,MAAM,KAAK,GAAc,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC;IAE9D,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,CAAC,SAAS,CAAC,EAAE,IAAa;QAC1B,IAAI;QACJ,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAQ;QACpC,MAAM;QACN,KAAK;QACL,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC,EAAE,CAAQ;QAC/D,MAAM,EAAE,YAAY,CAAC,MAAM,CAAC;QAC5B,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,KAAK,EAAE,MAAM,IAAI,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,EAAE,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACnG,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,CAAC;KACnD,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,CAAU;IACpC,OAAO,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAK,CAAS,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC;AACxE,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,GAAiB,EAAE,CAA6B;IACxE,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;AAChE,CAAC;AAED,2BAA2B;AAC3B,MAAM,UAAU,YAAY,CAAC,GAAiB;IAC5C,OAAO,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,MAAgB,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;AAC5F,CAAC;AAED,wBAAwB;AACxB,MAAM,UAAU,eAAe,CAAC,GAAiB;IAC/C,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,MAAgB,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,aAAc,CAAC,CAAC,CAC/G,CAAC;AACJ,CAAC"}
|
package/dist/primitives/event.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { EVENT } from '../brands.js';
|
|
2
2
|
import { declError } from '../errors.js';
|
|
3
|
-
import { objectSchema } from './fields.js';
|
|
3
|
+
import { isRelation, objectSchema } from './fields.js';
|
|
4
4
|
export function event(name, fields, opts) {
|
|
5
5
|
if (!/^[A-Z][A-Za-z0-9]*$/.test(name))
|
|
6
6
|
throw declError('CAGE-NAME-003', `事件名 ${name} 必须是大驼峰`);
|
|
7
|
+
if (Object.values(fields).some(isRelation))
|
|
8
|
+
throw declError('CAGE-REL-001', '事件载荷不声明持久化关系;传递 ID 数组请使用 list(id())');
|
|
7
9
|
return Object.freeze({
|
|
8
10
|
[EVENT]: true,
|
|
9
11
|
name,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event.js","sourceRoot":"","sources":["../../src/primitives/event.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAiC,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"event.js","sourceRoot":"","sources":["../../src/primitives/event.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAiC,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAatF,MAAM,UAAU,KAAK,CAA2C,IAAO,EAAE,MAAS,EAAE,IAA+B;IACjH,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,MAAM,SAAS,CAAC,eAAe,EAAE,OAAO,IAAI,SAAS,CAAC,CAAC;IAC9F,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC;QAAE,MAAM,SAAS,CAAC,cAAc,EAAE,qCAAqC,CAAC,CAAC;IACnH,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,CAAC,KAAK,CAAC,EAAE,IAAa;QACtB,IAAI;QACJ,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC;QACpC,MAAM,EAAE,YAAY,CAAC,MAAM,CAAC;QAC5B,WAAW,EAAE,IAAI,EAAE,WAAW;KAC/B,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,CAAU;IAChC,OAAO,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAK,CAAS,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC;AACpE,CAAC;AASD,MAAM,UAAU,IAAI,CAAqB,EAAK,EAAE,GAA+D;IAC7G,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,CAAsB;IAC/C,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AACvC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,EAAY,EAAE,QAAiC,EAAE,KAA8B;IAC5G,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC;QACvC,IAAI,CAAC,IAAI,QAAQ;YAAE,GAAG,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;aACnC,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,GAAG,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC;aAC3C,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK;YAAE,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -2,7 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
/** 敏感种类是封闭集合,每种在内核里有一个掩码函数(见 db/ddl.ts 的 sys.mask)。 */
|
|
3
3
|
export type SensitiveKind = 'phone' | 'idCard' | 'bankCard' | 'email' | 'name' | 'address';
|
|
4
4
|
export declare const SENSITIVE_KINDS: readonly SensitiveKind[];
|
|
5
|
-
export type FieldKind = 'id' | 'ref' | 'text' | 'int' | 'number' | 'money' | 'date' | 'datetime' | 'bool' | 'enum' | 'list';
|
|
5
|
+
export type FieldKind = 'id' | 'ref' | 'text' | 'int' | 'number' | 'money' | 'date' | 'datetime' | 'bool' | 'enum' | 'list' | 'hasMany' | 'manyToMany';
|
|
6
6
|
export interface FieldMeta {
|
|
7
7
|
readonly kind: FieldKind;
|
|
8
8
|
readonly sql: string;
|
|
@@ -10,6 +10,8 @@ export interface FieldMeta {
|
|
|
10
10
|
readonly sensitiveKind?: SensitiveKind;
|
|
11
11
|
/** 引用的聚合名,抽取器据此画引用边,生成外键 */
|
|
12
12
|
readonly ref?: string;
|
|
13
|
+
/** hasMany 的反向外键。关系字段不占主表列,sql 为空字符串。 */
|
|
14
|
+
readonly mappedBy?: string;
|
|
13
15
|
readonly isUnique?: boolean;
|
|
14
16
|
/** 隐藏字段:不进入安全视图、审计、事件、前端契约(例如密码哈希) */
|
|
15
17
|
readonly isHidden?: boolean;
|
|
@@ -26,12 +28,13 @@ export interface FieldMeta {
|
|
|
26
28
|
* 字段定义。方法都返回新对象(不可变),可以链式调用:
|
|
27
29
|
* text(50).sensitive('phone').optional()
|
|
28
30
|
*/
|
|
29
|
-
export declare class Field<T extends z.ZodType = z.ZodType, Opt extends boolean = boolean> implements FieldMeta {
|
|
30
|
-
readonly kind:
|
|
31
|
+
export declare class Field<T extends z.ZodType = z.ZodType, Opt extends boolean = boolean, K extends FieldKind = FieldKind> implements FieldMeta {
|
|
32
|
+
readonly kind: K;
|
|
31
33
|
readonly sql: string;
|
|
32
34
|
readonly isOptional: boolean;
|
|
33
35
|
readonly sensitiveKind?: SensitiveKind;
|
|
34
36
|
readonly ref?: string;
|
|
37
|
+
readonly mappedBy?: string;
|
|
35
38
|
readonly isUnique?: boolean;
|
|
36
39
|
readonly isHidden?: boolean;
|
|
37
40
|
readonly description?: string;
|
|
@@ -42,53 +45,70 @@ export declare class Field<T extends z.ZodType = z.ZodType, Opt extends boolean
|
|
|
42
45
|
readonly base: T;
|
|
43
46
|
/** 仅用于类型推导 */
|
|
44
47
|
readonly __opt?: Opt;
|
|
45
|
-
constructor(base: T, meta: Omit<FieldMeta, 'isOptional'> & {
|
|
48
|
+
constructor(base: T, meta: Omit<FieldMeta, 'kind' | 'isOptional'> & {
|
|
49
|
+
kind: K;
|
|
46
50
|
isOptional?: boolean;
|
|
47
51
|
});
|
|
48
|
-
meta(): FieldMeta
|
|
52
|
+
meta(): FieldMeta & {
|
|
53
|
+
kind: K;
|
|
54
|
+
};
|
|
49
55
|
private with;
|
|
50
56
|
/** 校验规则(前后端共用);可选字段允许 null 与缺省 */
|
|
51
57
|
get zod(): z.ZodType;
|
|
52
58
|
/** 可选字段:列可空,输入可以不填 */
|
|
53
|
-
optional(): Field<T, true>;
|
|
59
|
+
optional(): Field<T, true, K>;
|
|
54
60
|
/** 敏感字段:读侧按种类掩码,持有 聚合.unmask.种类 能力才能看明文 */
|
|
55
|
-
sensitive(kind: SensitiveKind): Field<T, Opt>;
|
|
61
|
+
sensitive(kind: SensitiveKind): Field<T, Opt, K>;
|
|
56
62
|
/** 唯一约束(租户内) */
|
|
57
|
-
unique(): Field<T, Opt>;
|
|
63
|
+
unique(): Field<T, Opt, K>;
|
|
58
64
|
/** 隐藏字段:不出现在读侧、审计、事件和前端契约 */
|
|
59
|
-
hidden(): Field<T, Opt>;
|
|
65
|
+
hidden(): Field<T, Opt, K>;
|
|
60
66
|
/** 口径说明 */
|
|
61
|
-
describe(text: string): Field<T, Opt>;
|
|
67
|
+
describe(text: string): Field<T, Opt, K>;
|
|
62
68
|
}
|
|
63
69
|
export type AnyField = Field<z.ZodType, boolean>;
|
|
64
70
|
export type Fields = Record<string, AnyField>;
|
|
65
71
|
/** 字段的 TypeScript 值类型 */
|
|
66
|
-
export type ValueOf<F> = F extends Field<infer T, infer O> ? ([O] extends [true] ? z.output<T> | null : z.output<T>) : never;
|
|
72
|
+
export type ValueOf<F> = F extends Field<infer T, infer O, any> ? ([O] extends [true] ? z.output<T> | null : z.output<T>) : never;
|
|
67
73
|
export type FieldValues<F extends Fields> = {
|
|
68
|
-
-readonly [K in keyof F]: ValueOf<F[K]>;
|
|
74
|
+
-readonly [K in keyof F as F[K]['kind'] extends 'hasMany' ? never : K]: ValueOf<F[K]>;
|
|
69
75
|
};
|
|
70
76
|
/** 文本,默认最长 500 字 */
|
|
71
|
-
export declare const text: (max?: number) => Field<z.ZodString, false>;
|
|
77
|
+
export declare const text: (max?: number) => Field<z.ZodString, false, "text">;
|
|
72
78
|
/** 整数 */
|
|
73
|
-
export declare const int: () => Field<z.ZodNumber, false>;
|
|
79
|
+
export declare const int: () => Field<z.ZodNumber, false, "int">;
|
|
74
80
|
/** 数值(双精度)。金额请用 money() */
|
|
75
|
-
export declare const number: () => Field<z.ZodNumber, false>;
|
|
81
|
+
export declare const number: () => Field<z.ZodNumber, false, "number">;
|
|
76
82
|
/** 金额:用字符串传输,避免浮点误差 */
|
|
77
|
-
export declare const money: () => Field<z.ZodString, false>;
|
|
83
|
+
export declare const money: () => Field<z.ZodString, false, "money">;
|
|
78
84
|
/** 主键或外键 */
|
|
79
|
-
export declare const id: () => Field<z.ZodString, false>;
|
|
85
|
+
export declare const id: () => Field<z.ZodString, false, "id">;
|
|
80
86
|
/** 日期 YYYY-MM-DD */
|
|
81
|
-
export declare const date: () => Field<z.ZodString, false>;
|
|
87
|
+
export declare const date: () => Field<z.ZodString, false, "date">;
|
|
82
88
|
/** 时间戳(ISO 8601) */
|
|
83
|
-
export declare const datetime: () => Field<z.ZodString, false>;
|
|
89
|
+
export declare const datetime: () => Field<z.ZodString, false, "datetime">;
|
|
84
90
|
/** 布尔 */
|
|
85
|
-
export declare const bool: () => Field<z.ZodBoolean, false>;
|
|
91
|
+
export declare const bool: () => Field<z.ZodBoolean, false, "bool">;
|
|
86
92
|
/** 枚举:enumOf('A', 'B') */
|
|
87
|
-
export declare function enumOf<const V extends readonly [string, ...string[]]>(...values: V): Field<z.ZodEnum<{ [k_1 in V[number]]: k_1; } extends infer T ? { [k in keyof T]: T[k]; } : never>, false>;
|
|
93
|
+
export declare function enumOf<const V extends readonly [string, ...string[]]>(...values: V): Field<z.ZodEnum<{ [k_1 in V[number]]: k_1; } extends infer T ? { [k in keyof T]: T[k]; } : never>, false, "enum">;
|
|
88
94
|
/** 引用另一个聚合:本质是 uuid,额外记下引用目标。可传聚合对象或聚合名(解决循环导入)。 */
|
|
89
95
|
export declare function ref(to: {
|
|
90
96
|
name: string;
|
|
91
|
-
} | string): Field<z.ZodString, false>;
|
|
97
|
+
} | string): Field<z.ZodString, false, "ref">;
|
|
98
|
+
/** 一对多反查:实际外键在目标聚合上,不创建另一份关系或可写数组。 */
|
|
99
|
+
export declare function hasMany(to: {
|
|
100
|
+
name: string;
|
|
101
|
+
} | string, options: {
|
|
102
|
+
mappedBy: string;
|
|
103
|
+
description?: string;
|
|
104
|
+
}): Field<z.ZodArray<z.ZodString>, false, "hasMany">;
|
|
105
|
+
/** 多对多:字段与普通属性一处声明,中间表由框架生成;业务命令返回目标 ID 数组。 */
|
|
106
|
+
export declare function manyToMany(to: {
|
|
107
|
+
name: string;
|
|
108
|
+
} | string, options?: {
|
|
109
|
+
description?: string;
|
|
110
|
+
}): Field<z.ZodArray<z.ZodString>, false, "manyToMany">;
|
|
111
|
+
export declare const isRelation: (field: FieldMeta) => boolean;
|
|
92
112
|
type ShapeZod<S extends Fields> = {
|
|
93
113
|
[K in keyof S]: S[K] extends Field<infer T, infer O> ? ([O] extends [true] ? z.ZodOptional<z.ZodNullable<T>> : T) : never;
|
|
94
114
|
};
|
|
@@ -103,5 +123,5 @@ export declare function isField(v: unknown): v is AnyField;
|
|
|
103
123
|
/** 把一组字段拼成对象 schema */
|
|
104
124
|
export declare function objectSchema(fields: Fields): z.ZodObject<any>;
|
|
105
125
|
/** 结构不固定的 JSON 对象(例如能力点参数),存为 jsonb。业务字段请优先用 list 或具体类型。 */
|
|
106
|
-
export declare const json: () => Field<z.ZodRecord<z.ZodString, z.ZodUnknown>, false>;
|
|
126
|
+
export declare const json: () => Field<z.ZodRecord<z.ZodString, z.ZodUnknown>, false, "list">;
|
|
107
127
|
export {};
|
|
@@ -12,6 +12,7 @@ export class Field {
|
|
|
12
12
|
isOptional;
|
|
13
13
|
sensitiveKind;
|
|
14
14
|
ref;
|
|
15
|
+
mappedBy;
|
|
15
16
|
isUnique;
|
|
16
17
|
isHidden;
|
|
17
18
|
description;
|
|
@@ -27,6 +28,7 @@ export class Field {
|
|
|
27
28
|
this.isOptional = meta.isOptional ?? false;
|
|
28
29
|
this.sensitiveKind = meta.sensitiveKind;
|
|
29
30
|
this.ref = meta.ref;
|
|
31
|
+
this.mappedBy = meta.mappedBy;
|
|
30
32
|
this.isUnique = meta.isUnique;
|
|
31
33
|
this.isHidden = meta.isHidden;
|
|
32
34
|
this.description = meta.description;
|
|
@@ -40,6 +42,7 @@ export class Field {
|
|
|
40
42
|
kind: this.kind, sql: this.sql, isOptional: this.isOptional, sensitiveKind: this.sensitiveKind, ref: this.ref,
|
|
41
43
|
isUnique: this.isUnique, isHidden: this.isHidden, description: this.description, values: this.values,
|
|
42
44
|
shape: this.shape, max: this.max,
|
|
45
|
+
mappedBy: this.mappedBy,
|
|
43
46
|
};
|
|
44
47
|
}
|
|
45
48
|
with(patch) {
|
|
@@ -51,20 +54,28 @@ export class Field {
|
|
|
51
54
|
}
|
|
52
55
|
/** 可选字段:列可空,输入可以不填 */
|
|
53
56
|
optional() {
|
|
57
|
+
if (isRelation(this))
|
|
58
|
+
throw declError('CAGE-REL-001', '集合关系使用空集合表示没有关联,不支持 optional');
|
|
54
59
|
return this.with({ isOptional: true });
|
|
55
60
|
}
|
|
56
61
|
/** 敏感字段:读侧按种类掩码,持有 聚合.unmask.种类 能力才能看明文 */
|
|
57
62
|
sensitive(kind) {
|
|
63
|
+
if (isRelation(this))
|
|
64
|
+
throw declError('CAGE-REL-001', '关系字段不支持 sensitive;目标字段按目标聚合权限掩码');
|
|
58
65
|
if (!SENSITIVE_KINDS.includes(kind))
|
|
59
66
|
throw declError('CAGE-FIELD-001', `未知的敏感种类 ${kind}`);
|
|
60
67
|
return this.with({ sensitiveKind: kind });
|
|
61
68
|
}
|
|
62
69
|
/** 唯一约束(租户内) */
|
|
63
70
|
unique() {
|
|
71
|
+
if (isRelation(this))
|
|
72
|
+
throw declError('CAGE-REL-001', '集合关系自动保证关联对唯一,不支持单字段 unique');
|
|
64
73
|
return this.with({ isUnique: true });
|
|
65
74
|
}
|
|
66
75
|
/** 隐藏字段:不出现在读侧、审计、事件和前端契约 */
|
|
67
76
|
hidden() {
|
|
77
|
+
if (isRelation(this))
|
|
78
|
+
throw declError('CAGE-REL-001', '关系使用所属对象和目标对象权限,不支持 hidden 修饰');
|
|
68
79
|
return this.with({ isHidden: true });
|
|
69
80
|
}
|
|
70
81
|
/** 口径说明 */
|
|
@@ -103,7 +114,25 @@ export function ref(to) {
|
|
|
103
114
|
throw declError('CAGE-FIELD-002', 'ref() 的目标为空,可能是循环导入;可改为 ref("聚合名")');
|
|
104
115
|
return mk(z.string().uuid(), { kind: 'ref', sql: 'uuid', ref: target });
|
|
105
116
|
}
|
|
117
|
+
/** 一对多反查:实际外键在目标聚合上,不创建另一份关系或可写数组。 */
|
|
118
|
+
export function hasMany(to, options) {
|
|
119
|
+
const target = typeof to === 'string' ? to : to?.name;
|
|
120
|
+
if (!target)
|
|
121
|
+
throw declError('CAGE-REL-001', 'hasMany() 需要目标聚合;循环引用请使用聚合名字符串');
|
|
122
|
+
return mk(z.array(z.string().uuid()), { kind: 'hasMany', sql: '', ref: target, ...options });
|
|
123
|
+
}
|
|
124
|
+
/** 多对多:字段与普通属性一处声明,中间表由框架生成;业务命令返回目标 ID 数组。 */
|
|
125
|
+
export function manyToMany(to, options = {}) {
|
|
126
|
+
const target = typeof to === 'string' ? to : to?.name;
|
|
127
|
+
if (!target)
|
|
128
|
+
throw declError('CAGE-REL-001', 'manyToMany() 需要目标聚合;循环引用请使用聚合名字符串');
|
|
129
|
+
return mk(z.array(z.string().uuid()), { kind: 'manyToMany', sql: '', ref: target, ...options });
|
|
130
|
+
}
|
|
131
|
+
export const isRelation = (field) => field.kind === 'hasMany' || field.kind === 'manyToMany';
|
|
106
132
|
export function list(arg) {
|
|
133
|
+
const members = isField(arg) ? [arg] : Object.values(arg);
|
|
134
|
+
if (members.some(isRelation))
|
|
135
|
+
throw declError('CAGE-REL-001', '关系字段只能直接声明在聚合 fields 中,不能嵌入 JSON 明细');
|
|
107
136
|
if (isField(arg)) {
|
|
108
137
|
return mk(z.array(arg.zod), { kind: 'list', sql: 'jsonb', shape: { item: arg } });
|
|
109
138
|
}
|
|
@@ -115,7 +144,7 @@ export function isField(v) {
|
|
|
115
144
|
}
|
|
116
145
|
/** 把一组字段拼成对象 schema */
|
|
117
146
|
export function objectSchema(fields) {
|
|
118
|
-
return z.object(Object.fromEntries(Object.entries(fields).map(([k, f]) => [k, f.zod])));
|
|
147
|
+
return z.object(Object.fromEntries(Object.entries(fields).filter(([, f]) => f.kind !== 'hasMany').map(([k, f]) => [k, f.zod])));
|
|
119
148
|
}
|
|
120
149
|
/** 结构不固定的 JSON 对象(例如能力点参数),存为 jsonb。业务字段请优先用 list 或具体类型。 */
|
|
121
150
|
export const json = () => mk(z.record(z.string(), z.unknown()), { kind: 'list', sql: 'jsonb' });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fields.js","sourceRoot":"","sources":["../../src/primitives/fields.ts"],"names":[],"mappings":"AAAA,mDAAmD;AACnD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAIzC,MAAM,CAAC,MAAM,eAAe,GAA6B,CAAC,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"fields.js","sourceRoot":"","sources":["../../src/primitives/fields.ts"],"names":[],"mappings":"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"}
|
package/dist/read.d.ts
CHANGED
|
@@ -24,15 +24,21 @@ export declare class ReadSide {
|
|
|
24
24
|
private readonly k;
|
|
25
25
|
constructor(k: Kernel);
|
|
26
26
|
/** 读侧账号上的只读事务,已写入会话变量 */
|
|
27
|
-
tx<T>(ctx: Ctx, fn: (tx: Transaction<any>) => Promise<T
|
|
27
|
+
tx<T>(ctx: Ctx, fn: (tx: Transaction<any>) => Promise<T>, isolation?: 'read committed' | 'repeatable read'): Promise<T>;
|
|
28
28
|
private agg;
|
|
29
29
|
private need;
|
|
30
30
|
/** 明文读取敏感字段要写读审计 */
|
|
31
31
|
private auditUnmask;
|
|
32
32
|
get(a: AnyAggregate | string, id: string, ctx: Ctx): Promise<Record<string, any>>;
|
|
33
33
|
list(a: AnyAggregate | string, params: ListParams, ctx: Ctx): Promise<Page<Record<string, any>>>;
|
|
34
|
+
/** 关联按需分页:先确认所属对象可见,再从目标安全视图读取;两侧能力、范围和掩码都生效。 */
|
|
35
|
+
related(a: AnyAggregate | string, id: string, fieldName: string, params: ListParams, ctx: Ctx): Promise<Page<Record<string, any>>>;
|
|
34
36
|
private listIn;
|
|
35
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* worker 使用的导出构建器:同一只读快照内分批读取,避免翻页期间数据变化导致漏行或重复。
|
|
39
|
+
* 上限用于约束单任务内存与数据库文件存储;超限明确失败,绝不能静默截断成“成功文件”。
|
|
40
|
+
* HTTP 只提交任务,保留本方法供受控的服务端调用。
|
|
41
|
+
*/
|
|
36
42
|
export(a: AnyAggregate | string, params: ListParams, ctx: Ctx): Promise<{
|
|
37
43
|
filename: string;
|
|
38
44
|
contentType: string;
|
package/dist/read.js
CHANGED
|
@@ -6,7 +6,10 @@ import { viewRowToObject } from './db/rows.js';
|
|
|
6
6
|
import { setSessionVars } from './db/session.js';
|
|
7
7
|
import { cageError } from './errors.js';
|
|
8
8
|
import { hiddenFields, sensitiveFields } from './primitives/aggregate.js';
|
|
9
|
+
import { isRelation } from './primitives/fields.js';
|
|
9
10
|
import { snake } from './util/names.js';
|
|
11
|
+
import { csvCell } from './transfer/csv.js';
|
|
12
|
+
import { relationTable } from './relations.js';
|
|
10
13
|
const SYS = { id: 'id', status: 'status', version: 'version', createdAt: 'created_at', updatedAt: 'updated_at' };
|
|
11
14
|
const RESERVED = new Set(['page', 'pageSize', 'sort', 'createdAfter', 'createdBefore']);
|
|
12
15
|
function coerce(kind, v) {
|
|
@@ -24,9 +27,10 @@ export class ReadSide {
|
|
|
24
27
|
this.k = k;
|
|
25
28
|
}
|
|
26
29
|
/** 读侧账号上的只读事务,已写入会话变量 */
|
|
27
|
-
async tx(ctx, fn) {
|
|
30
|
+
async tx(ctx, fn, isolation = 'read committed') {
|
|
28
31
|
return this.k.readDb
|
|
29
32
|
.transaction()
|
|
33
|
+
.setIsolationLevel(isolation)
|
|
30
34
|
.setAccessMode('read only')
|
|
31
35
|
.execute(async (tx) => {
|
|
32
36
|
await setSessionVars(tx, ctx);
|
|
@@ -56,7 +60,12 @@ export class ReadSide {
|
|
|
56
60
|
capability: `${agg.name}.${kind}`, command: null, aggregate: agg.name,
|
|
57
61
|
aggregateId: ids.length === 1 ? ids[0] : null, result: 'ok',
|
|
58
62
|
diff: { unmasked: kinds, rows: ids.length }, requestId: ctx.requestId ?? null, client: ctx.client ?? null,
|
|
59
|
-
}).catch((e) =>
|
|
63
|
+
}).catch((e) => {
|
|
64
|
+
// 导出会产生可离线保存的文件,审计失败不能交付文件;交给任务系统重试。
|
|
65
|
+
if (kind === 'export')
|
|
66
|
+
throw e;
|
|
67
|
+
this.k.log.warn({ err: String(e) }, '读审计写入失败');
|
|
68
|
+
});
|
|
60
69
|
}
|
|
61
70
|
async get(a, id, ctx) {
|
|
62
71
|
const agg = this.agg(a);
|
|
@@ -76,7 +85,28 @@ export class ReadSide {
|
|
|
76
85
|
void this.auditUnmask(agg, ctx, res.items.map((r) => r.id), 'read');
|
|
77
86
|
return res;
|
|
78
87
|
}
|
|
79
|
-
|
|
88
|
+
/** 关联按需分页:先确认所属对象可见,再从目标安全视图读取;两侧能力、范围和掩码都生效。 */
|
|
89
|
+
async related(a, id, fieldName, params, ctx) {
|
|
90
|
+
const owner = this.agg(a), field = owner.fields[fieldName];
|
|
91
|
+
this.need(ctx, `${owner.name}.read`);
|
|
92
|
+
if (!field || !isRelation(field) || !/^[0-9a-f-]{36}$/i.test(id))
|
|
93
|
+
throw cageError('NOT_FOUND');
|
|
94
|
+
const target = this.agg(field.ref);
|
|
95
|
+
this.need(ctx, `${target.name}.read`);
|
|
96
|
+
const res = await this.tx(ctx, async (tx) => {
|
|
97
|
+
const source = await tx.selectFrom(`safe.${owner.table.name}`).select('id').where('id', '=', id).where('status', '<>', 'deleted').executeTakeFirst();
|
|
98
|
+
if (!source)
|
|
99
|
+
throw cageError('NOT_FOUND');
|
|
100
|
+
const table = relationTable(owner, fieldName);
|
|
101
|
+
const restrict = field.kind === 'hasMany'
|
|
102
|
+
? (q) => q.where(sql.ref(snake(field.mappedBy)), '=', id)
|
|
103
|
+
: (q) => q.where('id', 'in', tx.selectFrom(`safe.${table.name}`).select('target_id').where('source_id', '=', id));
|
|
104
|
+
return this.listIn(tx, target, params, 200, true, restrict);
|
|
105
|
+
}, 'repeatable read');
|
|
106
|
+
void this.auditUnmask(target, ctx, res.items.map((row) => row.id), 'read');
|
|
107
|
+
return res;
|
|
108
|
+
}
|
|
109
|
+
async listIn(tx, agg, params, maxPageSize, count = true, restrict = (q) => q) {
|
|
80
110
|
const page = Math.max(1, Number(params.page ?? 1) || 1);
|
|
81
111
|
const pageSize = Math.min(maxPageSize, Math.max(1, Number(params.pageSize ?? 20) || 20));
|
|
82
112
|
const hidden = new Set(hiddenFields(agg));
|
|
@@ -86,7 +116,7 @@ export class ReadSide {
|
|
|
86
116
|
if (RESERVED.has(k) || v === undefined || v === '')
|
|
87
117
|
continue;
|
|
88
118
|
const f = agg.fields[k];
|
|
89
|
-
const col = SYS[k] ?? (f && !hidden.has(k) && f.sql !== 'jsonb' && !f.sensitiveKind ? snake(k) : undefined);
|
|
119
|
+
const col = SYS[k] ?? (f && !isRelation(f) && !hidden.has(k) && f.sql !== 'jsonb' && !f.sensitiveKind ? snake(k) : undefined);
|
|
90
120
|
if (!col)
|
|
91
121
|
throw cageError('INVALID_INPUT', [{ path: k, message: `不能按 ${k} 过滤` }]);
|
|
92
122
|
if (k === 'status' && typeof v === 'string' && v.includes(','))
|
|
@@ -100,34 +130,51 @@ export class ReadSide {
|
|
|
100
130
|
q = q.where('created_at', '>=', new Date(String(params.createdAfter)));
|
|
101
131
|
if (params.createdBefore)
|
|
102
132
|
q = q.where('created_at', '<', new Date(String(params.createdBefore)));
|
|
103
|
-
return q;
|
|
133
|
+
return restrict(q);
|
|
104
134
|
};
|
|
105
135
|
let q = apply(tx.selectFrom(view).selectAll());
|
|
106
136
|
const sortKey = typeof params.sort === 'string' && params.sort ? params.sort.replace(/^-/, '') : 'createdAt';
|
|
107
|
-
const
|
|
137
|
+
const sortField = agg.fields[sortKey];
|
|
138
|
+
const sortCol = SYS[sortKey] ?? (sortField && !isRelation(sortField) && !hidden.has(sortKey) ? snake(sortKey) : undefined);
|
|
108
139
|
if (!sortCol)
|
|
109
140
|
throw cageError('INVALID_INPUT', [{ path: 'sort', message: `不能按 ${sortKey} 排序` }]);
|
|
110
141
|
const desc = typeof params.sort === 'string' ? params.sort.startsWith('-') : true;
|
|
111
142
|
q = q.orderBy(sql.ref(sortCol), desc ? 'desc' : 'asc').orderBy('id').limit(pageSize).offset((page - 1) * pageSize);
|
|
112
143
|
const rows = await q.execute();
|
|
113
|
-
const cnt = await apply(tx.selectFrom(view).select(sql `count(*)::int`.as('n'))).executeTakeFirst();
|
|
144
|
+
const cnt = count ? await apply(tx.selectFrom(view).select(sql `count(*)::int`.as('n'))).executeTakeFirst() : null;
|
|
114
145
|
return { items: rows.map(viewRowToObject), total: Number(cnt?.n ?? 0), page, pageSize };
|
|
115
146
|
}
|
|
116
|
-
/**
|
|
147
|
+
/**
|
|
148
|
+
* worker 使用的导出构建器:同一只读快照内分批读取,避免翻页期间数据变化导致漏行或重复。
|
|
149
|
+
* 上限用于约束单任务内存与数据库文件存储;超限明确失败,绝不能静默截断成“成功文件”。
|
|
150
|
+
* HTTP 只提交任务,保留本方法供受控的服务端调用。
|
|
151
|
+
*/
|
|
117
152
|
async export(a, params, ctx) {
|
|
118
153
|
const agg = this.agg(a);
|
|
119
154
|
this.need(ctx, `${agg.name}.export`);
|
|
120
|
-
const
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
155
|
+
const cols = ['id', ...Object.entries(agg.fields).filter(([, f]) => !f.isHidden && !isRelation(f)).map(([k]) => k), 'status', 'version', 'createdAt', 'updatedAt'];
|
|
156
|
+
const lines = ['\uFEFF' + cols.map(csvCell).join(',')], ids = [];
|
|
157
|
+
let bytes = Buffer.byteLength(lines[0]);
|
|
158
|
+
await this.tx(ctx, async (tx) => {
|
|
159
|
+
let total = 0;
|
|
160
|
+
for (let page = 1; page === 1 || ids.length < total; page++) {
|
|
161
|
+
const res = await this.listIn(tx, agg, { ...params, page, pageSize: 1000 }, 1000, page === 1);
|
|
162
|
+
if (page === 1)
|
|
163
|
+
total = res.total;
|
|
164
|
+
if (total > 100_000)
|
|
165
|
+
throw cageError('INVALID_INPUT', '导出最多支持 100000 行,请缩小筛选范围');
|
|
166
|
+
for (const row of res.items) {
|
|
167
|
+
const line = cols.map((c) => csvCell(row[c])).join(',');
|
|
168
|
+
bytes += Buffer.byteLength(line) + 1;
|
|
169
|
+
if (bytes > 20 * 1024 * 1024)
|
|
170
|
+
throw cageError('INVALID_INPUT', '导出文件超过 20 MiB,请缩小筛选范围');
|
|
171
|
+
lines.push(line);
|
|
172
|
+
ids.push(row.id);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}, 'repeatable read');
|
|
176
|
+
await this.auditUnmask(agg, ctx, ids, 'export');
|
|
177
|
+
return { filename: `${agg.table.name}.csv`, contentType: 'text/csv; charset=utf-8', body: lines.join('\n'), rows: ids.length };
|
|
131
178
|
}
|
|
132
179
|
/** 执行 query 声明:需要 reads 里每个聚合的 read 能力;手写 SQL 用 :name 占位,全部参数化 */
|
|
133
180
|
async run(q, raw, ctx) {
|