@ai0x0/utils 0.0.49 → 0.1.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 (86) hide show
  1. package/.agents/skills/ai0x0-utils/backend/SKILL.md +35 -11
  2. package/README.md +3 -3
  3. package/SKILL.md +35 -11
  4. package/es/backend/actions/create-delete-action.d.ts +4 -2
  5. package/es/backend/actions/create-delete-action.js +22 -28
  6. package/es/backend/actions/create-get-action.d.ts +4 -1
  7. package/es/backend/actions/create-get-action.js +10 -10
  8. package/es/backend/actions/create-get-list-action.d.ts +4 -1
  9. package/es/backend/actions/create-get-list-action.js +9 -9
  10. package/es/backend/actions/create-put-action.d.ts +3 -2
  11. package/es/backend/actions/create-put-action.js +23 -38
  12. package/es/backend/actions/get-list-query.d.ts +8 -1
  13. package/es/backend/actions/get-list-query.js +19 -8
  14. package/es/backend/index.d.ts +1 -0
  15. package/es/backend/index.js +2 -1
  16. package/es/backend/route-operation/delete-operation.d.ts +3 -1
  17. package/es/backend/route-operation/get-list-operation.d.ts +3 -1
  18. package/es/backend/route-operation/get-operation.d.ts +3 -1
  19. package/es/backend/route-operation/operation-common.d.ts +80 -8
  20. package/es/backend/route-operation/operation-common.js +216 -67
  21. package/es/backend/route-operation/post-operation.d.ts +3 -1
  22. package/es/backend/route-operation/put-operation.d.ts +3 -1
  23. package/es/backend/route-operation/read-operation-core.d.ts +17 -14
  24. package/es/backend/route-operation/read-operation-core.js +42 -34
  25. package/es/backend/route-operation/write-operation-core.d.ts +25 -22
  26. package/es/backend/route-operation/write-operation-core.js +195 -165
  27. package/es/backend/schemas/index.d.ts +240 -240
  28. package/es/backend/scope.d.ts +27 -0
  29. package/es/backend/scope.js +60 -0
  30. package/es/backend/sqlite/actions/create-delete-action.d.ts +4 -2
  31. package/es/backend/sqlite/actions/create-delete-action.js +21 -28
  32. package/es/backend/sqlite/actions/create-get-action.d.ts +4 -1
  33. package/es/backend/sqlite/actions/create-get-action.js +9 -7
  34. package/es/backend/sqlite/actions/create-get-list-action.d.ts +4 -1
  35. package/es/backend/sqlite/actions/create-get-list-action.js +8 -6
  36. package/es/backend/sqlite/actions/create-put-action.d.ts +3 -2
  37. package/es/backend/sqlite/actions/create-put-action.js +20 -38
  38. package/es/backend/sqlite/actions/get-list-query.d.ts +4 -1
  39. package/es/backend/sqlite/actions/get-list-query.js +8 -3
  40. package/es/backend/sqlite/route-operation/delete-operation.d.ts +3 -1
  41. package/es/backend/sqlite/route-operation/get-list-operation.d.ts +3 -1
  42. package/es/backend/sqlite/route-operation/get-operation.d.ts +3 -1
  43. package/es/backend/sqlite/route-operation/post-operation.d.ts +3 -1
  44. package/es/backend/sqlite/route-operation/put-operation.d.ts +3 -1
  45. package/lib/backend/actions/create-delete-action.d.ts +4 -2
  46. package/lib/backend/actions/create-delete-action.js +10 -16
  47. package/lib/backend/actions/create-get-action.d.ts +4 -1
  48. package/lib/backend/actions/create-get-action.js +2 -1
  49. package/lib/backend/actions/create-get-list-action.d.ts +4 -1
  50. package/lib/backend/actions/create-get-list-action.js +2 -1
  51. package/lib/backend/actions/create-put-action.d.ts +3 -2
  52. package/lib/backend/actions/create-put-action.js +10 -21
  53. package/lib/backend/actions/get-list-query.d.ts +8 -1
  54. package/lib/backend/actions/get-list-query.js +11 -4
  55. package/lib/backend/index.d.ts +1 -0
  56. package/lib/backend/index.js +3 -1
  57. package/lib/backend/route-operation/delete-operation.d.ts +3 -1
  58. package/lib/backend/route-operation/get-list-operation.d.ts +3 -1
  59. package/lib/backend/route-operation/get-operation.d.ts +3 -1
  60. package/lib/backend/route-operation/operation-common.d.ts +80 -8
  61. package/lib/backend/route-operation/operation-common.js +57 -7
  62. package/lib/backend/route-operation/post-operation.d.ts +3 -1
  63. package/lib/backend/route-operation/put-operation.d.ts +3 -1
  64. package/lib/backend/route-operation/read-operation-core.d.ts +17 -14
  65. package/lib/backend/route-operation/read-operation-core.js +12 -6
  66. package/lib/backend/route-operation/write-operation-core.d.ts +25 -22
  67. package/lib/backend/route-operation/write-operation-core.js +52 -16
  68. package/lib/backend/schemas/index.d.ts +240 -240
  69. package/lib/backend/scope.d.ts +27 -0
  70. package/lib/backend/scope.js +57 -0
  71. package/lib/backend/sqlite/actions/create-delete-action.d.ts +4 -2
  72. package/lib/backend/sqlite/actions/create-delete-action.js +10 -16
  73. package/lib/backend/sqlite/actions/create-get-action.d.ts +4 -1
  74. package/lib/backend/sqlite/actions/create-get-action.js +2 -1
  75. package/lib/backend/sqlite/actions/create-get-list-action.d.ts +4 -1
  76. package/lib/backend/sqlite/actions/create-get-list-action.js +2 -1
  77. package/lib/backend/sqlite/actions/create-put-action.d.ts +3 -2
  78. package/lib/backend/sqlite/actions/create-put-action.js +10 -21
  79. package/lib/backend/sqlite/actions/get-list-query.d.ts +4 -1
  80. package/lib/backend/sqlite/actions/get-list-query.js +5 -1
  81. package/lib/backend/sqlite/route-operation/delete-operation.d.ts +3 -1
  82. package/lib/backend/sqlite/route-operation/get-list-operation.d.ts +3 -1
  83. package/lib/backend/sqlite/route-operation/get-operation.d.ts +3 -1
  84. package/lib/backend/sqlite/route-operation/post-operation.d.ts +3 -1
  85. package/lib/backend/sqlite/route-operation/put-operation.d.ts +3 -1
  86. package/package.json +38 -1
