@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.
Files changed (76) hide show
  1. package/.agents/skills/ai0x0-utils/backend/SKILL.md +8 -5
  2. package/SKILL.md +8 -5
  3. package/es/backend/actions/create-delete-action.d.ts +2 -3
  4. package/es/backend/actions/create-delete-action.js +8 -44
  5. package/es/backend/actions/create-get-action.js +11 -7
  6. package/es/backend/actions/create-get-list-action.js +11 -6
  7. package/es/backend/actions/create-put-action.d.ts +1 -1
  8. package/es/backend/actions/create-put-action.js +15 -39
  9. package/es/backend/actions/get-list-data.d.ts +1 -1
  10. package/es/backend/actions/get-list-data.js +12 -5
  11. package/es/backend/actions/get-list-query-core.d.ts +67 -0
  12. package/es/backend/actions/get-list-query-core.js +306 -0
  13. package/es/backend/actions/get-list-query.d.ts +7 -18
  14. package/es/backend/actions/get-list-query.js +23 -220
  15. package/es/backend/actions/index.d.ts +1 -0
  16. package/es/backend/actions/index.js +2 -1
  17. package/es/backend/actions/paging.d.ts +11 -0
  18. package/es/backend/actions/paging.js +32 -0
  19. package/es/backend/actions/write-action-core.d.ts +18 -0
  20. package/es/backend/actions/write-action-core.js +119 -0
  21. package/es/backend/route-operation/write-operation-core.js +12 -5
  22. package/es/backend/schemas/common.d.ts +112 -0
  23. package/es/backend/schemas/common.js +158 -0
  24. package/es/backend/schemas/index.d.ts +735 -364
  25. package/es/backend/schemas/index.js +8 -139
  26. package/es/backend/sqlite/actions/create-delete-action.d.ts +1 -2
  27. package/es/backend/sqlite/actions/create-delete-action.js +7 -43
  28. package/es/backend/sqlite/actions/create-get-action.js +9 -6
  29. package/es/backend/sqlite/actions/create-get-list-action.js +7 -5
  30. package/es/backend/sqlite/actions/create-put-action.d.ts +1 -1
  31. package/es/backend/sqlite/actions/create-put-action.js +13 -36
  32. package/es/backend/sqlite/actions/get-list-data.d.ts +1 -1
  33. package/es/backend/sqlite/actions/get-list-data.js +11 -5
  34. package/es/backend/sqlite/actions/get-list-query.d.ts +6 -9
  35. package/es/backend/sqlite/actions/get-list-query.js +24 -217
  36. package/es/backend/sqlite/errors.d.ts +1 -4
  37. package/es/backend/sqlite/errors.js +6 -31
  38. package/es/backend/sqlite/schemas/index.d.ts +1 -111
  39. package/es/backend/sqlite/schemas/index.js +8 -139
  40. package/lib/backend/actions/create-delete-action.d.ts +2 -3
  41. package/lib/backend/actions/create-delete-action.js +2 -14
  42. package/lib/backend/actions/create-get-action.js +3 -1
  43. package/lib/backend/actions/create-get-list-action.js +3 -1
  44. package/lib/backend/actions/create-put-action.d.ts +1 -1
  45. package/lib/backend/actions/create-put-action.js +3 -16
  46. package/lib/backend/actions/get-list-data.d.ts +1 -1
  47. package/lib/backend/actions/get-list-data.js +8 -3
  48. package/lib/backend/actions/get-list-query-core.d.ts +67 -0
  49. package/lib/backend/actions/get-list-query-core.js +206 -0
  50. package/lib/backend/actions/get-list-query.d.ts +7 -18
  51. package/lib/backend/actions/get-list-query.js +16 -141
  52. package/lib/backend/actions/index.d.ts +1 -0
  53. package/lib/backend/actions/index.js +7 -2
  54. package/lib/backend/actions/paging.d.ts +11 -0
  55. package/lib/backend/actions/paging.js +50 -0
  56. package/lib/backend/actions/write-action-core.d.ts +18 -0
  57. package/lib/backend/actions/write-action-core.js +70 -0
  58. package/lib/backend/schemas/common.d.ts +112 -0
  59. package/lib/backend/schemas/common.js +112 -0
  60. package/lib/backend/schemas/index.d.ts +735 -364
  61. package/lib/backend/schemas/index.js +12 -83
  62. package/lib/backend/sqlite/actions/create-delete-action.d.ts +1 -2
  63. package/lib/backend/sqlite/actions/create-delete-action.js +2 -14
  64. package/lib/backend/sqlite/actions/create-get-action.js +3 -1
  65. package/lib/backend/sqlite/actions/create-get-list-action.js +3 -1
  66. package/lib/backend/sqlite/actions/create-put-action.d.ts +1 -1
  67. package/lib/backend/sqlite/actions/create-put-action.js +3 -16
  68. package/lib/backend/sqlite/actions/get-list-data.d.ts +1 -1
  69. package/lib/backend/sqlite/actions/get-list-data.js +8 -3
  70. package/lib/backend/sqlite/actions/get-list-query.d.ts +6 -9
  71. package/lib/backend/sqlite/actions/get-list-query.js +16 -141
  72. package/lib/backend/sqlite/errors.d.ts +1 -4
  73. package/lib/backend/sqlite/errors.js +2 -8
  74. package/lib/backend/sqlite/schemas/index.d.ts +1 -111
  75. package/lib/backend/sqlite/schemas/index.js +12 -83
  76. package/package.json +1 -1
