@ai0x0/utils 0.6.1 → 0.7.1

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 (47) 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-get-action.js +11 -7
  4. package/es/backend/actions/create-get-list-action.js +11 -6
  5. package/es/backend/actions/get-list-data.d.ts +1 -1
  6. package/es/backend/actions/get-list-data.js +12 -5
  7. package/es/backend/actions/get-list-query-core.d.ts +26 -5
  8. package/es/backend/actions/get-list-query-core.js +92 -22
  9. package/es/backend/actions/get-list-query.d.ts +2 -0
  10. package/es/backend/actions/get-list-query.js +7 -4
  11. package/es/backend/actions/index.d.ts +1 -0
  12. package/es/backend/actions/index.js +2 -1
  13. package/es/backend/actions/paging.d.ts +11 -0
  14. package/es/backend/actions/paging.js +32 -0
  15. package/es/backend/route-operation/write-operation-core.js +12 -5
  16. package/es/backend/schemas/common.d.ts +2 -2
  17. package/es/backend/schemas/common.js +1 -1
  18. package/es/backend/sqlite/actions/create-get-action.js +9 -6
  19. package/es/backend/sqlite/actions/create-get-list-action.js +7 -5
  20. package/es/backend/sqlite/actions/get-list-data.d.ts +1 -1
  21. package/es/backend/sqlite/actions/get-list-data.js +11 -5
  22. package/es/backend/sqlite/actions/get-list-query.d.ts +2 -0
  23. package/es/backend/sqlite/actions/get-list-query.js +10 -6
  24. package/eslint-rules/api-route-via-nrf.js +83 -0
  25. package/eslint-rules/index.js +14 -1
  26. package/eslint-rules/no-direct-api-url.js +55 -0
  27. package/lib/backend/actions/create-get-action.js +3 -1
  28. package/lib/backend/actions/create-get-list-action.js +3 -1
  29. package/lib/backend/actions/get-list-data.d.ts +1 -1
  30. package/lib/backend/actions/get-list-data.js +8 -3
  31. package/lib/backend/actions/get-list-query-core.d.ts +26 -5
  32. package/lib/backend/actions/get-list-query-core.js +73 -18
  33. package/lib/backend/actions/get-list-query.d.ts +2 -0
  34. package/lib/backend/actions/get-list-query.js +3 -3
  35. package/lib/backend/actions/index.d.ts +1 -0
  36. package/lib/backend/actions/index.js +7 -2
  37. package/lib/backend/actions/paging.d.ts +11 -0
  38. package/lib/backend/actions/paging.js +50 -0
  39. package/lib/backend/schemas/common.d.ts +2 -2
  40. package/lib/backend/schemas/common.js +1 -1
  41. package/lib/backend/sqlite/actions/create-get-action.js +3 -1
  42. package/lib/backend/sqlite/actions/create-get-list-action.js +3 -1
  43. package/lib/backend/sqlite/actions/get-list-data.d.ts +1 -1
  44. package/lib/backend/sqlite/actions/get-list-data.js +8 -3
  45. package/lib/backend/sqlite/actions/get-list-query.d.ts +2 -0
  46. package/lib/backend/sqlite/actions/get-list-query.js +3 -3
  47. package/package.json +1 -1
@@ -6,6 +6,8 @@ import { type ListQueryParams } from "./get-list-query-core.js";
6
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 —— 写成可选的话,漏传就退化成查全表,
@@ -25,13 +25,13 @@ module.exports = __toCommonJS(get_list_query_exports);
25
25
  var import_drizzle_orm = require("drizzle-orm");
26
26
  var import_get_list_query_core = require("./get-list-query-core");