@@ -1,13 +1,16 @@
1
1
  import { z, ZodSchema } from "zod";
2
2
  import { BaseTable, GetListRelations } from "../types.js";
3
3
  import { NodePgDatabase } from "drizzle-orm/node-postgres";
4
+ import { ScopeArg } from "../scope.js";
4
5
  export declare function createGetListAction<T extends ZodSchema, TTable extends BaseTable>({ bodySchema, db, jsonArrayFields, relations, table, }: {
5
6
  bodySchema: T;
6
7
  db: NodePgDatabase<Record<string, unknown>>;
7
8
  jsonArrayFields?: string[];
8
9
  relations?: GetListRelations;
9
10
  table: TTable;
10
- }): (params: Partial<z.infer<T>> & Record<string, unknown>) => Promise<{
11
+ }): (params: Partial<z.infer<T>> & Record<string, unknown>, { scope }: {
12
+ scope: ScopeArg;
13
+ }) => Promise<{
11
14
  data: any;
12
15
  total: number;
13
16
  }>;
@@ -31,7 +31,7 @@ function createGetListAction({
31
31
  relations,
32
32
  table
33
33
  }) {
34
- return async (params) => {
34
+ return async (params, { scope }) => {
35
35
  const { current, pageSize, ...other } = params;
36
36
  const fields = {};
37
37
  for (const key of Object.keys(bodySchema.shape)) {
@@ -46,6 +46,7 @@ function createGetListAction({
46
46
  jsonArrayFields,
47
47
  params: other,
48
48
  relations,
49
+ scope,
49
50
  table
50
51
  });
51
52
  return (0, import_get_list_data.getListData)({
@@ -1,10 +1,11 @@
1
1
  import { z, ZodSchema } from "zod";
2
2
  import { NodePgDatabase } from "drizzle-orm/node-postgres";
3
3
  import { BaseTable } from "../types.js";
4
+ import { ScopeArg } from "../scope.js";
4
5
  export declare function createPutAction<T extends ZodSchema, TTable extends BaseTable>({ db, table, }: {
5
6
  bodySchema: T;
6
7
  db: NodePgDatabase<Record<string, unknown>>;
7
8
  table: TTable;
8
- }): (body: Partial<z.infer<T>> & Record<string, unknown>, { byCreator, }?: {
9
- byCreator?: boolean | undefined;
9
+ }): (body: Partial<z.infer<T>> & Record<string, unknown>, { scope }: {
10
+ scope: ScopeArg;
10
11
  }) => Promise<z.infer<T>>;
@@ -22,35 +22,24 @@ __export(create_put_action_exports, {
22
22
  createPutAction: () => createPutAction
23
23
  });
24
24
  module.exports = __toCommonJS(create_put_action_exports);
25
- var import_zod = require("zod");
26
25
  var import_transform_body = require("./transform-body");
27
26
  var import_drizzle_orm = require("drizzle-orm");
28
- var import_backend = require("..");
27
+ var import_scope = require("../scope");
29
28
  var import_errors = require("../errors");
30
29
  function createPutAction({
31
30
  db,
32
31
  table
33
32
  }) {
34
- return async (body, {
35
- byCreator = true
36
- } = {}) => {
37
- if (byCreator) {
38
- const data2 = await (0, import_backend.createGetAction)({
39
- db,
40
- table,
41
- bodySchema: import_zod.z.object({
42
- creatorId: import_zod.z.string(),
43
- id: import_zod.z.string()
44
- })
45
- })({
46
- creatorId: body.editorId,
47
- id: body.id
48
- });
49
- if (!data2) {
50
- throw new import_errors.HttpError(404, "未找到编辑对象,或没有权限");
51
- }
33
+ return async (body, { scope }) => {
34
+ const id = body.id;
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, "未找到编辑对象,或没有权限");
52
42
  }
53
- const [data] = await db.update(table).set((0, import_transform_body.transformBody)(body)).where((0, import_drizzle_orm.eq)(table.id, body.id)).returning();
54
43
  return data;
55
44
  };
56
45
  }
@@ -2,10 +2,17 @@ import { BaseTable, GetListRelations } from "../types.js";
2
2
  import { SelectedFields } from "drizzle-orm/pg-core/query-builders/select.types";
3
3
  import { SQL } from "drizzle-orm";
4
4
  import { NodePgDatabase } from "drizzle-orm/node-postgres";
5
- export declare function getListQuery<TTable extends BaseTable, TSelection extends SelectedFields>({ db, fields, jsonArrayFields, params, relations, table, }: {
5
+ import { ScopeArg } from "../scope.js";
6
+ export declare function getListQuery<TTable extends BaseTable, TSelection extends SelectedFields>({ db, fields, jsonArrayFields, params, relations, scope, table, }: {
6
7
  db: NodePgDatabase<Record<string, unknown>>;
7
8
  fields: TSelection;
8
9
  jsonArrayFields?: string[];
10
+ /**
11
+ * 行级作用域。**必填**,不想隔离就显式传 NO_SCOPE —— 写成可选的话,漏传就退化成查全表,
12
+ * 而那是静默的越权(见 scope.ts)。它不走下面 params 那条筛选通道,因为那条的规矩是
13
+ * 「空值跳过」,一个取不到值的作用域会让整条 where 消失。
14
+ */
15
+ scope: ScopeArg;
9
16
  params: {
10
17
  [field: string]: unknown;
11
18
  orderBy?: keyof BaseTable;
@@ -23,12 +23,14 @@ __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 import_scope = require("../scope");
26
27
  function getListQuery({
27
28
  db,
28
29
  fields,
29
30
  jsonArrayFields,
30
31
  params,
31
32
  relations,
33
+ scope,
32
34
  table
33
35
  }) {
34
36
  const {
@@ -109,8 +111,9 @@ function getListQuery({
109
111
  function buildConditions(filters2, table2, relations2) {
110
112
  const conditions2 = [];
111
113
  for (const [key, value] of Object.entries(filters2)) {
112
- if (!value)
114
+ if (!value) {
113
115
  continue;
116
+ }
114
117
  if (key.endsWith("AtFrom") || key.endsWith("AtTo")) {
115
118
  const baseFieldName = key.replace(/(AtFrom|AtTo)$/, "");
116
119
  const targetColumn2 = findTargetColumn(
@@ -118,8 +121,9 @@ function getListQuery({
118
121
  table2,
119
122
  relations2
120
123
  );
121
- if (!targetColumn2)
124
+ if (!targetColumn2) {
122
125
  continue;
126
+ }
123
127
  if (key.endsWith("AtFrom")) {
124
128
  conditions2.push((0, import_drizzle_orm.gte)(targetColumn2, new Date(value)));
125
129
  } else if (key.endsWith("AtTo")) {
@@ -132,8 +136,9 @@ function getListQuery({
132
136
  table2,
133
137
  relations2
134
138
  );
135
- if (!targetColumn)
139
+ if (!targetColumn) {
136
140
  continue;
141
+ }
137
142
  addCondition(conditions2, key, value, targetColumn);
138
143
  }
139
144
  return conditions2;
@@ -143,7 +148,9 @@ function getListQuery({
143
148
  return orderDir2 === "asc" ? (0, import_drizzle_orm.asc)(orderColumn) : (0, import_drizzle_orm.desc)(orderColumn);
144
149
  }
145
150
  const baseQuery = buildBaseQuery(table, fields, relations);
146
- const conditions = buildConditions(filters, table, relations);
151
+ const scopeSql = (0, import_scope.scopeCondition)(table, scope);
152
+ const conditions = scopeSql ? [scopeSql] : [];
153
+ conditions.push(...buildConditions(filters, table, relations));
147
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);
148
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);
149
156
  if (relations == null ? void 0 : relations.length) {
@@ -2,3 +2,4 @@ export * from "./actions/index.js";
2
2
  export * from "./route-operation/index.js";
3
3
  export * from "./schemas/index.js";
4
4
  export * from "./errors.js";
5
+ export * from "./scope.js";
@@ -20,10 +20,12 @@ __reExport(backend_exports, require("./actions"), module.exports);
20
20
  __reExport(backend_exports, require("./route-operation"), module.exports);
21
21
  __reExport(backend_exports, require("./schemas"), module.exports);
22
22
  __reExport(backend_exports, require("./errors"), module.exports);
23
+ __reExport(backend_exports, require("./scope"), module.exports);
23
24
  // Annotate the CommonJS export names for ESM import in node:
24
25
  0 && (module.exports = {
25
26
  ...require("./actions"),
26
27
  ...require("./route-operation"),
27
28
  ...require("./schemas"),
28
- ...require("./errors")
29
+ ...require("./errors"),
30
+ ...require("./scope")
29
31
  });
@@ -11,4 +11,6 @@ export declare const createDeleteOperation: ({ db, getSession, }: {
11
11
  } | undefined>;
12
12
  }) => <B extends ZodSchema<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>> = import("zod").ZodObject<{
13
13
  id: import("zod").ZodString;
14
- }, import("zod/v4/core").$strip>>({ table, schemas, access, openApiOperation, handler, catch: catchHandler, }: CoreDeleteOperationOptions<BaseTable, B>) => import("next-rest-framework").RouteOperationDefinition<"DELETE">;
14
+ }, import("zod/v4/core").$strip>>({ table, schemas, access, openApiOperation, handler, catch: catchHandler, }: CoreDeleteOperationOptions<BaseTable, B, {
15
+ userId?: string | undefined;
16
+ }>) => import("next-rest-framework").RouteOperationDefinition<"DELETE">;
@@ -9,4 +9,6 @@ export declare const createGetListOperation: ({ db, getSession, }: {
9
9
  getSession(req: NextRequest): Promise<{
10
10
  userId?: string;
11
11
  } | undefined>;
12
- }) => <T extends ZodSchema<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>, Q extends ZodSchema<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>>({ schemas, access, table, openApiOperation, relations, jsonArrayFields, setParams, handler, catch: catchHandler, }: CoreGetListOperationOptions<T, Q, BaseTable, GetListRelations>) => import("next-rest-framework").RouteOperationDefinition<"GET">;
12
+ }) => <T extends ZodSchema<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>, Q extends ZodSchema<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>>({ schemas, access, table, openApiOperation, relations, jsonArrayFields, setParams, handler, catch: catchHandler, }: CoreGetListOperationOptions<T, Q, BaseTable, GetListRelations, {
13
+ userId?: string | undefined;
14
+ }>) => import("next-rest-framework").RouteOperationDefinition<"GET">;
@@ -9,4 +9,6 @@ export declare const createGetOperation: ({ getSession, db, }: {
9
9
  getSession(req: NextRequest): Promise<{
10
10
  userId?: string;
11
11
  } | undefined>;
12
- }) => <T extends ZodSchema<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>, Q extends ZodSchema<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>>({ schemas, access, table, openApiOperation, relations, setParams, jsonArrayFields, handler, catch: catchHandler, }: CoreGetOperationOptions<T, Q, BaseTable, GetListRelations>) => import("next-rest-framework").RouteOperationDefinition<"GET">;
12
+ }) => <T extends ZodSchema<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>, Q extends ZodSchema<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>>({ schemas, access, table, openApiOperation, relations, setParams, jsonArrayFields, handler, catch: catchHandler, }: CoreGetOperationOptions<T, Q, BaseTable, GetListRelations, {
13
+ userId?: string | undefined;
14
+ }>) => import("next-rest-framework").RouteOperationDefinition<"GET">;
@@ -1,11 +1,75 @@
1
1
  import { NextRequest } from "next/server";
2
2
  import { TypedNextRequest, TypedNextResponse } from "next-rest-framework";
3
3
  import { z, ZodSchema } from "zod";
4
+ import { ScopeArg, ScopeValue } from "../scope.js";
4
5
  export type JsonResponse = ReturnType<(typeof TypedNextResponse)["json"]>;
5
6
  export type RouteCatch = (error: Error) => Promise<JsonResponse | undefined>;
6
- export type SessionGetter = (req: NextRequest) => Promise<{
7
+ /** 默认的会话形状。够用于「一条记录属于一个人」的场景。 */
8
+ export interface DefaultSession {
7
9
  userId?: string;
8
- } | undefined>;
10
+ }
11
+ /**
12
+ * 会话读取器。泛型化是为了让调用方能在这里一次性解析出「你是谁 + 你现在站在哪个空间 +
13
+ * 你在那个空间里是什么角色」,后面 access.scope / access.can 都读同一份,不必各自再查一遍库。
14
+ */
15
+ export type SessionGetter<TSession = DefaultSession> = (req: NextRequest) => Promise<TSession | undefined>;
16
+ export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
17
+ /** can 返回它表示拒绝,可自定状态码与文案;返回 false 等价于 { status: 403 }。 */
18
+ export interface AccessDenial {
19
+ status?: number;
20
+ message?: string;
21
+ }
22
+ export interface AccessOptions<TSession = DefaultSession> {
23
+ /**
24
+ * 老写法,等价于 `scope: { column: "creatorId", value: (s) => s.userId }`。
25
+ * 与 scope 同时给时以 scope 为准。
26
+ */
27
+ byCreator?: boolean;
28
+ /**
29
+ * 行级作用域:这次请求只能看到 / 只能动 `column` 等于(或属于)`value` 的那些行。
30
+ * column 不填默认 "creatorId"。
31
+ *
32
+ * value 拿不到值(会话没有、或返回空)时请求会被拒绝,**不会**退化成「不筛」——
33
+ * 那正是这套东西存在的意义,见 scope.ts 的说明。
34
+ */
35
+ scope?: {
36
+ column?: string;
37
+ value: (session: TSession) => ScopeValue | undefined;
38
+ };
39
+ /**
40
+ * 准入判断。作用域管「能看到哪些行」,这里管「能不能做这个动作」——比如只读角色不能写。
41
+ * 收在一处而不是散在各路由里:多一档角色时,散着写必然漏。
42
+ */
43
+ can?: (_ctx: {
44
+ session: TSession;
45
+ method: HttpMethod;
46
+ }) => boolean | AccessDenial | Promise<boolean | AccessDenial>;
47
+ }
48
+ /**
49
+ * 把会话读取包成「本次请求最多取一次」的取值器。
50
+ *
51
+ * 会话在一次请求里可能被好几处要:作用域要它算归属、can 要它判角色、PUT 还要它填 editorId。
52
+ * 各自去调 getSession 就是重复往返;预先无条件取一次,则公开端点(关掉隔离的那些)要白付一次
53
+ * 读取。包一层惰性缓存之后两个问题一起没了 —— 谁要谁调,取不取由用不用决定,不必谁去传一个
54
+ * 「要不要取」的开关。
55
+ */
56
+ export declare function sessionLoader<TSession>(getSession: SessionGetter<TSession>, req: NextRequest): () => Promise<TSession | undefined>;
57
+ /**
58
+ * 解析这次请求的会话、作用域与准入。
59
+ *
60
+ * **失败一律抛,不降级。** 会话拿不到就是 401、作用域取不到值就是 403 —— 绝不把一个空值
61
+ * 往下传。这条是本次改动的核心:在此之前空值会一路流到查询构造器,被「空值跳过筛选」那条
62
+ * 规则吃掉,结果是列表返回全表、删除按 id 删任意行。
63
+ */
64
+ export declare function resolveAccess<TSession>({ access, loadSession, method, }: {
65
+ access?: AccessOptions<TSession>;
66
+ /** 见 sessionLoader —— 传取值器而不是 (getSession, req),取不取由这里自己决定。 */
67
+ loadSession: () => Promise<TSession | undefined>;
68
+ method: HttpMethod;
69
+ }): Promise<{
70
+ scope: ScopeArg;
71
+ session: TSession | undefined;
72
+ }>;
9
73
  export type ActionFactory<TTable, TAction> = (_options: {
10
74
  bodySchema?: ZodSchema;
11
75
  db: unknown;
@@ -25,14 +89,22 @@ export declare function getDefaultTags<TTable>({ getTableName, table, }: {
25
89
  export type GetOperationParams<Q extends ZodSchema> = z.infer<Q> & Record<string, unknown> & {
26
90
  creatorId?: string;
27
91
  };
28
- export declare function getReadParams<Q extends ZodSchema>({ access, getSession, req, schemas, setParams, }: {
29
- access?: {
30
- byCreator?: boolean;
31
- };
32
- getSession: SessionGetter;
92
+ /**
93
+ * 读操作的参数与作用域。
94
+ *
95
+ * 注意作用域**不进 params**:params 会被查询构造器当筛选条件处理,而那里的规矩是「空值跳过」。
96
+ * 作用域走单独一条通道交给 action,见 scope.ts 顶部第 1 条。
97
+ */
98
+ export declare function getReadParams<Q extends ZodSchema, TSession>({ access, getSession, req, schemas, setParams, }: {
99
+ access?: AccessOptions<TSession>;
100
+ getSession: SessionGetter<TSession>;
33
101
  req: NextRequest;
34
102
  schemas: {
35
103
  query: Q;
36
104
  };
37
105
  setParams?: (_req: TypedNextRequest<"GET", "application/json", unknown, z.infer<Q>>) => Promise<Record<string, unknown>>;
38
- }): Promise<GetOperationParams<Q>>;
106
+ }): Promise<{
107
+ params: GetOperationParams<Q>;
108
+ scope: ScopeArg;
109
+ session: TSession | undefined;
110
+ }>;
@@ -22,10 +22,53 @@ __export(operation_common_exports, {
22
22
  getDefaultTags: () => getDefaultTags,
23
23
  getReadParams: () => getReadParams,
24
24
  handleOperationError: () => handleOperationError,
25
- readPostBody: () => readPostBody
25
+ readPostBody: () => readPostBody,
26
+ resolveAccess: () => resolveAccess,
27
+ sessionLoader: () => sessionLoader
26
28
  });
27
29
  module.exports = __toCommonJS(operation_common_exports);
28
30
  var import_next_rest_framework = require("next-rest-framework");
31
+ var import_errors = require("../errors");
32
+ var import_scope = require("../scope");
33
+ var DEFAULT_SCOPE_COLUMN = "creatorId";
34
+ function sessionLoader(getSession, req) {
35
+ let pending;
36
+ return () => pending ?? (pending = getSession(req));
37
+ }
38
+ async function resolveAccess({
39
+ access,
40
+ loadSession,
41
+ method
42
+ }) {
43
+ var _a;
44
+ const wantsScope = Boolean(access == null ? void 0 : access.scope) || ((access == null ? void 0 : access.byCreator) ?? true);
45
+ const session = wantsScope || (access == null ? void 0 : access.can) ? await loadSession() : void 0;
46
+ if (access == null ? void 0 : access.can) {
47
+ if (!session) {
48
+ throw new import_errors.HttpError(401, "未登录");
49
+ }
50
+ const verdict = await access.can({ method, session });
51
+ if (verdict !== true) {
52
+ const denial = typeof verdict === "object" ? verdict : {};
53
+ throw new import_errors.HttpError(denial.status ?? 403, denial.message ?? "没有权限");
54
+ }
55
+ }
56
+ if (!wantsScope) {
57
+ return { scope: import_scope.NO_SCOPE, session };
58
+ }
59
+ if (!session) {
60
+ throw new import_errors.HttpError(401, "未登录");
61
+ }
62
+ const column = ((_a = access == null ? void 0 : access.scope) == null ? void 0 : _a.column) ?? DEFAULT_SCOPE_COLUMN;
63
+ const value = (access == null ? void 0 : access.scope) ? access.scope.value(session) : session.userId;
64
+ if ((0, import_scope.isEmptyScopeValue)(value)) {
65
+ throw new import_errors.HttpError(
66
+ 403,
67
+ `无法确定这次请求的归属(${column})。这不是「不做隔离」——请求被拒绝。`
68
+ );
69
+ }
70
+ return { scope: { column, value }, session };
71
+ }
29
72
  function isHttpLikeError(error) {
30
73
  return error instanceof Error && error.name === "HttpError" && typeof error.status === "number";
31
74
  }
@@ -72,19 +115,26 @@ async function getReadParams({
72
115
  const extraParams = await (setParams == null ? void 0 : setParams(
73
116
  req
74
117
  )) || {};
75
- if ((access == null ? void 0 : access.byCreator) ?? true) {
76
- const { userId } = await getSession(req) || {};
77
- extraParams.creatorId = userId;
78
- }
118
+ const { scope, session } = await resolveAccess({
119
+ access,
120
+ loadSession: sessionLoader(getSession, req),
121
+ method: "GET"
122
+ });
79
123
  const queryParams = schemas.query.parse(
80
124
  Object.fromEntries(new URL(req.url).searchParams)
81
125
  );
82
- return { ...queryParams, ...extraParams };
126
+ return {
127
+ params: { ...queryParams, ...extraParams },
128
+ scope,
129
+ session
130
+ };
83
131
  }
84
132
  // Annotate the CommonJS export names for ESM import in node:
85
133
  0 && (module.exports = {
86
134
  getDefaultTags,
87
135
  getReadParams,
88
136
  handleOperationError,
89
- readPostBody
137
+ readPostBody,
138
+ resolveAccess,
139
+ sessionLoader
90
140
  });
@@ -9,4 +9,6 @@ export declare const createPostOperation: ({ getSession, db, }: {
9
9
  getSession(req: NextRequest): Promise<{
10
10
  userId?: string;
11
11
  } | undefined>;
12
- }) => <IB extends ZodSchema<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>, OB extends ZodSchema<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>>({ schemas, contentType, openApiOperation, parseBody, setBody, table, handler, catch: catchHandler, }: CorePostOperationOptions<IB, OB, PgTable<import("drizzle-orm/pg-core").TableConfig>>) => import("next-rest-framework").RouteOperationDefinition<"POST">;
12
+ }) => <IB extends ZodSchema<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>, OB extends ZodSchema<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>>({ schemas, access, contentType, openApiOperation, parseBody, setBody, table, handler, catch: catchHandler, }: CorePostOperationOptions<IB, OB, PgTable<import("drizzle-orm/pg-core").TableConfig>, {
13
+ userId?: string | undefined;
14
+ }>) => import("next-rest-framework").RouteOperationDefinition<"POST">;
@@ -9,4 +9,6 @@ export declare const createPutOperation: ({ getSession, db, }: {
9
9
  userId?: string;
10
10
  } | undefined>;
11
11
  db?: NodePgDatabase<Record<string, unknown>> | undefined;
12
- }) => <IB extends ZodSchema<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>, OB extends ZodSchema<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>>({ schemas, access, openApiOperation, table, setBody, handler, catch: catchHandler, }: CorePutOperationOptions<IB, OB, PgTable<import("drizzle-orm/pg-core").TableConfig>>) => import("next-rest-framework").RouteOperationDefinition<"PUT">;
12
+ }) => <IB extends ZodSchema<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>, OB extends ZodSchema<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>>({ schemas, access, openApiOperation, table, setBody, handler, catch: catchHandler, }: CorePutOperationOptions<IB, OB, PgTable<import("drizzle-orm/pg-core").TableConfig>, {
13
+ userId?: string | undefined;
14
+ }>) => import("next-rest-framework").RouteOperationDefinition<"PUT">;
@@ -2,15 +2,14 @@ import { NextRequest } from "next/server";
2
2
  import { TypedNextRequest } from "next-rest-framework";
3
3
  import { z, ZodSchema } from "zod";
4
4
  import { type RouteOpenApiOperation } from "./open-api-operation.js";
5
- import { ActionFactory, GetOperationParams, RouteCatch, SessionGetter } from "./operation-common.js";
6
- export interface GetOperationOptions<T extends ZodSchema, Q extends ZodSchema, TTable, TRelations> {
5
+ import { AccessOptions, ActionFactory, DefaultSession, GetOperationParams, RouteCatch, SessionGetter } from "./operation-common.js";
6
+ import { ScopeArg } from "../scope.js";
7
+ export interface GetOperationOptions<T extends ZodSchema, Q extends ZodSchema, TTable, TRelations, TSession = DefaultSession> {
7
8
  schemas: {
8
9
  query: Q;
9
10
  response: T;
10
11
  };
11
- access?: {
12
- byCreator?: boolean;
13
- };
12
+ access?: AccessOptions<TSession>;
14
13
  jsonArrayFields?: string[];
15
14
  relations?: TRelations;
16
15
  setParams?: (_req: TypedNextRequest<"GET", "application/json", unknown, z.infer<Q>>) => Promise<Record<string, unknown>>;
@@ -24,13 +23,15 @@ export interface GetOperationOptions<T extends ZodSchema, Q extends ZodSchema, T
24
23
  table?: TTable;
25
24
  }
26
25
  export declare function createGetOperationFactory<TTable, TRelations>({ createAction, getTableName, }: {
27
- createAction: ActionFactory<TTable, (_params: Record<string, unknown>) => Promise<unknown | undefined>>;
26
+ createAction: ActionFactory<TTable, (_params: Record<string, unknown>, _options: {
27
+ scope: ScopeArg;
28
+ }) => Promise<unknown | undefined>>;
28
29
  getTableName(table: TTable): string;
29
- }): ({ db, getSession }: {
30
+ }): <TSession = DefaultSession>({ db, getSession, }: {
30
31
  db?: unknown;
31
- getSession: SessionGetter;
32
- }) => <T extends z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, Q extends z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>({ schemas, access, table, openApiOperation, relations, setParams, jsonArrayFields, handler, catch: catchHandler, }: GetOperationOptions<T, Q, TTable, TRelations>) => import("next-rest-framework").RouteOperationDefinition<"GET">;
33
- export interface GetListOperationOptions<T extends ZodSchema, Q extends ZodSchema, TTable, TRelations> extends Omit<GetOperationOptions<T, Q, TTable, TRelations>, "handler"> {
32
+ getSession: SessionGetter<TSession>;
33
+ }) => <T extends z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, Q extends z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>({ schemas, access, table, openApiOperation, relations, setParams, jsonArrayFields, handler, catch: catchHandler, }: GetOperationOptions<T, Q, TTable, TRelations, TSession>) => import("next-rest-framework").RouteOperationDefinition<"GET">;
34
+ export interface GetListOperationOptions<T extends ZodSchema, Q extends ZodSchema, TTable, TRelations, TSession = DefaultSession> extends Omit<GetOperationOptions<T, Q, TTable, TRelations, TSession>, "handler"> {
34
35
  handler?: <D extends T>(_payload: {
35
36
  params: GetOperationParams<Q>;
36
37
  data: {
@@ -44,12 +45,14 @@ export interface GetListOperationOptions<T extends ZodSchema, Q extends ZodSchem
44
45
  }>;
45
46
  }
46
47
  export declare function createGetListOperationFactory<TTable, TRelations>({ createAction, getTableName, }: {
47
- createAction: ActionFactory<TTable, (_params: Record<string, unknown>) => Promise<{
48
+ createAction: ActionFactory<TTable, (_params: Record<string, unknown>, _options: {
49
+ scope: ScopeArg;
50
+ }) => Promise<{
48
51
  data: unknown[];
49
52
  total: number;
50
53
  }>>;
51
54
  getTableName(table: TTable): string;
52
- }): ({ db, getSession }: {
55
+ }): <TSession = DefaultSession>({ db, getSession, }: {
53
56
  db?: unknown;
54
- getSession: SessionGetter;
55
- }) => <T extends z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, Q extends z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>({ schemas, access, table, openApiOperation, relations, jsonArrayFields, setParams, handler, catch: catchHandler, }: GetListOperationOptions<T, Q, TTable, TRelations>) => import("next-rest-framework").RouteOperationDefinition<"GET">;
57
+ getSession: SessionGetter<TSession>;
58
+ }) => <T extends z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, Q extends z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>({ schemas, access, table, openApiOperation, relations, jsonArrayFields, setParams, handler, catch: catchHandler, }: GetListOperationOptions<T, Q, TTable, TRelations, TSession>) => import("next-rest-framework").RouteOperationDefinition<"GET">;
@@ -31,7 +31,10 @@ function createGetOperationFactory({
31
31
  createAction,
32
32
  getTableName
33
33
  }) {
34
- return ({ db, getSession }) => ({
34
+ return ({
35
+ db,
36
+ getSession
37
+ }) => ({
35
38
  schemas,
36
39
  access,
37
40
  table,
@@ -57,7 +60,7 @@ function createGetOperationFactory({
57
60
  }
58
61
  ]).handler(async (req) => {
59
62
  try {
60
- const mergedParams = await (0, import_operation_common.getReadParams)({
63
+ const { params: mergedParams, scope } = await (0, import_operation_common.getReadParams)({
61
64
  access,
62
65
  getSession,
63
66
  req,
@@ -70,7 +73,7 @@ function createGetOperationFactory({
70
73
  jsonArrayFields,
71
74
  relations,
72
75
  table
73
- })(mergedParams) : mergedParams;
76
+ })(mergedParams, { scope }) : mergedParams;
74
77
  let result = rawResult ?? {};
75
78
  if (handler) {
76
79
  result = await handler({
@@ -89,7 +92,10 @@ function createGetListOperationFactory({
89
92
  createAction,
90
93
  getTableName
91
94
  }) {
92
- return ({ db, getSession }) => ({
95
+ return ({
96
+ db,
97
+ getSession
98
+ }) => ({
93
99
  schemas,
94
100
  access,
95
101
  table,
@@ -115,7 +121,7 @@ function createGetListOperationFactory({
115
121
  }
116
122
  ]).handler(async (req) => {
117
123
  try {
118
- const mergedParams = await (0, import_operation_common.getReadParams)({
124
+ const { params: mergedParams, scope } = await (0, import_operation_common.getReadParams)({
119
125
  access,
120
126
  getSession,
121
127
  req,
@@ -128,7 +134,7 @@ function createGetListOperationFactory({
128
134
  jsonArrayFields,
129
135
  relations,
130
136
  table
131
- })(mergedParams) : { data: [], total: 0 };
137
+ })(mergedParams, { scope }) : { data: [], total: 0 };
132
138
  if (handler) {
133
139
  result = await handler({
134
140
  data: result,