@@ -0,0 +1,306 @@
1
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
+ var _templateObject;
3
+ var _excluded = ["page", "pageSize", "orderBy", "orderDir"];
4
+ function _taggedTemplateLiteral(strings, raw) { if (!raw) { raw = strings.slice(0); } return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); }
5
+ function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
6
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
7
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
8
+ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
9
+ function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
10
+ function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
11
+ function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
12
+ function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
13
+ function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
14
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
15
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
16
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
17
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
18
+ function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
19
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
20
+ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
21
+ function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
22
+ function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
23
+ // ==============================================================================
24
+ // 列表查询的公共实现(pg / sqlite 共用)
25
+ // ==============================================================================
26
+ // 从前 pg 与 sqlite 各有一份 270 行的 get-list-query,逐字相同的部分占八成 —— 筛选条件怎么拼、
27
+ // 日期范围怎么认、多值怎么拆、排序与分页怎么加、计数查询怎么建,两边一模一样。**真正的方言
28
+ // 差异只有两条原语**:
29
+ //
30
+ // · 大小写不敏感的模糊匹配 —— pg 用 ilike,sqlite 的 like 本来就不区分大小写,但要自己带
31
+ // ESCAPE(pg 的 LIKE 默认拿反斜杠当转义符,sqlite 不带 ESCAPE 子句就一个转义符都没有);
32
+ // · 「JSON 数组列里有没有含这段文字的元素」—— pg 用 jsonb_array_elements_text,
33
+ // sqlite 用 json_each(而且取值的字段名也不同:tag vs tag.value)。
34
+ //
35
+ // 所以这里收下一个 dialect 就够了,剩下的一份写完两边共用。与 route-operation 那边
36
+ // createXxxOperationFactory + 薄封装是同一个套路。
37
+ //
38
+ // 抄两份的代价不是抽象的:这个文件改过好几次(作用域先于筛选、空值跳过、日期范围),
39
+ // 每一次都得记得改另一棵树,而漏掉不会有任何东西报错 —— 只会让 sqlite 那边悄悄少一条规则。
40
+
41
+ import { and, asc, Column, desc, eq, gte, inArray, is, lte, or, sql } from "drizzle-orm";
42
+ import { HttpError } from "../errors.js";
43
+ import { scopeCondition } from "../scope.js";
44
+ import { normalizePaging } from "./paging.js";
45
+
46
+ /**
47
+ * 核心用得到表的哪一部分:只有 `id`(计数与分组要它)和「按列名取列」。
48
+ * 两棵树的 BaseTable 都满足它,而 dialect 各自的 Table 类型不必进来。
49
+ */
50
+
51
+ /** 两套方言之间**全部**的差异。加第三种数据库时要写的也只有这两条。 */
52
+
53
+ /**
54
+ * 把用户输入变成 LIKE 模式里的**字面量**。
55
+ *
56
+ * 不转义的话 `?name=%` 就是「匹配全部」、`?name=a_c` 里的下划线是「任意一个字符」—— 前者只是
57
+ * 筛不准,后者配合「能按某一列筛」就是一个逐字符试探值的通道。反斜杠自己也要转,否则
58
+ * `?name=\` 会把后面那个 `%` 吃掉。
59
+ */
60
+ export function escapeLikePattern(keyword) {
61
+ return "%".concat(keyword.replace(/[\\%_]/g, function (char) {
62
+ return "\\".concat(char);
63
+ }), "%");
64
+ }
65
+
66
+ // 按列名从表(或它的关联表)里取列。**唯一一处按字符串索引的地方** —— 收在这儿,
67
+ // 免得每个用到的地方都写一次断言。取不到回 undefined,调用方据此跳过这个条件。
68
+ //
69
+ // is(…, Column) 这层不是多余的:按字符串取属性会取到原型链上的东西(`?orderBy=constructor`
70
+ // 拿到的是 Object 构造函数),而它不会在这里报错,会一路流到 drizzle 里才炸 —— 症状是 500,
71
+ // 而正确的症状是「这不是一个列」。
72
+ function columnAt(table, key) {
73
+ var value = table[key];
74
+ return is(value, Column) ? value : undefined;
75
+ }
76
+
77
+ // 「这个键该精确匹配还是模糊匹配」。
78
+ //
79
+ // 从前是 `/id/i.test(key)` —— 「名字里带 id」,于是 width / hidden / video 这类列名也被当成 id
80
+ // 列。文档写的一直是「以 Id 结尾」,这里按文档来。
81
+ function isIdKey(key) {
82
+ return key === "id" || key.endsWith("Id");
83
+ }
84
+
85
+ // 模糊匹配只在**文本列**上成立。uuid / 数字 / 时间 / 枚举列上 ILIKE 不是「筛不出东西」,是
86
+ // 数据库层面的类型错误(PG:operator does not exist: uuid ~~* unknown),也就是一个 500。
87
+ // 所以这些列一律退回精确匹配 —— 这同时让上面那条 isIdKey 的收紧变得安全:一个不以 Id 结尾的
88
+ // uuid 列(比如 parent、owner)从「精确」掉到「模糊」也不会炸。
89
+ function isFuzzyMatchable(column) {
90
+ return column.dataType === "string" && !/uuid|enum/i.test(column.columnType);
91
+ }
92
+ export function createGetListQuery(dialect) {
93
+ return function getListQuery(_ref) {
94
+ var db = _ref.db,
95
+ fields = _ref.fields,
96
+ filterable = _ref.filterable,
97
+ jsonArrayFields = _ref.jsonArrayFields,
98
+ params = _ref.params,
99
+ relations = _ref.relations,
100
+ scope = _ref.scope,
101
+ table = _ref.table;
102
+ var page = params.page,
103
+ rawPageSize = params.pageSize,
104
+ orderBy = params.orderBy,
105
+ _params$orderDir = params.orderDir,
106
+ orderDir = _params$orderDir === void 0 ? "desc" : _params$orderDir,
107
+ filters = _objectWithoutProperties(params, _excluded);
108
+ var _normalizePaging = normalizePaging({
109
+ current: page,
110
+ pageSize: rawPageSize
111
+ }),
112
+ current = _normalizePaging.current,
113
+ pageSize = _normalizePaging.pageSize;
114
+ var allowed = filterable ? new Set(filterable) : undefined;
115
+ var isFilterable = function isFilterable(key) {
116
+ return !allowed || allowed.has(key);
117
+ };
118
+
119
+ // ==========================================================================
120
+ // 基础查询
121
+ // ==========================================================================
122
+ function buildBaseQuery() {
123
+ var queryFields = _objectSpread({}, fields);
124
+ if (relations !== null && relations !== void 0 && relations.length) {
125
+ var _iterator = _createForOfIteratorHelper(relations),
126
+ _step;
127
+ try {
128
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
129
+ var select = _step.value.select;
130
+ queryFields = _objectSpread(_objectSpread({}, queryFields), select);
131
+ }
132
+ } catch (err) {
133
+ _iterator.e(err);
134
+ } finally {
135
+ _iterator.f();
136
+ }
137
+ }
138
+ var query = db.select(queryFields).from(table);
139
+ if (relations !== null && relations !== void 0 && relations.length) {
140
+ query.groupBy.apply(query, [table.id].concat(_toConsumableArray(relations.filter(function (_ref2) {
141
+ var groupBy = _ref2.groupBy;
142
+ return groupBy;
143
+ }).filter(function (_ref3) {
144
+ var joined = _ref3.table;
145
+ return joined;
146
+ }).map(function (_ref4) {
147
+ var joined = _ref4.table;
148
+ return joined.id;
149
+ }))));
150
+ }
151
+ return query;
152
+ }
153
+
154
+ // 先在主表上找,找不到再去关联表上找。
155
+ function findTargetColumn(key) {
156
+ return [table].concat((relations === null || relations === void 0 ? void 0 : relations.filter(function (_ref5) {
157
+ var joined = _ref5.table;
158
+ return joined;
159
+ }).map(function (_ref6) {
160
+ var joined = _ref6.table;
161
+ return joined;
162
+ })) || []).map(function (candidate) {
163
+ return columnAt(candidate, key);
164
+ }).find(Boolean);
165
+ }
166
+
167
+ // ==========================================================================
168
+ // 单个筛选条件
169
+ // ==========================================================================
170
+ // 三条规矩,两套方言一致:
171
+ // · 以 Id 结尾的键(以及非文本列)按精确匹配(多值走 IN),其余按模糊;
172
+ // · 逗号分隔视为多值;
173
+ // · JSON 数组列走 dialect 那条 EXISTS。
174
+ function addCondition(conditions, key, value, targetColumn) {
175
+ var isExact = isIdKey(key) || !isFuzzyMatchable(targetColumn);
176
+ var isJsonArray = jsonArrayFields === null || jsonArrayFields === void 0 ? void 0 : jsonArrayFields.includes(key);
177
+ if (typeof value !== "string") {
178
+ conditions.push(eq(targetColumn, value));
179
+ return;
180
+ }
181
+ if (value.includes(",")) {
182
+ var values = value.split(",").map(function (item) {
183
+ return item.trim();
184
+ }).filter(Boolean);
185
+ if (!values.length) {
186
+ return;
187
+ }
188
+ if (isJsonArray) {
189
+ // 多值时只认第一个 —— 与从前逐字一致,不趁重构改行为。
190
+ conditions.push(dialect.jsonArrayContains(targetColumn, escapeLikePattern(values[0])));
191
+ } else if (isExact) {
192
+ conditions.push(inArray(targetColumn, values));
193
+ } else {
194
+ conditions.push(or.apply(void 0, _toConsumableArray(values.map(function (item) {
195
+ return dialect.contains(targetColumn, escapeLikePattern(item));
196
+ }))));
197
+ }
198
+ return;
199
+ }
200
+ if (isJsonArray) {
201
+ conditions.push(dialect.jsonArrayContains(targetColumn, escapeLikePattern(value)));
202
+ } else if (isExact) {
203
+ conditions.push(eq(targetColumn, value));
204
+ } else {
205
+ conditions.push(dialect.contains(targetColumn, escapeLikePattern(value)));
206
+ }
207
+ }
208
+ function buildConditions() {
209
+ var conditions = [];
210
+ for (var _i = 0, _Object$entries = Object.entries(filters); _i < _Object$entries.length; _i++) {
211
+ var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
212
+ key = _Object$entries$_i[0],
213
+ value = _Object$entries$_i[1];
214
+ // 空值跳过。**这条只对筛选成立**,作用域另走一条路(见下)。
215
+ if (!value) {
216
+ continue;
217
+ }
218
+
219
+ // 日期范围:xxxAtFrom / xxxAtTo 落到 xxxAt 那一列上。
220
+ if (key.endsWith("AtFrom") || key.endsWith("AtTo")) {
221
+ var baseFieldName = "".concat(key.replace(/(AtFrom|AtTo)$/, ""), "At");
222
+ // 准入按落到的**那一列**判,不按参数名 —— createdAtFrom 能不能用,取决于
223
+ // createdAt 是不是一个调用方读得到的列。
224
+ if (!isFilterable(baseFieldName)) {
225
+ continue;
226
+ }
227
+ var _targetColumn = findTargetColumn(baseFieldName);
228
+ if (!_targetColumn) {
229
+ continue;
230
+ }
231
+ var at = new Date(value);
232
+ // 认不出来的日期不能往下传:drizzle 序列化 Invalid Date 时抛 RangeError,
233
+ // 那是一个 500,而这明明是请求写错了。
234
+ if (Number.isNaN(at.getTime())) {
235
+ throw new HttpError(400, "".concat(key, " \u4E0D\u662F\u4E00\u4E2A\u80FD\u8BC6\u522B\u7684\u65F6\u95F4\uFF0C\u8BF7\u7528 ISO 8601\uFF08\u5982 2026-08-01T00:00:00Z\uFF09\u3002"));
236
+ }
237
+ conditions.push(key.endsWith("AtFrom") ? gte(_targetColumn, at) : lte(_targetColumn, at));
238
+ continue;
239
+ }
240
+ if (!isFilterable(key)) {
241
+ continue;
242
+ }
243
+ var targetColumn = findTargetColumn(key);
244
+ if (!targetColumn) {
245
+ continue;
246
+ }
247
+ addCondition(conditions, key, value, targetColumn);
248
+ }
249
+ return conditions;
250
+ }
251
+ function buildOrderBy() {
252
+ // 请求里给了值、和「谁都没给于是走默认」是两件事,错误也就不是同一种:前者是请求写错
253
+ // (400),后者是这张表上根本没有 createdAt,属于配置错(跟 scopeCondition 那边同一类,
254
+ // 抛普通 Error)。从前两种都退化成 `order by desc` —— 一句语法错误的 SQL,500。
255
+ var requested = typeof orderBy === "string" && orderBy ? orderBy : undefined;
256
+ var orderKey = requested !== null && requested !== void 0 ? requested : "createdAt";
257
+ var orderColumn = columnAt(table, orderKey);
258
+ if (!orderColumn) {
259
+ if (requested) {
260
+ throw new HttpError(400, "orderBy \"".concat(requested, "\" \u4E0D\u662F\u8FD9\u5F20\u8868\u4E0A\u7684\u5217\u3002"));
261
+ }
262
+ throw new Error("\u9ED8\u8BA4\u6392\u5E8F\u5217 createdAt \u4E0D\u5728\u8FD9\u5F20\u8868\u4E0A\u3002\u7ED9\u8FD9\u5F20\u8868\u7684\u5217\u8868\u67E5\u8BE2\u663E\u5F0F\u4F20\u4E00\u4E2A orderBy\u3002");
263
+ }
264
+ return orderDir === "asc" ? asc(orderColumn) : desc(orderColumn);
265
+ }
266
+
267
+ // ==========================================================================
268
+ // 组装
269
+ // ==========================================================================
270
+ var baseQuery = buildBaseQuery();
271
+
272
+ // **作用域先于筛选加进去**,且它不受 buildConditions 里「空值跳过」那条规矩管:
273
+ // 那条规矩对 `?name=` 这种空参数是对的,对归属则是灾难性的 —— 条件消失即全表可见。
274
+ var scopeSql = scopeCondition(table, scope);
275
+ var conditions = scopeSql ? [scopeSql] : [];
276
+ conditions.push.apply(conditions, _toConsumableArray(buildConditions()));
277
+ var where = conditions.length > 0 ? and.apply(void 0, conditions) : undefined;
278
+ var query = baseQuery.where(where).orderBy(buildOrderBy()).limit(pageSize).offset((current - 1) * pageSize);
279
+ var countQuery = db.select({
280
+ count: sql(_templateObject || (_templateObject = _taggedTemplateLiteral(["COUNT(DISTINCT ", ")"])), table.id)
281
+ }).from(table).where(where);
282
+ if (relations !== null && relations !== void 0 && relations.length) {
283
+ var _iterator2 = _createForOfIteratorHelper(relations),
284
+ _step2;
285
+ try {
286
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
287
+ var _step2$value = _step2.value,
288
+ relationTable = _step2$value.table,
289
+ joinSql = _step2$value.sql;
290
+ if (relationTable) {
291
+ query.leftJoin(relationTable, joinSql);
292
+ countQuery.leftJoin(relationTable, joinSql);
293
+ }
294
+ }
295
+ } catch (err) {
296
+ _iterator2.e(err);
297
+ } finally {
298
+ _iterator2.f();
299
+ }
300
+ }
301
+ return {
302
+ countQuery: countQuery,
303
+ query: query
304
+ };
305
+ };
306
+ }
@@ -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
- export declare function getListQuery<TTable extends BaseTable, TSelection extends SelectedFields>({ db, fields, jsonArrayFields, params, relations, scope, table, }: {
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)。它不走下面 params 那条筛选通道,因为那条的规矩是
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: Omit<import("drizzle-orm/pg-core").PgSelectBase<string, {
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
  };
@@ -1,223 +1,26 @@
1
- function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
- var _templateObject, _templateObject2, _templateObject3;
3
- var _excluded = ["page", "pageSize", "orderBy", "orderDir"];
4
- function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
5
- function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
6
- function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
7
- function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
1
+ var _templateObject;
8
2
  function _taggedTemplateLiteral(strings, raw) { if (!raw) { raw = strings.slice(0); } return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); }