27
27
  var run = (0, import_get_list_query_core.createGetListQuery)({
28
- contains: (column, keyword) => (0, import_drizzle_orm.ilike)(column, `%${keyword}%`),
28
+ contains: (column, pattern) => (0, import_drizzle_orm.ilike)(column, pattern),
29
29
  // jsonb 数组展开成行再逐个 LIKE。列存的是 text[] 形状的 jsonb(见 createTableSchema 的
30
30
  // jsonArrayFields),所以用 _text 那个变体,取到的 tag 直接就是字符串。
31
- jsonArrayContains: (column, keyword) => import_drizzle_orm.sql`
31
+ jsonArrayContains: (column, pattern) => import_drizzle_orm.sql`
32
32
  EXISTS (
33
33
  SELECT 1 FROM jsonb_array_elements_text(${column}) tag
34
- WHERE tag LIKE ${`%${keyword}%`}
34
+ WHERE tag LIKE ${pattern}
35
35
  )
36
36
  `
37
37
  });
@@ -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
+ };
@@ -0,0 +1,50 @@
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, desc) => {
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: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+
19
+ // src/backend/actions/paging.ts
20
+ var paging_exports = {};
21
+ __export(paging_exports, {
22
+ DEFAULT_PAGE_SIZE: () => DEFAULT_PAGE_SIZE,
23
+ MAX_PAGE_SIZE: () => MAX_PAGE_SIZE,
24
+ normalizePaging: () => normalizePaging
25
+ });
26
+ module.exports = __toCommonJS(paging_exports);
27
+ var MAX_PAGE_SIZE = 1e4;
28
+ var DEFAULT_PAGE_SIZE = 10;
29
+ function toPositiveInt(value, fallback) {
30
+ const parsed = Math.floor(Number(value));
31
+ return Number.isFinite(parsed) && parsed >= 1 ? parsed : fallback;
32
+ }
33
+ function normalizePaging({
34
+ current,
35
+ pageSize
36
+ }) {
37
+ return {
38
+ current: toPositiveInt(current, 1),
39
+ pageSize: Math.min(
40
+ toPositiveInt(pageSize, DEFAULT_PAGE_SIZE),
41
+ MAX_PAGE_SIZE
42
+ )
43
+ };
44
+ }
45
+ // Annotate the CommonJS export names for ESM import in node:
46
+ 0 && (module.exports = {
47
+ DEFAULT_PAGE_SIZE,
48
+ MAX_PAGE_SIZE,
49
+ normalizePaging
50
+ });
@@ -32,7 +32,7 @@ export declare const BASIC_UPDATE_OMIT: {
32
32
  readonly createdAt: true;
33
33
  readonly updatedAt: true;
34
34
  };
35
- export declare const queryListSchema: <Incoming extends z.ZodObject<z.core.$ZodLooseShape, z.core.$strip>>(schema: Incoming) => z.ZodObject<(("orderBy" | "orderDir" | "pageSize" | "creatorId" | "current" | "createdAtFrom" | "createdAtTo") & keyof Incoming["shape"] extends never ? {
35
+ export declare const queryListSchema: <Incoming extends z.ZodObject<z.core.$ZodLooseShape, z.core.$strip>>(schema: Incoming) => z.ZodObject<(("current" | "pageSize" | "orderBy" | "orderDir" | "creatorId" | "createdAtFrom" | "createdAtTo") & keyof Incoming["shape"] extends never ? {
36
36
  current: z.ZodDefault<z.ZodOptional<z.ZodString>>;
37
37
  pageSize: z.ZodDefault<z.ZodOptional<z.ZodString>>;
38
38
  createdAtFrom: z.ZodOptional<z.ZodString>;
@@ -65,7 +65,7 @@ export declare const queryListSchema: <Incoming extends z.ZodObject<z.core.$ZodL
65
65
  asc: "asc";
66
66
  desc: "desc";
67
67
  }>>;
68
- }[K]; } : never) & (Incoming["shape"] extends infer T_2 extends z.core.util.SomeObject ? { [K_1 in keyof T_2]: Incoming["shape"][K_1]; } : never)) extends infer T ? { [k in keyof T]: (("orderBy" | "orderDir" | "pageSize" | "creatorId" | "current" | "createdAtFrom" | "createdAtTo") & keyof Incoming["shape"] extends never ? {
68
+ }[K]; } : never) & (Incoming["shape"] extends infer T_2 extends z.core.util.SomeObject ? { [K_1 in keyof T_2]: Incoming["shape"][K_1]; } : never)) extends infer T ? { [k in keyof T]: (("current" | "pageSize" | "orderBy" | "orderDir" | "creatorId" | "createdAtFrom" | "createdAtTo") & keyof Incoming["shape"] extends never ? {
69
69
  current: z.ZodDefault<z.ZodOptional<z.ZodString>>;
70
70
  pageSize: z.ZodDefault<z.ZodOptional<z.ZodString>>;
71
71
  createdAtFrom: z.ZodOptional<z.ZodString>;
@@ -46,7 +46,7 @@ var BASIC_UPDATE_OMIT = {
46
46
  var queryListSchema = (schema) => import_zod.z.object({
47
47
  current: import_zod.z.string().optional().default("1").describe("Page number, starting at 1."),
48
48
  pageSize: import_zod.z.string().optional().default("10").describe(
49
- "Rows per page. Omitting it yields only 10 — pass a larger value to get the whole set."
49
+ "Rows per page. Omitting it yields only 10 — pass a larger value to get the whole set. Capped at 10000; compare `total` against what you received to tell whether more remain."
50
50
  ),
51
51
  createdAtFrom: import_zod.z.string().optional().describe(
52
52
  "Keep only rows created at or after this instant (ISO 8601, e.g. 2026-08-01T00:00:00Z)."
@@ -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 Object.keys(bodySchema.shape)) {
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
  scope,
@@ -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 Object.keys(bodySchema.shape)) {
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
  scope,
@@ -1,5 +1,5 @@
1
1
  import { getListQuery } from "./get-list-query.js";
2
- export declare function getListData({ query, countQuery, pageSize, current, }: {
2
+ export declare function getListData({ query, countQuery, pageSize: rawPageSize, current: rawCurrent, }: {
3
3
  bodySchema?: any;
4
4
  current?: number;
5
5
  pageSize?: number;
@@ -22,6 +22,7 @@ __export(get_list_data_exports, {
22
22
  getListData: () => getListData
23
23
  });
24
24
  module.exports = __toCommonJS(get_list_data_exports);
25
+ var import_paging = require("../../actions/paging");
25
26
  async function runSelect(query) {
26
27
  const executable = query;
27
28
  if (executable.all) {
@@ -35,11 +36,15 @@ async function runSelect(query) {
35
36
  function getListData({
36
37
  query,
37
38
  countQuery,
38
- pageSize = 10,
39
- current = 1
39
+ pageSize: rawPageSize,
40
+ current: rawCurrent
40
41
  }) {
42
+ const { current, pageSize } = (0, import_paging.normalizePaging)({
43
+ current: rawCurrent,
44
+ pageSize: rawPageSize
45
+ });
41
46
  return async () => {
42
- const dataQuery = query.limit(+pageSize).offset((+current - 1) * +pageSize);
47
+ const dataQuery = query.limit(pageSize).offset((current - 1) * pageSize);
43
48
  const data = await runSelect(dataQuery);
44
49
  const [{ count }] = await runSelect(countQuery);
45
50
  return { data, total: Number(count) };
@@ -5,6 +5,8 @@ import { type ListQueryParams } from "../../actions/get-list-query-core.js";
5
5
  export declare function getListQuery<TTable extends BaseTable, TSelection extends SelectedFields>(args: {
6
6
  db: any;
7
7
  fields: TSelection;
8
+ /** 允许被当成筛选条件的键。不传等于不限制,见核心里的说明。 */
9
+ filterable?: readonly string[];
8
10
  jsonArrayFields?: string[];
9
11
  /** 必填。与 pg 那套同义,见 backend/scope.ts。 */
10
12
  scope: ScopeArg;
@@ -25,13 +25,13 @@ module.exports = __toCommonJS(get_list_query_exports);
25
25
  var import_drizzle_orm = require("drizzle-orm");
26
26
  var import_get_list_query_core = require("../../actions/get-list-query-core");
27
27
  var run = (0, import_get_list_query_core.createGetListQuery)({
28
- contains: (column, keyword) => (0, import_drizzle_orm.like)(column, `%${keyword}%`),
28
+ contains: (column, pattern) => import_drizzle_orm.sql`${column} LIKE ${pattern} ESCAPE '\\'`,
29
29
  // json_each 展开出来的行里,值在 tag.value 上(pg 那个 _text 变体是直接给字符串的)——
30
30
  // 这是两边唯一一处连字段名都不同的地方。
31
- jsonArrayContains: (column, keyword) => import_drizzle_orm.sql`
31
+ jsonArrayContains: (column, pattern) => import_drizzle_orm.sql`
32
32
  EXISTS (
33
33
  SELECT 1 FROM json_each(${column}) tag
34
- WHERE tag.value LIKE ${`%${keyword}%`}
34
+ WHERE tag.value LIKE ${pattern} ESCAPE '\\'
35
35
  )
36
36
  `
37
37
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai0x0/utils",
3
- "version": "0.6.1",
3
+ "version": "0.7.1",
4
4
  "description": "AI0x0 utils",
5
5
  "keywords": [
6
6
  "ai0x0"