@ai0x0/utils 0.0.50 → 0.1.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.
- package/.agents/skills/ai0x0-utils/backend/SKILL.md +35 -11
- package/README.md +3 -3
- package/SKILL.md +35 -11
- package/es/backend/actions/create-delete-action.d.ts +4 -2
- package/es/backend/actions/create-delete-action.js +22 -28
- package/es/backend/actions/create-get-action.d.ts +4 -1
- package/es/backend/actions/create-get-action.js +10 -10
- package/es/backend/actions/create-get-list-action.d.ts +4 -1
- package/es/backend/actions/create-get-list-action.js +9 -9
- package/es/backend/actions/create-put-action.d.ts +3 -2
- package/es/backend/actions/create-put-action.js +23 -38
- package/es/backend/actions/get-list-query.d.ts +8 -1
- package/es/backend/actions/get-list-query.js +19 -8
- package/es/backend/index.d.ts +1 -0
- package/es/backend/index.js +2 -1
- package/es/backend/route-operation/delete-operation.d.ts +3 -1
- package/es/backend/route-operation/get-list-operation.d.ts +3 -1
- package/es/backend/route-operation/get-operation.d.ts +3 -1
- package/es/backend/route-operation/operation-common.d.ts +80 -8
- package/es/backend/route-operation/operation-common.js +216 -67
- package/es/backend/route-operation/post-operation.d.ts +3 -1
- package/es/backend/route-operation/put-operation.d.ts +3 -1
- package/es/backend/route-operation/read-operation-core.d.ts +17 -14
- package/es/backend/route-operation/read-operation-core.js +42 -34
- package/es/backend/route-operation/write-operation-core.d.ts +25 -22
- package/es/backend/route-operation/write-operation-core.js +195 -165
- package/es/backend/schemas/index.d.ts +240 -240
- package/es/backend/scope.d.ts +27 -0
- package/es/backend/scope.js +60 -0
- package/es/backend/sqlite/actions/create-delete-action.d.ts +4 -2
- package/es/backend/sqlite/actions/create-delete-action.js +21 -28
- package/es/backend/sqlite/actions/create-get-action.d.ts +4 -1
- package/es/backend/sqlite/actions/create-get-action.js +9 -7
- package/es/backend/sqlite/actions/create-get-list-action.d.ts +4 -1
- package/es/backend/sqlite/actions/create-get-list-action.js +8 -6
- package/es/backend/sqlite/actions/create-put-action.d.ts +3 -2
- package/es/backend/sqlite/actions/create-put-action.js +20 -38
- package/es/backend/sqlite/actions/get-list-query.d.ts +4 -1
- package/es/backend/sqlite/actions/get-list-query.js +8 -3
- package/es/backend/sqlite/route-operation/delete-operation.d.ts +3 -1
- package/es/backend/sqlite/route-operation/get-list-operation.d.ts +3 -1
- package/es/backend/sqlite/route-operation/get-operation.d.ts +3 -1
- package/es/backend/sqlite/route-operation/post-operation.d.ts +3 -1
- package/es/backend/sqlite/route-operation/put-operation.d.ts +3 -1
- package/lib/backend/actions/create-delete-action.d.ts +4 -2
- package/lib/backend/actions/create-delete-action.js +10 -16
- package/lib/backend/actions/create-get-action.d.ts +4 -1
- package/lib/backend/actions/create-get-action.js +2 -1
- package/lib/backend/actions/create-get-list-action.d.ts +4 -1
- package/lib/backend/actions/create-get-list-action.js +2 -1
- package/lib/backend/actions/create-put-action.d.ts +3 -2
- package/lib/backend/actions/create-put-action.js +10 -21
- package/lib/backend/actions/get-list-query.d.ts +8 -1
- package/lib/backend/actions/get-list-query.js +11 -4
- package/lib/backend/index.d.ts +1 -0
- package/lib/backend/index.js +3 -1
- package/lib/backend/route-operation/delete-operation.d.ts +3 -1
- package/lib/backend/route-operation/get-list-operation.d.ts +3 -1
- package/lib/backend/route-operation/get-operation.d.ts +3 -1
- package/lib/backend/route-operation/operation-common.d.ts +80 -8
- package/lib/backend/route-operation/operation-common.js +57 -7
- package/lib/backend/route-operation/post-operation.d.ts +3 -1
- package/lib/backend/route-operation/put-operation.d.ts +3 -1
- package/lib/backend/route-operation/read-operation-core.d.ts +17 -14
- package/lib/backend/route-operation/read-operation-core.js +12 -6
- package/lib/backend/route-operation/write-operation-core.d.ts +25 -22
- package/lib/backend/route-operation/write-operation-core.js +52 -16
- package/lib/backend/schemas/index.d.ts +240 -240
- package/lib/backend/scope.d.ts +27 -0
- package/lib/backend/scope.js +57 -0
- package/lib/backend/sqlite/actions/create-delete-action.d.ts +4 -2
- package/lib/backend/sqlite/actions/create-delete-action.js +10 -16
- package/lib/backend/sqlite/actions/create-get-action.d.ts +4 -1
- package/lib/backend/sqlite/actions/create-get-action.js +2 -1
- package/lib/backend/sqlite/actions/create-get-list-action.d.ts +4 -1
- package/lib/backend/sqlite/actions/create-get-list-action.js +2 -1
- package/lib/backend/sqlite/actions/create-put-action.d.ts +3 -2
- package/lib/backend/sqlite/actions/create-put-action.js +10 -21
- package/lib/backend/sqlite/actions/get-list-query.d.ts +4 -1
- package/lib/backend/sqlite/actions/get-list-query.js +5 -1
- package/lib/backend/sqlite/route-operation/delete-operation.d.ts +3 -1
- package/lib/backend/sqlite/route-operation/get-list-operation.d.ts +3 -1
- package/lib/backend/sqlite/route-operation/get-operation.d.ts +3 -1
- package/lib/backend/sqlite/route-operation/post-operation.d.ts +3 -1
- package/lib/backend/sqlite/route-operation/put-operation.d.ts +3 -1
- package/package.json +39 -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
|
|
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>, {
|
|
9
|
-
|
|
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
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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
|
|
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) {
|
package/lib/backend/index.d.ts
CHANGED
package/lib/backend/index.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
7
|
+
/** 默认的会话形状。够用于「一条记录属于一个人」的场景。 */
|
|
8
|
+
export interface DefaultSession {
|
|
7
9
|
userId?: string;
|
|
8
|
-
}
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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<
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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 {
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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 ({
|
|
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 ({
|
|
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,
|