9
- function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
10
- function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
11
- function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
12
- function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
13
- function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
14
- function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
15
- function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
16
- function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
17
- function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
18
- function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
19
- function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
20
- function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
21
- function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
22
- function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
23
- import { and, asc, desc, eq, gte, ilike, inArray, lte, or, sql } from "drizzle-orm";
24
- import { scopeCondition } from "../scope.js";
25
- export function getListQuery(_ref) {
26
- var db = _ref.db,
27
- fields = _ref.fields,
28
- jsonArrayFields = _ref.jsonArrayFields,
29
- params = _ref.params,
30
- relations = _ref.relations,
31
- scope = _ref.scope,
32
- table = _ref.table;
33
- var _params$page = params.page,
34
- page = _params$page === void 0 ? 1 : _params$page,
35
- _params$pageSize = params.pageSize,
36
- pageSize = _params$pageSize === void 0 ? 10 : _params$pageSize,
37
- _params$orderBy = params.orderBy,
38
- orderBy = _params$orderBy === void 0 ? "createdAt" : _params$orderBy,
39
- _params$orderDir = params.orderDir,
40
- orderDir = _params$orderDir === void 0 ? "desc" : _params$orderDir,
41
- filters = _objectWithoutProperties(params, _excluded);
42
-
43
- // 构建基础查询
44
- function buildBaseQuery(table, fields, relations) {
45
- var queryFields = _objectSpread({}, fields);
46
-
47
- // 添加关联字段
48
- if (relations !== null && relations !== void 0 && relations.length) {
49
- var _iterator = _createForOfIteratorHelper(relations),
50
- _step;
51
- try {
52
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
53
- var select = _step.value.select;
54
- queryFields = _objectSpread(_objectSpread({}, queryFields), select);
55
- }
56
- } catch (err) {
57
- _iterator.e(err);
58
- } finally {
59
- _iterator.f();
60
- }
61
- }
62
- var query = db.select(queryFields).from(table);
63
-
64
- // 添加分组
65
- if (relations !== null && relations !== void 0 && relations.length) {
66
- query.groupBy.apply(query, [table.id].concat(_toConsumableArray(relations.filter(function (_ref2) {
67
- var groupBy = _ref2.groupBy;
68
- return groupBy;
69
- }).filter(function (_ref3) {
70
- var table = _ref3.table;
71
- return table;
72
- }).map(function (_ref4) {
73
- var table = _ref4.table;
74
- return table.id;
75
- }))));
76
- }
77
- return query;
78
- }
79
-
80
- // 查找目标列
81
- function findTargetColumn(key, table, relations) {
82
- var _concat$find;
83
- return (_concat$find = [table].concat((relations === null || relations === void 0 ? void 0 : relations.filter(function (_ref5) {
84
- var table = _ref5.table;
85
- return table;
86
- }).map(function (_ref6) {
87
- var table = _ref6.table;
88
- return table;
89
- })) || []).find(function (table) {
90
- return table[key];
91
- })) === null || _concat$find === void 0 ? void 0 : _concat$find[key];
92
- }
93
-
94
- // 添加条件
95
- function addCondition(conditions, key, value, targetColumn) {
96
- var isIdField = /id/i.test(key);
97
- var isJsonArray = jsonArrayFields === null || jsonArrayFields === void 0 ? void 0 : jsonArrayFields.includes(key);
98
- if (typeof value === "string") {
99
- if (value.includes(",")) {
100
- // 处理多值
101
- var values = value.split(",").map(function (val) {
102
- return val.trim();
103
- }).filter(Boolean);
104
- if (values.length > 0) {
105
- if (isJsonArray) {
106
- // 处理JSONB数组字段
107
- conditions.push(sql(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n EXISTS (\n SELECT 1 FROM jsonb_array_elements_text(", ") tag\n WHERE tag LIKE ", "\n )\n "])), targetColumn, "%".concat(values[0], "%")));
108
- } else if (isIdField) {
109
- conditions.push(inArray(targetColumn, values));
110
- } else {
111
- conditions.push(or.apply(void 0, _toConsumableArray(values.map(function (val) {
112
- return ilike(targetColumn, "%".concat(val, "%"));
113
- }))));
114
- }
115
- }
116
- } else {
117
- // 处理单值
118
- if (isJsonArray) {
119
- // 处理JSONB数组字段
120
- conditions.push(sql(_templateObject2 || (_templateObject2 = _taggedTemplateLiteral(["\n EXISTS (\n SELECT 1 FROM jsonb_array_elements_text(", ") tag\n WHERE tag LIKE ", "\n )\n "])), targetColumn, "%".concat(value, "%")));
121
- } else if (isIdField) {
122
- conditions.push(eq(targetColumn, value));
123
- } else {
124
- conditions.push(ilike(targetColumn, "%".concat(value, "%")));
125
- }
126
- }
127
- } else {
128
- conditions.push(eq(targetColumn, value));
129
- }
130
- }
131
-
132
- // 构建查询条件
133
- function buildConditions(filters, table, relations) {
134
- var conditions = [];
135
-
136
- // 处理过滤条件
137
- for (var _i = 0, _Object$entries = Object.entries(filters); _i < _Object$entries.length; _i++) {
138
- var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
139
- key = _Object$entries$_i[0],
140
- value = _Object$entries$_i[1];
141
- if (!value) {
142
- continue;
143
- }
144
-
145
- // 处理日期范围字段
146
- if (key.endsWith("AtFrom") || key.endsWith("AtTo")) {
147
- var baseFieldName = key.replace(/(AtFrom|AtTo)$/, "");
148
- var _targetColumn = findTargetColumn("".concat(baseFieldName, "At"), table, relations);
149
- if (!_targetColumn) {
150
- continue;
151
- }
152
- if (key.endsWith("AtFrom")) {
153
- conditions.push(gte(_targetColumn, new Date(value)));
154
- } else if (key.endsWith("AtTo")) {
155
- conditions.push(lte(_targetColumn, new Date(value)));
156
- }
157
- continue;
158
- }
159
-
160
- // 处理其他普通过滤条件
161
- var targetColumn = findTargetColumn(key, table, relations);
162
- if (!targetColumn) {
163
- continue;
164
- }
165
- addCondition(conditions, key, value, targetColumn);
166
- }
167
- return conditions;
3
+ // ==============================================================================
4
+ // 列表查询(Postgres)
5
+ // ==============================================================================
6
+ // 实现在 get-list-query-core,两套方言共用。这里只给它两条 pg 专属的原语,外加把签名收紧到
7
+ // pg 的类型上 —— 核心为了同时喂 sqlite,db 那一栏是宽的,调用方不该跟着变宽。
8
+
9
+ import { ilike, sql } from "drizzle-orm";
10
+ import { createGetListQuery } from "./get-list-query-core.js";
11
+
12
+ // pattern 由核心转义好再传进来(见 escapeLikePattern)。pg 这边不用写 ESCAPE 子句:
13
+ // LIKE / ILIKE 默认就拿反斜杠当转义符,而模式串是绑定参数,不经过字符串字面量那套解析。
14
+ var run = createGetListQuery({
15
+ contains: function contains(column, pattern) {
16
+ return ilike(column, pattern);
17
+ },
18
+ // jsonb 数组展开成行再逐个 LIKE。列存的是 text[] 形状的 jsonb(见 createTableSchema 的
19
+ // jsonArrayFields),所以用 _text 那个变体,取到的 tag 直接就是字符串。
20
+ jsonArrayContains: function jsonArrayContains(column, pattern) {
21
+ return sql(_templateObject || (_templateObject = _taggedTemplateLiteral(["\n EXISTS (\n SELECT 1 FROM jsonb_array_elements_text(", ") tag\n WHERE tag LIKE ", "\n )\n "])), column, pattern);
168
22
  }
169
-
170
- // 构建排序
171
- function buildOrderBy(table, orderBy, orderDir) {
172
- var orderColumn = table[orderBy];
173
- return orderDir === "asc" ? asc(orderColumn) : desc(orderColumn);
174
- }
175
-
176
- // 构建基础查询
177
- var baseQuery = buildBaseQuery(table, fields, relations);
178
-
179
- // 构建条件。**作用域先于筛选加进去**,且它不受 buildConditions 里「空值跳过」那条规矩管:
180
- // 那条规矩对 `?name=` 这种空参数是对的,对归属则是灾难性的 —— 条件消失即全表可见。
181
- var scopeSql = scopeCondition(table, scope);
182
- var conditions = scopeSql ? [scopeSql] : [];
183
- conditions.push.apply(conditions, _toConsumableArray(buildConditions(filters, table, relations)));
184
-
185
- // 构建主查询
186
- var query = baseQuery.where(conditions.length > 0 ? and.apply(void 0, conditions) : undefined).orderBy(buildOrderBy(table, orderBy, orderDir))
187
-
188
- // @ts-ignore
189
- .limit(pageSize).offset((page - 1) * pageSize);
190
-
191
- // 构建计数查询(优化后)
192
- var countQuery = db.select({
193
- count: sql(_templateObject3 || (_templateObject3 = _taggedTemplateLiteral(["COUNT(DISTINCT ", ")"])), table.id)
194
- }).from(table).where(conditions.length > 0 ? and.apply(void 0, conditions) : undefined);
195
-
196
- // 添加关联
197
- if (relations !== null && relations !== void 0 && relations.length) {
198
- var _iterator2 = _createForOfIteratorHelper(relations),
199
- _step2;
200
- try {
201
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
202
- var _step2$value = _step2.value,
203
- relationTable = _step2$value.table,
204
- joinSql = _step2$value.sql;
205
- if (relationTable) {
206
- query.leftJoin(relationTable, joinSql);
207
- countQuery.leftJoin(relationTable, joinSql);
208
- }
209
- }
210
- } catch (err) {
211
- _iterator2.e(err);
212
- } finally {
213
- _iterator2.f();
214
- }
215
- }
216
-
217
- // console.log(query.prepare("a").getQuery());
218
-
219
- return {
220
- countQuery: countQuery,
221
- query: query
222
- };
23
+ });
24
+ export function getListQuery(args) {
25
+ return run(args);
223
26
  }
@@ -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";
@@ -3,4 +3,5 @@ export { createGetAction } from "./create-get-action.js";
3
3
  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
- export { getListQuery } from "./get-list-query.js";
6
+ export { getListQuery } from "./get-list-query.js";
7
+ export { MAX_PAGE_SIZE, normalizePaging } from "./paging.js";
@@ -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
+ };