@ai0x0/utils 0.6.0 → 0.7.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/.agents/skills/ai0x0-utils/backend/SKILL.md +8 -5
- package/SKILL.md +8 -5
- package/es/backend/actions/create-delete-action.d.ts +2 -3
- package/es/backend/actions/create-delete-action.js +8 -44
- package/es/backend/actions/create-get-action.js +11 -7
- package/es/backend/actions/create-get-list-action.js +11 -6
- package/es/backend/actions/create-put-action.d.ts +1 -1
- package/es/backend/actions/create-put-action.js +15 -39
- package/es/backend/actions/get-list-data.d.ts +1 -1
- package/es/backend/actions/get-list-data.js +12 -5
- package/es/backend/actions/get-list-query-core.d.ts +67 -0
- package/es/backend/actions/get-list-query-core.js +306 -0
- package/es/backend/actions/get-list-query.d.ts +7 -18
- package/es/backend/actions/get-list-query.js +23 -220
- package/es/backend/actions/index.d.ts +1 -0
- package/es/backend/actions/index.js +2 -1
- package/es/backend/actions/paging.d.ts +11 -0
- package/es/backend/actions/paging.js +32 -0
- package/es/backend/actions/write-action-core.d.ts +18 -0
- package/es/backend/actions/write-action-core.js +119 -0
- package/es/backend/route-operation/write-operation-core.js +12 -5
- package/es/backend/schemas/common.d.ts +112 -0
- package/es/backend/schemas/common.js +158 -0
- package/es/backend/schemas/index.d.ts +735 -364
- package/es/backend/schemas/index.js +8 -139
- package/es/backend/sqlite/actions/create-delete-action.d.ts +1 -2
- package/es/backend/sqlite/actions/create-delete-action.js +7 -43
- package/es/backend/sqlite/actions/create-get-action.js +9 -6
- package/es/backend/sqlite/actions/create-get-list-action.js +7 -5
- package/es/backend/sqlite/actions/create-put-action.d.ts +1 -1
- package/es/backend/sqlite/actions/create-put-action.js +13 -36
- package/es/backend/sqlite/actions/get-list-data.d.ts +1 -1
- package/es/backend/sqlite/actions/get-list-data.js +11 -5
- package/es/backend/sqlite/actions/get-list-query.d.ts +6 -9
- package/es/backend/sqlite/actions/get-list-query.js +24 -217
- package/es/backend/sqlite/errors.d.ts +1 -4
- package/es/backend/sqlite/errors.js +6 -31
- package/es/backend/sqlite/schemas/index.d.ts +1 -111
- package/es/backend/sqlite/schemas/index.js +8 -139
- package/lib/backend/actions/create-delete-action.d.ts +2 -3
- package/lib/backend/actions/create-delete-action.js +2 -14
- package/lib/backend/actions/create-get-action.js +3 -1
- package/lib/backend/actions/create-get-list-action.js +3 -1
- package/lib/backend/actions/create-put-action.d.ts +1 -1
- package/lib/backend/actions/create-put-action.js +3 -16
- package/lib/backend/actions/get-list-data.d.ts +1 -1
- package/lib/backend/actions/get-list-data.js +8 -3
- package/lib/backend/actions/get-list-query-core.d.ts +67 -0
- package/lib/backend/actions/get-list-query-core.js +206 -0
- package/lib/backend/actions/get-list-query.d.ts +7 -18
- package/lib/backend/actions/get-list-query.js +16 -141
- package/lib/backend/actions/index.d.ts +1 -0
- package/lib/backend/actions/index.js +7 -2
- package/lib/backend/actions/paging.d.ts +11 -0
- package/lib/backend/actions/paging.js +50 -0
- package/lib/backend/actions/write-action-core.d.ts +18 -0
- package/lib/backend/actions/write-action-core.js +70 -0
- package/lib/backend/schemas/common.d.ts +112 -0
- package/lib/backend/schemas/common.js +112 -0
- package/lib/backend/schemas/index.d.ts +735 -364
- package/lib/backend/schemas/index.js +12 -83
- package/lib/backend/sqlite/actions/create-delete-action.d.ts +1 -2
- package/lib/backend/sqlite/actions/create-delete-action.js +2 -14
- package/lib/backend/sqlite/actions/create-get-action.js +3 -1
- package/lib/backend/sqlite/actions/create-get-list-action.js +3 -1
- package/lib/backend/sqlite/actions/create-put-action.d.ts +1 -1
- package/lib/backend/sqlite/actions/create-put-action.js +3 -16
- package/lib/backend/sqlite/actions/get-list-data.d.ts +1 -1
- package/lib/backend/sqlite/actions/get-list-data.js +8 -3
- package/lib/backend/sqlite/actions/get-list-query.d.ts +6 -9
- package/lib/backend/sqlite/actions/get-list-query.js +16 -141
- package/lib/backend/sqlite/errors.d.ts +1 -4
- package/lib/backend/sqlite/errors.js +2 -8
- package/lib/backend/sqlite/schemas/index.d.ts +1 -111
- package/lib/backend/sqlite/schemas/index.js +12 -83
- package/package.json +1 -1
|
@@ -32,8 +32,9 @@ function createGetAction({
|
|
|
32
32
|
table
|
|
33
33
|
}) {
|
|
34
34
|
return async (params, { scope }) => {
|
|
35
|
+
const exposed = Object.keys(bodySchema.shape);
|
|
35
36
|
const fields = {};
|
|
36
|
-
for (const key of
|
|
37
|
+
for (const key of exposed) {
|
|
37
38
|
const field = table[key];
|
|
38
39
|
if (field) {
|
|
39
40
|
fields[key] = field;
|
|
@@ -42,6 +43,7 @@ function createGetAction({
|
|
|
42
43
|
const { query, countQuery } = (0, import_get_list_query.getListQuery)({
|
|
43
44
|
db,
|
|
44
45
|
fields,
|
|
46
|
+
filterable: exposed,
|
|
45
47
|
jsonArrayFields,
|
|
46
48
|
params,
|
|
47
49
|
relations,
|
|
@@ -33,8 +33,9 @@ function createGetListAction({
|
|
|
33
33
|
}) {
|
|
34
34
|
return async (params, { scope }) => {
|
|
35
35
|
const { current, pageSize, ...other } = params;
|
|
36
|
+
const exposed = Object.keys(bodySchema.shape);
|
|
36
37
|
const fields = {};
|
|
37
|
-
for (const key of
|
|
38
|
+
for (const key of exposed) {
|
|
38
39
|
const field = table[key];
|
|
39
40
|
if (field) {
|
|
40
41
|
fields[key] = field;
|
|
@@ -43,6 +44,7 @@ function createGetListAction({
|
|
|
43
44
|
const { query, countQuery } = (0, import_get_list_query.getListQuery)({
|
|
44
45
|
db,
|
|
45
46
|
fields,
|
|
47
|
+
filterable: exposed,
|
|
46
48
|
jsonArrayFields,
|
|
47
49
|
params: other,
|
|
48
50
|
relations,
|
|
@@ -6,6 +6,6 @@ export declare function createPutAction<T extends ZodSchema, TTable extends Base
|
|
|
6
6
|
bodySchema: T;
|
|
7
7
|
db: NodePgDatabase<Record<string, unknown>>;
|
|
8
8
|
table: TTable;
|
|
9
|
-
}): (body: Partial<z.infer<T>> & Record<string, unknown>,
|
|
9
|
+
}): (body: Partial<z.infer<T>> & Record<string, unknown>, options: {
|
|
10
10
|
scope: ScopeArg;
|
|
11
11
|
}) => Promise<z.infer<T>>;
|
|
@@ -22,26 +22,13 @@ __export(create_put_action_exports, {
|
|
|
22
22
|
createPutAction: () => createPutAction
|
|
23
23
|
});
|
|
24
24
|
module.exports = __toCommonJS(create_put_action_exports);
|
|
25
|
-
var
|
|
26
|
-
var import_drizzle_orm = require("drizzle-orm");
|
|
27
|
-
var import_scope = require("../scope");
|
|
28
|
-
var import_errors = require("../errors");
|
|
25
|
+
var import_write_action_core = require("./write-action-core");
|
|
29
26
|
function createPutAction({
|
|
30
27
|
db,
|
|
31
28
|
table
|
|
32
29
|
}) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (!id) {
|
|
36
|
-
throw new import_errors.HttpError(400, "缺少 id");
|
|
37
|
-
}
|
|
38
|
-
const guard = (0, import_scope.scopeCondition)(table, scope);
|
|
39
|
-
const [data] = await db.update(table).set((0, import_transform_body.transformBody)(body)).where(guard ? (0, import_drizzle_orm.and)((0, import_drizzle_orm.eq)(table.id, id), guard) : (0, import_drizzle_orm.eq)(table.id, id)).returning();
|
|
40
|
-
if (!data && scope !== import_scope.NO_SCOPE) {
|
|
41
|
-
throw new import_errors.HttpError(404, "未找到编辑对象,或没有权限");
|
|
42
|
-
}
|
|
43
|
-
return data;
|
|
44
|
-
};
|
|
30
|
+
const run = (0, import_write_action_core.createPutActionCore)({ db, table });
|
|
31
|
+
return async (body, options) => await run(body, options);
|
|
45
32
|
}
|
|
46
33
|
// Annotate the CommonJS export names for ESM import in node:
|
|
47
34
|
0 && (module.exports = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ZodSchema } from "zod";
|
|
2
2
|
import { getListQuery } from "./get-list-query.js";
|
|
3
|
-
export declare function getListData<B extends ZodSchema>({ query, countQuery, pageSize, current, bodySchema, }: {
|
|
3
|
+
export declare function getListData<B extends ZodSchema>({ query, countQuery, pageSize: rawPageSize, current: rawCurrent, bodySchema, }: {
|
|
4
4
|
bodySchema: B;
|
|
5
5
|
current?: number;
|
|
6
6
|
pageSize?: number;
|
|
@@ -24,20 +24,25 @@ __export(get_list_data_exports, {
|
|
|
24
24
|
module.exports = __toCommonJS(get_list_data_exports);
|
|
25
25
|
var import_next_rest_framework = require("next-rest-framework");
|
|
26
26
|
var import_schemas = require("../schemas");
|
|
27
|
+
var import_paging = require("./paging");
|
|
27
28
|
function getListData({
|
|
28
29
|
query,
|
|
29
30
|
countQuery,
|
|
30
|
-
pageSize
|
|
31
|
-
current
|
|
31
|
+
pageSize: rawPageSize,
|
|
32
|
+
current: rawCurrent,
|
|
32
33
|
bodySchema
|
|
33
34
|
}) {
|
|
35
|
+
const { current, pageSize } = (0, import_paging.normalizePaging)({
|
|
36
|
+
current: rawCurrent,
|
|
37
|
+
pageSize: rawPageSize
|
|
38
|
+
});
|
|
34
39
|
return (0, import_next_rest_framework.rpcOperation)().outputs([
|
|
35
40
|
{
|
|
36
41
|
body: (0, import_schemas.listBodySchema)(bodySchema),
|
|
37
42
|
contentType: "application/json"
|
|
38
43
|
}
|
|
39
44
|
]).handler(async () => {
|
|
40
|
-
const data = await query.limit(
|
|
45
|
+
const data = await query.limit(pageSize).offset((current - 1) * pageSize).execute();
|
|
41
46
|
const [{ count }] = await countQuery.execute();
|
|
42
47
|
return { data, total: Number(count) };
|
|
43
48
|
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Column, SQL } from "drizzle-orm";
|
|
2
|
+
import { ScopeArg } from "../scope.js";
|
|
3
|
+
/**
|
|
4
|
+
* 核心用得到表的哪一部分:只有 `id`(计数与分组要它)和「按列名取列」。
|
|
5
|
+
* 两棵树的 BaseTable 都满足它,而 dialect 各自的 Table 类型不必进来。
|
|
6
|
+
*/
|
|
7
|
+
export interface ListQueryTable {
|
|
8
|
+
id: Column;
|
|
9
|
+
}
|
|
10
|
+
export interface ListQueryRelation {
|
|
11
|
+
groupBy?: boolean;
|
|
12
|
+
select?: Record<string, unknown>;
|
|
13
|
+
sql?: SQL;
|
|
14
|
+
table?: ListQueryTable;
|
|
15
|
+
}
|
|
16
|
+
/** 两套方言之间**全部**的差异。加第三种数据库时要写的也只有这两条。 */
|
|
17
|
+
export interface ListQueryDialect {
|
|
18
|
+
/**
|
|
19
|
+
* 大小写不敏感的「包含」。收到的 pattern 是**已经转义、已经带好 `%` 的完整模式串** ——
|
|
20
|
+
* 转义收在核心里做(见 escapeLikePattern),免得加第三种方言时漏掉那一步:漏掉不会报错,
|
|
21
|
+
* 只会让 `%` 重新变回通配符。
|
|
22
|
+
*/
|
|
23
|
+
contains(_column: Column, _pattern: string): SQL;
|
|
24
|
+
/** 「这个 JSON 数组列里有没有元素匹配这个模式」。pattern 同上,已转义。 */
|
|
25
|
+
jsonArrayContains(_column: Column, _pattern: string): SQL;
|
|
26
|
+
}
|
|
27
|
+
export interface ListQueryParams {
|
|
28
|
+
[field: string]: unknown;
|
|
29
|
+
orderBy?: string;
|
|
30
|
+
orderDir?: "asc" | "desc";
|
|
31
|
+
page?: number;
|
|
32
|
+
pageSize?: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 把用户输入变成 LIKE 模式里的**字面量**。
|
|
36
|
+
*
|
|
37
|
+
* 不转义的话 `?name=%` 就是「匹配全部」、`?name=a_c` 里的下划线是「任意一个字符」—— 前者只是
|
|
38
|
+
* 筛不准,后者配合「能按某一列筛」就是一个逐字符试探值的通道。反斜杠自己也要转,否则
|
|
39
|
+
* `?name=\` 会把后面那个 `%` 吃掉。
|
|
40
|
+
*/
|
|
41
|
+
export declare function escapeLikePattern(keyword: string): string;
|
|
42
|
+
export declare function createGetListQuery(dialect: ListQueryDialect): <TTable extends ListQueryTable, TSelection extends Record<string, unknown>>({ db, fields, filterable, jsonArrayFields, params, relations, scope, table, }: {
|
|
43
|
+
db: any;
|
|
44
|
+
fields: TSelection;
|
|
45
|
+
/**
|
|
46
|
+
* 允许被当成筛选条件的键。**不传等于不限制**(直接调 getListQuery 的老调用方行为不变)。
|
|
47
|
+
*
|
|
48
|
+
* 为什么需要它:筛选是按列名在表上找列的,而那跟「这个端点返回哪些列」完全脱钩 —— 于是
|
|
49
|
+
* 一个只返回 id / title 的列表,照样可以 `?secret=sk-a` 让数据库去 `ilike '%sk-a%'`,再从
|
|
50
|
+
* 返回的 total 上把这一列逐字符读出来。列表工厂传进来的是响应 schema 的字段集,也就是
|
|
51
|
+
* 「调用方本来就读得到的那些列」,筛选面与可见面因此对齐。
|
|
52
|
+
*/
|
|
53
|
+
filterable?: readonly string[] | undefined;
|
|
54
|
+
jsonArrayFields?: string[] | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* 行级作用域。**必填**,不想隔离就显式传 NO_SCOPE —— 写成可选的话,漏传就退化成查全表,
|
|
57
|
+
* 而那是静默的越权(见 scope.ts)。它不走下面 params 那条筛选通道,因为那条的规矩是
|
|
58
|
+
* 「空值跳过」,一个取不到值的作用域会让整条 where 消失。
|
|
59
|
+
*/
|
|
60
|
+
scope: ScopeArg;
|
|
61
|
+
params: ListQueryParams;
|
|
62
|
+
relations?: ListQueryRelation[] | undefined;
|
|
63
|
+
table: TTable;
|
|
64
|
+
}) => {
|
|
65
|
+
countQuery: any;
|
|
66
|
+
query: any;
|
|
67
|
+
};
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc2) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc2 = __getOwnPropDesc(from, key)) || desc2.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// src/backend/actions/get-list-query-core.ts
|
|
20
|
+
var get_list_query_core_exports = {};
|
|
21
|
+
__export(get_list_query_core_exports, {
|
|
22
|
+
createGetListQuery: () => createGetListQuery,
|
|
23
|
+
escapeLikePattern: () => escapeLikePattern
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(get_list_query_core_exports);
|
|
26
|
+
var import_drizzle_orm = require("drizzle-orm");
|
|
27
|
+
var import_errors = require("../errors");
|
|
28
|
+
var import_scope = require("../scope");
|
|
29
|
+
var import_paging = require("./paging");
|
|
30
|
+
function escapeLikePattern(keyword) {
|
|
31
|
+
return `%${keyword.replace(/[\\%_]/g, (char) => `\\${char}`)}%`;
|
|
32
|
+
}
|
|
33
|
+
function columnAt(table, key) {
|
|
34
|
+
const value = table[key];
|
|
35
|
+
return (0, import_drizzle_orm.is)(value, import_drizzle_orm.Column) ? value : void 0;
|
|
36
|
+
}
|
|
37
|
+
function isIdKey(key) {
|
|
38
|
+
return key === "id" || key.endsWith("Id");
|
|
39
|
+
}
|
|
40
|
+
function isFuzzyMatchable(column) {
|
|
41
|
+
return column.dataType === "string" && !/uuid|enum/i.test(column.columnType);
|
|
42
|
+
}
|
|
43
|
+
function createGetListQuery(dialect) {
|
|
44
|
+
return function getListQuery({
|
|
45
|
+
db,
|
|
46
|
+
fields,
|
|
47
|
+
filterable,
|
|
48
|
+
jsonArrayFields,
|
|
49
|
+
params,
|
|
50
|
+
relations,
|
|
51
|
+
scope,
|
|
52
|
+
table
|
|
53
|
+
}) {
|
|
54
|
+
const {
|
|
55
|
+
page,
|
|
56
|
+
pageSize: rawPageSize,
|
|
57
|
+
orderBy,
|
|
58
|
+
orderDir = "desc",
|
|
59
|
+
...filters
|
|
60
|
+
} = params;
|
|
61
|
+
const { current, pageSize } = (0, import_paging.normalizePaging)({
|
|
62
|
+
current: page,
|
|
63
|
+
pageSize: rawPageSize
|
|
64
|
+
});
|
|
65
|
+
const allowed = filterable ? new Set(filterable) : void 0;
|
|
66
|
+
const isFilterable = (key) => !allowed || allowed.has(key);
|
|
67
|
+
function buildBaseQuery() {
|
|
68
|
+
let queryFields = { ...fields };
|
|
69
|
+
if (relations == null ? void 0 : relations.length) {
|
|
70
|
+
for (const { select } of relations) {
|
|
71
|
+
queryFields = { ...queryFields, ...select };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const query2 = db.select(queryFields).from(table);
|
|
75
|
+
if (relations == null ? void 0 : relations.length) {
|
|
76
|
+
query2.groupBy(
|
|
77
|
+
table.id,
|
|
78
|
+
...relations.filter(({ groupBy }) => groupBy).filter(({ table: joined }) => joined).map(({ table: joined }) => joined.id)
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return query2;
|
|
82
|
+
}
|
|
83
|
+
function findTargetColumn(key) {
|
|
84
|
+
return [table].concat(
|
|
85
|
+
(relations == null ? void 0 : relations.filter(({ table: joined }) => joined).map(({ table: joined }) => joined)) || []
|
|
86
|
+
).map((candidate) => columnAt(candidate, key)).find(Boolean);
|
|
87
|
+
}
|
|
88
|
+
function addCondition(conditions2, key, value, targetColumn) {
|
|
89
|
+
const isExact = isIdKey(key) || !isFuzzyMatchable(targetColumn);
|
|
90
|
+
const isJsonArray = jsonArrayFields == null ? void 0 : jsonArrayFields.includes(key);
|
|
91
|
+
if (typeof value !== "string") {
|
|
92
|
+
conditions2.push((0, import_drizzle_orm.eq)(targetColumn, value));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (value.includes(",")) {
|
|
96
|
+
const values = value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
97
|
+
if (!values.length) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (isJsonArray) {
|
|
101
|
+
conditions2.push(
|
|
102
|
+
dialect.jsonArrayContains(
|
|
103
|
+
targetColumn,
|
|
104
|
+
escapeLikePattern(values[0])
|
|
105
|
+
)
|
|
106
|
+
);
|
|
107
|
+
} else if (isExact) {
|
|
108
|
+
conditions2.push((0, import_drizzle_orm.inArray)(targetColumn, values));
|
|
109
|
+
} else {
|
|
110
|
+
conditions2.push(
|
|
111
|
+
(0, import_drizzle_orm.or)(
|
|
112
|
+
...values.map(
|
|
113
|
+
(item) => dialect.contains(targetColumn, escapeLikePattern(item))
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (isJsonArray) {
|
|
121
|
+
conditions2.push(
|
|
122
|
+
dialect.jsonArrayContains(targetColumn, escapeLikePattern(value))
|
|
123
|
+
);
|
|
124
|
+
} else if (isExact) {
|
|
125
|
+
conditions2.push((0, import_drizzle_orm.eq)(targetColumn, value));
|
|
126
|
+
} else {
|
|
127
|
+
conditions2.push(
|
|
128
|
+
dialect.contains(targetColumn, escapeLikePattern(value))
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function buildConditions() {
|
|
133
|
+
const conditions2 = [];
|
|
134
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
135
|
+
if (!value) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (key.endsWith("AtFrom") || key.endsWith("AtTo")) {
|
|
139
|
+
const baseFieldName = `${key.replace(/(AtFrom|AtTo)$/, "")}At`;
|
|
140
|
+
if (!isFilterable(baseFieldName)) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const targetColumn2 = findTargetColumn(baseFieldName);
|
|
144
|
+
if (!targetColumn2) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const at = new Date(value);
|
|
148
|
+
if (Number.isNaN(at.getTime())) {
|
|
149
|
+
throw new import_errors.HttpError(
|
|
150
|
+
400,
|
|
151
|
+
`${key} 不是一个能识别的时间,请用 ISO 8601(如 2026-08-01T00:00:00Z)。`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
conditions2.push(
|
|
155
|
+
key.endsWith("AtFrom") ? (0, import_drizzle_orm.gte)(targetColumn2, at) : (0, import_drizzle_orm.lte)(targetColumn2, at)
|
|
156
|
+
);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (!isFilterable(key)) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const targetColumn = findTargetColumn(key);
|
|
163
|
+
if (!targetColumn) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
addCondition(conditions2, key, value, targetColumn);
|
|
167
|
+
}
|
|
168
|
+
return conditions2;
|
|
169
|
+
}
|
|
170
|
+
function buildOrderBy() {
|
|
171
|
+
const requested = typeof orderBy === "string" && orderBy ? orderBy : void 0;
|
|
172
|
+
const orderKey = requested ?? "createdAt";
|
|
173
|
+
const orderColumn = columnAt(table, orderKey);
|
|
174
|
+
if (!orderColumn) {
|
|
175
|
+
if (requested) {
|
|
176
|
+
throw new import_errors.HttpError(400, `orderBy "${requested}" 不是这张表上的列。`);
|
|
177
|
+
}
|
|
178
|
+
throw new Error(
|
|
179
|
+
`默认排序列 createdAt 不在这张表上。给这张表的列表查询显式传一个 orderBy。`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
return orderDir === "asc" ? (0, import_drizzle_orm.asc)(orderColumn) : (0, import_drizzle_orm.desc)(orderColumn);
|
|
183
|
+
}
|
|
184
|
+
const baseQuery = buildBaseQuery();
|
|
185
|
+
const scopeSql = (0, import_scope.scopeCondition)(table, scope);
|
|
186
|
+
const conditions = scopeSql ? [scopeSql] : [];
|
|
187
|
+
conditions.push(...buildConditions());
|
|
188
|
+
const where = conditions.length > 0 ? (0, import_drizzle_orm.and)(...conditions) : void 0;
|
|
189
|
+
const query = baseQuery.where(where).orderBy(buildOrderBy()).limit(pageSize).offset((current - 1) * pageSize);
|
|
190
|
+
const countQuery = db.select({ count: import_drizzle_orm.sql`COUNT(DISTINCT ${table.id})` }).from(table).where(where);
|
|
191
|
+
if (relations == null ? void 0 : relations.length) {
|
|
192
|
+
for (const { table: relationTable, sql: joinSql } of relations) {
|
|
193
|
+
if (relationTable) {
|
|
194
|
+
query.leftJoin(relationTable, joinSql);
|
|
195
|
+
countQuery.leftJoin(relationTable, joinSql);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return { countQuery, query };
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
203
|
+
0 && (module.exports = {
|
|
204
|
+
createGetListQuery,
|
|
205
|
+
escapeLikePattern
|
|
206
|
+
});
|
|
@@ -1,34 +1,23 @@
|
|
|
1
1
|
import { BaseTable, GetListRelations } from "../types.js";
|
|
2
2
|
import { SelectedFields } from "drizzle-orm/pg-core/query-builders/select.types";
|
|
3
|
-
import { SQL } from "drizzle-orm";
|
|
4
3
|
import { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
5
4
|
import { ScopeArg } from "../scope.js";
|
|
6
|
-
|
|
5
|
+
import { type ListQueryParams } from "./get-list-query-core.js";
|
|
6
|
+
export declare function getListQuery<TTable extends BaseTable, TSelection extends SelectedFields>(args: {
|
|
7
7
|
db: NodePgDatabase<Record<string, unknown>>;
|
|
8
8
|
fields: TSelection;
|
|
9
|
+
/** 允许被当成筛选条件的键。不传等于不限制,见核心里的说明。 */
|
|
10
|
+
filterable?: readonly string[];
|
|
9
11
|
jsonArrayFields?: string[];
|
|
10
12
|
/**
|
|
11
13
|
* 行级作用域。**必填**,不想隔离就显式传 NO_SCOPE —— 写成可选的话,漏传就退化成查全表,
|
|
12
|
-
* 而那是静默的越权(见 scope.ts
|
|
13
|
-
* 「空值跳过」,一个取不到值的作用域会让整条 where 消失。
|
|
14
|
+
* 而那是静默的越权(见 scope.ts)。
|
|
14
15
|
*/
|
|
15
16
|
scope: ScopeArg;
|
|
16
|
-
params:
|
|
17
|
-
[field: string]: unknown;
|
|
18
|
-
orderBy?: keyof BaseTable;
|
|
19
|
-
orderDir?: "asc" | "desc";
|
|
20
|
-
page?: number;
|
|
21
|
-
pageSize?: number;
|
|
22
|
-
};
|
|
17
|
+
params: ListQueryParams;
|
|
23
18
|
relations?: GetListRelations;
|
|
24
19
|
table: TTable;
|
|
25
20
|
}): {
|
|
26
|
-
countQuery:
|
|
27
|
-
count: SQL<unknown>;
|
|
28
|
-
}, "partial", Record<string, "not-null">, false, "where", {
|
|
29
|
-
count: unknown;
|
|
30
|
-
}[], {
|
|
31
|
-
count: import("drizzle-orm").DrizzleTypeError<"You cannot reference this field without assigning it an alias first - use `.as(<alias>)`">;
|
|
32
|
-
}>, "where">;
|
|
21
|
+
countQuery: any;
|
|
33
22
|
query: any;
|
|
34
23
|
};
|
|
@@ -6,11 +6,11 @@ var __export = (target, all) => {
|
|
|
6
6
|
for (var name in all)
|
|
7
7
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
8
|
};
|
|
9
|
-
var __copyProps = (to, from, except,
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
10
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
11
|
for (let key of __getOwnPropNames(from))
|
|
12
12
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
14
|
}
|
|
15
15
|
return to;
|
|
16
16
|
};
|
|
@@ -23,145 +23,20 @@ __export(get_list_query_exports, {
|
|
|
23
23
|
});
|
|
24
24
|
module.exports = __toCommonJS(get_list_query_exports);
|
|
25
25
|
var import_drizzle_orm = require("drizzle-orm");
|
|
26
|
-
var
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
jsonArrayFields
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
orderDir = "desc",
|
|
41
|
-
...filters
|
|
42
|
-
} = params;
|
|
43
|
-
function buildBaseQuery(table2, fields2, relations2) {
|
|
44
|
-
let queryFields = { ...fields2 };
|
|
45
|
-
if (relations2 == null ? void 0 : relations2.length) {
|
|
46
|
-
for (const { select } of relations2) {
|
|
47
|
-
queryFields = { ...queryFields, ...select };
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
const query2 = db.select(queryFields).from(table2);
|
|
51
|
-
if (relations2 == null ? void 0 : relations2.length) {
|
|
52
|
-
query2.groupBy(
|
|
53
|
-
table2.id,
|
|
54
|
-
...relations2.filter(({ groupBy }) => groupBy).filter(({ table: table3 }) => table3).map(({ table: table3 }) => table3.id)
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
return query2;
|
|
58
|
-
}
|
|
59
|
-
function findTargetColumn(key, table2, relations2) {
|
|
60
|
-
var _a;
|
|
61
|
-
return (_a = [table2].concat(
|
|
62
|
-
(relations2 == null ? void 0 : relations2.filter(({ table: table3 }) => table3).map(({ table: table3 }) => table3)) || []
|
|
63
|
-
).find((table3) => table3[key])) == null ? void 0 : _a[key];
|
|
64
|
-
}
|
|
65
|
-
function addCondition(conditions2, key, value, targetColumn) {
|
|
66
|
-
const isIdField = /id/i.test(key);
|
|
67
|
-
const isJsonArray = jsonArrayFields == null ? void 0 : jsonArrayFields.includes(key);
|
|
68
|
-
if (typeof value === "string") {
|
|
69
|
-
if (value.includes(",")) {
|
|
70
|
-
const values = value.split(",").map((val) => val.trim()).filter(Boolean);
|
|
71
|
-
if (values.length > 0) {
|
|
72
|
-
if (isJsonArray) {
|
|
73
|
-
conditions2.push(
|
|
74
|
-
import_drizzle_orm.sql`
|
|
75
|
-
EXISTS (
|
|
76
|
-
SELECT 1 FROM jsonb_array_elements_text(${targetColumn}) tag
|
|
77
|
-
WHERE tag LIKE ${`%${values[0]}%`}
|
|
78
|
-
)
|
|
79
|
-
`
|
|
80
|
-
);
|
|
81
|
-
} else if (isIdField) {
|
|
82
|
-
conditions2.push((0, import_drizzle_orm.inArray)(targetColumn, values));
|
|
83
|
-
} else {
|
|
84
|
-
conditions2.push(
|
|
85
|
-
(0, import_drizzle_orm.or)(
|
|
86
|
-
...values.map((val) => (0, import_drizzle_orm.ilike)(targetColumn, `%${val}%`))
|
|
87
|
-
)
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
} else {
|
|
92
|
-
if (isJsonArray) {
|
|
93
|
-
conditions2.push(
|
|
94
|
-
import_drizzle_orm.sql`
|
|
95
|
-
EXISTS (
|
|
96
|
-
SELECT 1 FROM jsonb_array_elements_text(${targetColumn}) tag
|
|
97
|
-
WHERE tag LIKE ${`%${value}%`}
|
|
98
|
-
)
|
|
99
|
-
`
|
|
100
|
-
);
|
|
101
|
-
} else if (isIdField) {
|
|
102
|
-
conditions2.push((0, import_drizzle_orm.eq)(targetColumn, value));
|
|
103
|
-
} else {
|
|
104
|
-
conditions2.push((0, import_drizzle_orm.ilike)(targetColumn, `%${value}%`));
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
} else {
|
|
108
|
-
conditions2.push((0, import_drizzle_orm.eq)(targetColumn, value));
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
function buildConditions(filters2, table2, relations2) {
|
|
112
|
-
const conditions2 = [];
|
|
113
|
-
for (const [key, value] of Object.entries(filters2)) {
|
|
114
|
-
if (!value) {
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
if (key.endsWith("AtFrom") || key.endsWith("AtTo")) {
|
|
118
|
-
const baseFieldName = key.replace(/(AtFrom|AtTo)$/, "");
|
|
119
|
-
const targetColumn2 = findTargetColumn(
|
|
120
|
-
`${baseFieldName}At`,
|
|
121
|
-
table2,
|
|
122
|
-
relations2
|
|
123
|
-
);
|
|
124
|
-
if (!targetColumn2) {
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
if (key.endsWith("AtFrom")) {
|
|
128
|
-
conditions2.push((0, import_drizzle_orm.gte)(targetColumn2, new Date(value)));
|
|
129
|
-
} else if (key.endsWith("AtTo")) {
|
|
130
|
-
conditions2.push((0, import_drizzle_orm.lte)(targetColumn2, new Date(value)));
|
|
131
|
-
}
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
const targetColumn = findTargetColumn(
|
|
135
|
-
key,
|
|
136
|
-
table2,
|
|
137
|
-
relations2
|
|
138
|
-
);
|
|
139
|
-
if (!targetColumn) {
|
|
140
|
-
continue;
|
|
141
|
-
}
|
|
142
|
-
addCondition(conditions2, key, value, targetColumn);
|
|
143
|
-
}
|
|
144
|
-
return conditions2;
|
|
145
|
-
}
|
|
146
|
-
function buildOrderBy(table2, orderBy2, orderDir2) {
|
|
147
|
-
const orderColumn = table2[orderBy2];
|
|
148
|
-
return orderDir2 === "asc" ? (0, import_drizzle_orm.asc)(orderColumn) : (0, import_drizzle_orm.desc)(orderColumn);
|
|
149
|
-
}
|
|
150
|
-
const baseQuery = buildBaseQuery(table, fields, relations);
|
|
151
|
-
const scopeSql = (0, import_scope.scopeCondition)(table, scope);
|
|
152
|
-
const conditions = scopeSql ? [scopeSql] : [];
|
|
153
|
-
conditions.push(...buildConditions(filters, table, relations));
|
|
154
|
-
const query = baseQuery.where(conditions.length > 0 ? (0, import_drizzle_orm.and)(...conditions) : void 0).orderBy(buildOrderBy(table, orderBy, orderDir)).limit(pageSize).offset((page - 1) * pageSize);
|
|
155
|
-
const countQuery = db.select({ count: import_drizzle_orm.sql`COUNT(DISTINCT ${table.id})` }).from(table).where(conditions.length > 0 ? (0, import_drizzle_orm.and)(...conditions) : void 0);
|
|
156
|
-
if (relations == null ? void 0 : relations.length) {
|
|
157
|
-
for (const { table: relationTable, sql: joinSql } of relations) {
|
|
158
|
-
if (relationTable) {
|
|
159
|
-
query.leftJoin(relationTable, joinSql);
|
|
160
|
-
countQuery.leftJoin(relationTable, joinSql);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return { countQuery, query };
|
|
26
|
+
var import_get_list_query_core = require("./get-list-query-core");
|
|
27
|
+
var run = (0, import_get_list_query_core.createGetListQuery)({
|
|
28
|
+
contains: (column, pattern) => (0, import_drizzle_orm.ilike)(column, pattern),
|
|
29
|
+
// jsonb 数组展开成行再逐个 LIKE。列存的是 text[] 形状的 jsonb(见 createTableSchema 的
|
|
30
|
+
// jsonArrayFields),所以用 _text 那个变体,取到的 tag 直接就是字符串。
|
|
31
|
+
jsonArrayContains: (column, pattern) => import_drizzle_orm.sql`
|
|
32
|
+
EXISTS (
|
|
33
|
+
SELECT 1 FROM jsonb_array_elements_text(${column}) tag
|
|
34
|
+
WHERE tag LIKE ${pattern}
|
|
35
|
+
)
|
|
36
|
+
`
|
|
37
|
+
});
|
|
38
|
+
function getListQuery(args) {
|
|
39
|
+
return run(args);
|
|
165
40
|
}
|
|
166
41
|
// Annotate the CommonJS export names for ESM import in node:
|
|
167
42
|
0 && (module.exports = {
|
|
@@ -4,3 +4,4 @@ export { createGetListAction } from "./create-get-list-action.js";
|
|
|
4
4
|
export { createPostAction } from "./create-post-action.js";
|
|
5
5
|
export { createPutAction } from "./create-put-action.js";
|
|
6
6
|
export { getListQuery } from "./get-list-query.js";
|
|
7
|
+
export { MAX_PAGE_SIZE, normalizePaging } from "./paging.js";
|
|
@@ -19,12 +19,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
// src/backend/actions/index.ts
|
|
20
20
|
var actions_exports = {};
|
|
21
21
|
__export(actions_exports, {
|
|
22
|
+
MAX_PAGE_SIZE: () => import_paging.MAX_PAGE_SIZE,
|
|
22
23
|
createDeleteAction: () => import_create_delete_action.createDeleteAction,
|
|
23
24
|
createGetAction: () => import_create_get_action.createGetAction,
|
|
24
25
|
createGetListAction: () => import_create_get_list_action.createGetListAction,
|
|
25
26
|
createPostAction: () => import_create_post_action.createPostAction,
|
|
26
27
|
createPutAction: () => import_create_put_action.createPutAction,
|
|
27
|
-
getListQuery: () => import_get_list_query.getListQuery
|
|
28
|
+
getListQuery: () => import_get_list_query.getListQuery,
|
|
29
|
+
normalizePaging: () => import_paging.normalizePaging
|
|
28
30
|
});
|
|
29
31
|
module.exports = __toCommonJS(actions_exports);
|
|
30
32
|
var import_create_delete_action = require("./create-delete-action");
|
|
@@ -33,12 +35,15 @@ var import_create_get_list_action = require("./create-get-list-action");
|
|
|
33
35
|
var import_create_post_action = require("./create-post-action");
|
|
34
36
|
var import_create_put_action = require("./create-put-action");
|
|
35
37
|
var import_get_list_query = require("./get-list-query");
|
|
38
|
+
var import_paging = require("./paging");
|
|
36
39
|
// Annotate the CommonJS export names for ESM import in node:
|
|
37
40
|
0 && (module.exports = {
|
|
41
|
+
MAX_PAGE_SIZE,
|
|
38
42
|
createDeleteAction,
|
|
39
43
|
createGetAction,
|
|
40
44
|
createGetListAction,
|
|
41
45
|
createPostAction,
|
|
42
46
|
createPutAction,
|
|
43
|
-
getListQuery
|
|
47
|
+
getListQuery,
|
|
48
|
+
normalizePaging
|
|
44
49
|
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** pageSize 的硬上界。取这个量级是因为「一次把一整块数据取回来」是列表的正常用法。 */
|
|
2
|
+
export declare const MAX_PAGE_SIZE = 10000;
|
|
3
|
+
export declare const DEFAULT_PAGE_SIZE = 10;
|
|
4
|
+
/** 返回一定能安全喂给 limit / offset 的一对整数。 */
|
|
5
|
+
export declare function normalizePaging({ current, pageSize, }: {
|
|
6
|
+
current?: unknown;
|
|
7
|
+
pageSize?: unknown;
|
|
8
|
+
}): {
|
|
9
|
+
current: number;
|
|
10
|
+
pageSize: number;
|
|
11
|
+
};
|