@ai0x0/utils 0.7.0 → 0.7.2

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.
@@ -2,4 +2,5 @@ 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 "./response-headers.js";
5
6
  export * from "./scope.js";
@@ -2,4 +2,5 @@ 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 "./response-headers.js";
5
6
  export * from "./scope.js";
@@ -0,0 +1,16 @@
1
+ /**
2
+ * 记一个响应头,等这次请求出结果时一起发出去。
3
+ *
4
+ * @param owner 这次请求的会话对象 —— 处理器收到的那个 session(本库把 getSession 的返回值
5
+ * 原样传给处理器,所以业务那边手上的就是它)。传 undefined 等于不记。
6
+ */
7
+ export declare function setResponseHeader(owner: unknown, name: string, value: string): void;
8
+ /**
9
+ * 把这次请求攒下的头写进已经建好的那条响应。**工厂出口用**。
10
+ *
11
+ * 为什么是「建好之后再写」而不是建的时候一起传:TypedNextResponse.json 的第二个参数只收
12
+ * status(见 next-rest-framework 的类型),而响应对象本身的 headers 是可写的。
13
+ */
14
+ export declare function withResponseHeaders<T extends {
15
+ headers: Headers;
16
+ }>(response: T, owner: unknown): T;
@@ -0,0 +1,79 @@
1
+ function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
2
+ 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."); }
3
+ 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); }
4
+ 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; }
5
+ 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; } }
6
+ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
7
+ 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); }
8
+ 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; }
9
+ 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; }
10
+ 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; }
11
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
12
+ 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); }
13
+ // =============================================================================
14
+ // 给这次请求的响应挂一个额外的头
15
+ // =============================================================================
16
+ // 处理器深处发生了「顺带得告诉客户端」的事,而那一层够不着响应 —— 典型是扣费:扣完之后
17
+ // 新余额只有那一层知道,界面上却要立刻更新;不给条路的话,客户端只能在每次请求之后再问一遍
18
+ // 余额,或者干脆一直显示旧数。
19
+ //
20
+ // 通道是一张 WeakMap,键是**这次请求解析出来的会话对象**(getSession 每个请求回一个新的,
21
+ // 见 sessionLoader)。两个「为什么不是」:
22
+ // · 不是 AsyncLocalStorage —— 那要求运行时开 node 兼容,而这套要能在边缘运行时里跑;
23
+ // · 不是 req —— 业务那一层常常只拿得到会话(扣费函数收的就是它),够不着请求对象。
24
+ //
25
+ // **取一次就没了**(takeResponseHeaders 会清掉):一个请求攒的头不该漏进下一个 —— 键虽然
26
+ // 每请求一个,但调用方完全可能把会话对象缓存起来复用。
27
+ //
28
+ // 键认不出、这次没人挂过头,一律当作没有。这是个可选的补丁,任何一步不对劲都不该影响那条
29
+ // 响应本身。
30
+
31
+ var pending = new WeakMap();
32
+
33
+ /**
34
+ * 记一个响应头,等这次请求出结果时一起发出去。
35
+ *
36
+ * @param owner 这次请求的会话对象 —— 处理器收到的那个 session(本库把 getSession 的返回值
37
+ * 原样传给处理器,所以业务那边手上的就是它)。传 undefined 等于不记。
38
+ */
39
+ export function setResponseHeader(owner, name, value) {
40
+ if (!isKey(owner)) {
41
+ return;
42
+ }
43
+ pending.set(owner, _objectSpread(_objectSpread({}, pending.get(owner)), {}, _defineProperty({}, name, value)));
44
+ }
45
+
46
+ // 会话对象长什么样由调用方定(TSession 是个不带约束的泛型),所以这两个口子收 unknown,
47
+ // 在这儿把「能不能当 WeakMap 的键」判掉 —— 让每个调用点各写一遍类型断言是把脏活外包出去。
48
+ function isKey(owner) {
49
+ return _typeof(owner) === "object" && owner !== null;
50
+ }
51
+
52
+ /** 取出并清空这次请求攒下的头。**工厂出口用**,业务不该调它。 */
53
+ function takeResponseHeaders(owner) {
54
+ if (!isKey(owner)) {
55
+ return undefined;
56
+ }
57
+ var headers = pending.get(owner);
58
+ if (headers) {
59
+ pending.delete(owner);
60
+ }
61
+ return headers;
62
+ }
63
+
64
+ /**
65
+ * 把这次请求攒下的头写进已经建好的那条响应。**工厂出口用**。
66
+ *
67
+ * 为什么是「建好之后再写」而不是建的时候一起传:TypedNextResponse.json 的第二个参数只收
68
+ * status(见 next-rest-framework 的类型),而响应对象本身的 headers 是可写的。
69
+ */
70
+ export function withResponseHeaders(response, owner) {
71
+ var extra = takeResponseHeaders(owner);
72
+ for (var _i = 0, _Object$entries = Object.entries(extra !== null && extra !== void 0 ? extra : {}); _i < _Object$entries.length; _i++) {
73
+ var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
74
+ name = _Object$entries$_i[0],
75
+ value = _Object$entries$_i[1];
76
+ response.headers.set(name, value);
77
+ }
78
+ return response;
79
+ }
@@ -11,6 +11,7 @@ import { routeOperation, TypedNextResponse } from "next-rest-framework";
11
11
  import { listBodySchema } from "../schemas/index.js";
12
12
  import { createOpenApiOperation } from "./open-api-operation.js";
13
13
  import { callerIdOf, getDefaultTags, getReadParams, handleOperationError } from "./operation-common.js";
14
+ import { withResponseHeaders } from "../response-headers.js";
14
15
 
15
16
  // ==============================================================================
16
17
  // GET single operation
@@ -106,9 +107,9 @@ export function createGetOperationFactory(_ref) {
106
107
  case 19:
107
108
  result = _context.sent;
108
109
  case 20:
109
- return _context.abrupt("return", TypedNextResponse.json(result, {
110
+ return _context.abrupt("return", withResponseHeaders(TypedNextResponse.json(result, {
110
111
  status: 200
111
- }));
112
+ }), session));
112
113
  case 23:
113
114
  _context.prev = 23;
114
115
  _context.t1 = _context["catch"](0);
@@ -223,9 +224,9 @@ export function createGetListOperationFactory(_ref5) {
223
224
  case 18:
224
225
  result = _context2.sent;
225
226
  case 19:
226
- return _context2.abrupt("return", TypedNextResponse.json(result, {
227
+ return _context2.abrupt("return", withResponseHeaders(TypedNextResponse.json(result, {
227
228
  status: 200
228
- }));
229
+ }), session));
229
230
  case 22:
230
231
  _context2.prev = 22;
231
232
  _context2.t1 = _context2["catch"](0);
@@ -12,6 +12,7 @@ import { z } from "zod";
12
12
  import { createOpenApiOperation } from "./open-api-operation.js";
13
13
  import { callerIdOf, getDefaultTags, handleOperationError, readPostBody, resolveAccess, sessionLoader } from "./operation-common.js";
14
14
  import { NO_SCOPE } from "../scope.js";
15
+ import { withResponseHeaders } from "../response-headers.js";
15
16
 
16
17
  // ==============================================================================
17
18
  // POST create operation
@@ -153,9 +154,9 @@ export function createPostOperationFactory(_ref) {
153
154
  _context.t2 = raw;
154
155
  case 32:
155
156
  data = _context.t2;
156
- return _context.abrupt("return", TypedNextResponse.json(data, {
157
+ return _context.abrupt("return", withResponseHeaders(TypedNextResponse.json(data, {
157
158
  status: 200
158
- }));
159
+ }), session));
159
160
  case 36:
160
161
  _context.prev = 36;
161
162
  _context.t3 = _context["catch"](0);
@@ -213,7 +214,7 @@ export function createPutOperationFactory(_ref5) {
213
214
  status: 200
214
215
  }]).handler( /*#__PURE__*/function () {
215
216
  var _ref8 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(req) {
216
- var _yield$loadSession, loadSession, _yield$resolveAccess2, scope, body, extraBody, editorId, params, raw, data;
217
+ var loadSession, _yield$resolveAccess2, scope, body, extraBody, session, editorId, params, raw, data;
217
218
  return _regeneratorRuntime().wrap(function _callee2$(_context2) {
218
219
  while (1) switch (_context2.prev = _context2.next) {
219
220
  case 0:
@@ -246,35 +247,17 @@ export function createPutOperationFactory(_ref5) {
246
247
  _context2.next = 17;
247
248
  return loadSession();
248
249
  case 17:
249
- _context2.t2 = _yield$loadSession = _context2.sent;
250
- _context2.t1 = _context2.t2 === null;
251
- if (_context2.t1) {
252
- _context2.next = 21;
253
- break;
254
- }
255
- _context2.t1 = _yield$loadSession === void 0;
256
- case 21:
257
- if (!_context2.t1) {
258
- _context2.next = 25;
259
- break;
260
- }
261
- _context2.t3 = void 0;
262
- _context2.next = 26;
263
- break;
264
- case 25:
265
- _context2.t3 = _yield$loadSession.userId;
266
- case 26:
267
- editorId = _context2.t3;
268
- // editorId 同样**最后**展开:它是审计字段,让请求体覆盖等于让人随便写「是谁改的」。
250
+ session = _context2.sent;
251
+ editorId = session === null || session === void 0 ? void 0 : session.userId; // editorId 同样**最后**展开:它是审计字段,让请求体覆盖等于让人随便写「是谁改的」。
269
252
  // 理由与 POST 那边的归属戳一样,见那里的说明。
270
253
  params = _objectSpread(_objectSpread(_objectSpread({}, body), extraBody), {}, {
271
254
  editorId: editorId
272
255
  });
273
256
  if (!(table && db)) {
274
- _context2.next = 34;
257
+ _context2.next = 26;
275
258
  break;
276
259
  }
277
- _context2.next = 31;
260
+ _context2.next = 23;
278
261
  return createAction({
279
262
  bodySchema: schemas.body,
280
263
  db: db,
@@ -282,19 +265,19 @@ export function createPutOperationFactory(_ref5) {
282
265
  })(params, {
283
266
  scope: scope
284
267
  });
285
- case 31:
286
- _context2.t4 = _context2.sent;
287
- _context2.next = 35;
268
+ case 23:
269
+ _context2.t1 = _context2.sent;
270
+ _context2.next = 27;
288
271
  break;
289
- case 34:
290
- _context2.t4 = params;
291
- case 35:
292
- raw = _context2.t4;
272
+ case 26:
273
+ _context2.t1 = params;
274
+ case 27:
275
+ raw = _context2.t1;
293
276
  if (!handler) {
294
- _context2.next = 42;
277
+ _context2.next = 34;
295
278
  break;
296
279
  }
297
- _context2.next = 39;
280
+ _context2.next = 31;
298
281
  return handler({
299
282
  data: raw,
300
283
  // 同 POST:callerId 不进 createAction 那一份(它会被 .set() 原样写进 UPDATE,
@@ -304,26 +287,26 @@ export function createPutOperationFactory(_ref5) {
304
287
  }),
305
288
  req: req
306
289
  });
307
- case 39:
308
- _context2.t5 = _context2.sent;
309
- _context2.next = 43;
290
+ case 31:
291
+ _context2.t2 = _context2.sent;
292
+ _context2.next = 35;
310
293
  break;
311
- case 42:
312
- _context2.t5 = raw;
313
- case 43:
314
- data = _context2.t5;
315
- return _context2.abrupt("return", TypedNextResponse.json(data, {
294
+ case 34:
295
+ _context2.t2 = raw;
296
+ case 35:
297
+ data = _context2.t2;
298
+ return _context2.abrupt("return", withResponseHeaders(TypedNextResponse.json(data, {
316
299
  status: 200
317
- }));
318
- case 47:
319
- _context2.prev = 47;
320
- _context2.t6 = _context2["catch"](0);
321
- return _context2.abrupt("return", handleOperationError(_context2.t6, catchHandler));
322
- case 50:
300
+ }), session));
301
+ case 39:
302
+ _context2.prev = 39;
303
+ _context2.t3 = _context2["catch"](0);
304
+ return _context2.abrupt("return", handleOperationError(_context2.t3, catchHandler));
305
+ case 42:
323
306
  case "end":
324
307
  return _context2.stop();
325
308
  }
326
- }, _callee2, null, [[0, 47]]);
309
+ }, _callee2, null, [[0, 39]]);
327
310
  }));
328
311
  return function (_x2) {
329
312
  return _ref8.apply(this, arguments);
@@ -0,0 +1,83 @@
1
+ /**
2
+ * api-route-via-nrf
3
+ * -----------------
4
+ * API 路由一律走 next-rest-framework(route / routeOperation),不许裸导 HTTP 方法。
5
+ *
6
+ * 裸写 `export async function POST(req)` 的代价不是风格不统一,是这条端点从此不存在于
7
+ * openapi.json:生成器不会给它出 client,CLI / agent 读不到它,前端只能手写 fetch 直连
8
+ * (那是 no-direct-api-url 拦的另一头)。框架统一做的鉴权预热、错误翻译、响应校验,
9
+ * 它也都得自己记得补。
10
+ *
11
+ * 判的是「导出形状」,不是「有没有 import route」:
12
+ * 报:`export async function POST(...)`、`export const GET = handler`;
13
+ * 放:`export const { POST } = route({...})`(从 route() 解构,id 是 ObjectPattern)。
14
+ *
15
+ * 作用范围由调用方在 flat config 里用 files 圈定(比如只在 api 目录的 route.ts 上开);
16
+ * 确有理由裸导的文件(第三方库的整体转发,如 better-auth 的 catch-all)用 files override
17
+ * 单独关掉,并在那个文件里写明理由。
18
+ */
19
+
20
+ const HTTP_METHODS = new Set([
21
+ "GET",
22
+ "POST",
23
+ "PUT",
24
+ "DELETE",
25
+ "PATCH",
26
+ "HEAD",
27
+ "OPTIONS",
28
+ ]);
29
+
30
+ /** @type {import('eslint').Rule.RuleModule} */
31
+ const rule = {
32
+ meta: {
33
+ type: "problem",
34
+ docs: {
35
+ description:
36
+ "API 路由必须用 next-rest-framework 的 route()/routeOperation() 构建,禁止裸导 HTTP 方法。",
37
+ },
38
+ schema: [],
39
+ messages: {
40
+ rawMethodExport:
41
+ "API 路由不许裸导 HTTP 方法,改用 next-rest-framework 的 route()/routeOperation" +
42
+ "(export const { POST } = route({...}))。裸导的端点进不了 openapi.json," +
43
+ "前端与 CLI 都拿不到它的 client。",
44
+ },
45
+ },
46
+ create(context) {
47
+ return {
48
+ ExportNamedDeclaration(node) {
49
+ const declaration = node.declaration;
50
+ if (!declaration) {
51
+ return;
52
+ }
53
+ if (
54
+ declaration.type === "FunctionDeclaration" &&
55
+ declaration.id &&
56
+ HTTP_METHODS.has(declaration.id.name)
57
+ ) {
58
+ context.report({
59
+ node: declaration.id,
60
+ messageId: "rawMethodExport",
61
+ });
62
+ return;
63
+ }
64
+ if (declaration.type === "VariableDeclaration") {
65
+ for (const declarator of declaration.declarations) {
66
+ // 从 route() 解构出来的是 ObjectPattern,那才叫走框架。
67
+ if (
68
+ declarator.id.type === "Identifier" &&
69
+ HTTP_METHODS.has(declarator.id.name)
70
+ ) {
71
+ context.report({
72
+ node: declarator.id,
73
+ messageId: "rawMethodExport",
74
+ });
75
+ }
76
+ }
77
+ }
78
+ },
79
+ };
80
+ },
81
+ };
82
+
83
+ export default rule;
@@ -12,6 +12,8 @@ import noThen from "./no-then.js";
12
12
  import noOneLetterVars from "./no-one-letter-vars.js";
13
13
  import noConsecutiveSetState from "./no-consecutive-setstate.js";
14
14
  import maxLines from "./max-lines.js";
15
+ import apiRouteViaNrf from "./api-route-via-nrf.js";
16
+ import noDirectApiUrl from "./no-direct-api-url.js";
15
17
 
16
18
  const rules = {
17
19
  "require-section-divider": requireSectionDivider,
@@ -25,8 +27,17 @@ const rules = {
25
27
  "no-one-letter-vars": noOneLetterVars,
26
28
  "no-consecutive-setstate": noConsecutiveSetState,
27
29
  "max-lines": maxLines,
30
+ "api-route-via-nrf": apiRouteViaNrf,
31
+ "no-direct-api-url": noDirectApiUrl,
28
32
  };
29
33
 
34
+ // 这两条只管「特定位置」的代码,不能跟着 recommended 全局开:
35
+ // · api-route-via-nrf 只该挂在 API 路由文件上(别的文件 export 一个叫 GET 的函数是自由的);
36
+ // · no-direct-api-url 只该挂在前端目录上(后端与中间件里出现 "/api/..." 字面量是本分)。
37
+ // 调用方在 flat config 里用 files 圈定范围自行开启,豁免文件用 files override 关掉,
38
+ // 用法写在两条规则各自的文件头。
39
+ const SCOPED_RULES = new Set(["api-route-via-nrf", "no-direct-api-url"]);
40
+
30
41
  const plugin = {
31
42
  rules,
32
43
  configs: {
@@ -34,7 +45,9 @@ const plugin = {
34
45
  plugins: { ai0x0: { rules } },
35
46
  rules: {
36
47
  ...Object.fromEntries(
37
- Object.keys(rules).map((name) => [`ai0x0/${name}`, "error"]),
48
+ Object.keys(rules)
49
+ .filter((name) => !SCOPED_RULES.has(name))
50
+ .map((name) => [`ai0x0/${name}`, "error"]),
38
51
  ),
39
52
  curly: "error",
40
53
  "ai0x0/max-lines": [
@@ -0,0 +1,55 @@
1
+ /**
2
+ * no-direct-api-url
3
+ * -----------------
4
+ * 前端不许手写 "/api/..." 地址直连后端,一律走生成的 client。
5
+ *
6
+ * 手写 `fetch("/api/xxx")` 绕掉的不是一层方便,是一整套集中处理(401 重登、错误提示、
7
+ * 作用域头、重复请求去重),而且 TypeScript 对它是瞎的 —— 路由改了形状,编译不红。
8
+ * 所以要调一个端点:先让它进 openapi.json(后端路由走 nrf,见 api-route-via-nrf),
9
+ * 重新生成 client,再调生成出来的方法。
10
+ *
11
+ * 判的是字符串字面量与模板串的开头:`/api/`。注释与 JSDoc 不是 AST 节点,天然不报。
12
+ * 作用范围由调用方在 flat config 里用 files 圈定(比如只挂前端目录),client 封装层
13
+ * 自己(生成文件与拦截器)用 files override 关掉 —— 地址本来就该只写在那儿。
14
+ */
15
+
16
+ const API_PREFIX = "/api/";
17
+
18
+ function startsWithApi(value) {
19
+ return typeof value === "string" && value.startsWith(API_PREFIX);
20
+ }
21
+
22
+ /** @type {import('eslint').Rule.RuleModule} */
23
+ const rule = {
24
+ meta: {
25
+ type: "problem",
26
+ docs: {
27
+ description:
28
+ '前端禁止手写 "/api/..." 地址直连后端,必须走生成的 client。',
29
+ },
30
+ schema: [],
31
+ messages: {
32
+ directApiUrl:
33
+ '前端不许手写 "/api/..." 直连后端:走生成的 client。端点先进 openapi.json' +
34
+ "(后端路由走 nrf),重新生成 client 之后调生成出来的方法;401 重登、错误提示、" +
35
+ "作用域头都在那一层集中处理,手写 fetch 全部绕开。",
36
+ },
37
+ },
38
+ create(context) {
39
+ return {
40
+ Literal(node) {
41
+ if (startsWithApi(node.value)) {
42
+ context.report({ node, messageId: "directApiUrl" });
43
+ }
44
+ },
45
+ TemplateLiteral(node) {
46
+ // 只看第一段静态文本:`${base}/api/x` 的地址不归这条管(那是封装层的拼法)。
47
+ if (startsWithApi(node.quasis[0]?.value.cooked)) {
48
+ context.report({ node, messageId: "directApiUrl" });
49
+ }
50
+ },
51
+ };
52
+ },
53
+ };
54
+
55
+ export default rule;
@@ -2,4 +2,5 @@ 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 "./response-headers.js";
5
6
  export * from "./scope.js";
@@ -20,6 +20,7 @@ __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("./response-headers"), module.exports);
23
24
  __reExport(backend_exports, require("./scope"), module.exports);
24
25
  // Annotate the CommonJS export names for ESM import in node:
25
26
  0 && (module.exports = {
@@ -27,5 +28,6 @@ __reExport(backend_exports, require("./scope"), module.exports);
27
28
  ...require("./route-operation"),
28
29
  ...require("./schemas"),
29
30
  ...require("./errors"),
31
+ ...require("./response-headers"),
30
32
  ...require("./scope")
31
33
  });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * 记一个响应头,等这次请求出结果时一起发出去。
3
+ *
4
+ * @param owner 这次请求的会话对象 —— 处理器收到的那个 session(本库把 getSession 的返回值
5
+ * 原样传给处理器,所以业务那边手上的就是它)。传 undefined 等于不记。
6
+ */
7
+ export declare function setResponseHeader(owner: unknown, name: string, value: string): void;
8
+ /**
9
+ * 把这次请求攒下的头写进已经建好的那条响应。**工厂出口用**。
10
+ *
11
+ * 为什么是「建好之后再写」而不是建的时候一起传:TypedNextResponse.json 的第二个参数只收
12
+ * status(见 next-rest-framework 的类型),而响应对象本身的 headers 是可写的。
13
+ */
14
+ export declare function withResponseHeaders<T extends {
15
+ headers: Headers;
16
+ }>(response: T, owner: unknown): T;
@@ -0,0 +1,57 @@
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/response-headers.ts
20
+ var response_headers_exports = {};
21
+ __export(response_headers_exports, {
22
+ setResponseHeader: () => setResponseHeader,
23
+ withResponseHeaders: () => withResponseHeaders
24
+ });
25
+ module.exports = __toCommonJS(response_headers_exports);
26
+ var pending = /* @__PURE__ */ new WeakMap();
27
+ function setResponseHeader(owner, name, value) {
28
+ if (!isKey(owner)) {
29
+ return;
30
+ }
31
+ pending.set(owner, { ...pending.get(owner), [name]: value });
32
+ }
33
+ function isKey(owner) {
34
+ return typeof owner === "object" && owner !== null;
35
+ }
36
+ function takeResponseHeaders(owner) {
37
+ if (!isKey(owner)) {
38
+ return void 0;
39
+ }
40
+ const headers = pending.get(owner);
41
+ if (headers) {
42
+ pending.delete(owner);
43
+ }
44
+ return headers;
45
+ }
46
+ function withResponseHeaders(response, owner) {
47
+ const extra = takeResponseHeaders(owner);
48
+ for (const [name, value] of Object.entries(extra ?? {})) {
49
+ response.headers.set(name, value);
50
+ }
51
+ return response;
52
+ }
53
+ // Annotate the CommonJS export names for ESM import in node:
54
+ 0 && (module.exports = {
55
+ setResponseHeader,
56
+ withResponseHeaders
57
+ });
@@ -27,6 +27,7 @@ var import_next_rest_framework = require("next-rest-framework");
27
27
  var import_schemas = require("../schemas");
28
28
  var import_open_api_operation = require("./open-api-operation");
29
29
  var import_operation_common = require("./operation-common");
30
+ var import_response_headers = require("../response-headers");
30
31
  function createGetOperationFactory({
31
32
  createAction,
32
33
  getTableName
@@ -87,7 +88,10 @@ function createGetOperationFactory({
87
88
  req
88
89
  });
89
90
  }
90
- return import_next_rest_framework.TypedNextResponse.json(result, { status: 200 });
91
+ return (0, import_response_headers.withResponseHeaders)(
92
+ import_next_rest_framework.TypedNextResponse.json(result, { status: 200 }),
93
+ session
94
+ );
91
95
  } catch (error) {
92
96
  return (0, import_operation_common.handleOperationError)(error, catchHandler);
93
97
  }
@@ -152,9 +156,10 @@ function createGetListOperationFactory({
152
156
  req
153
157
  });
154
158
  }
155
- return import_next_rest_framework.TypedNextResponse.json(result, {
156
- status: 200
157
- });
159
+ return (0, import_response_headers.withResponseHeaders)(
160
+ import_next_rest_framework.TypedNextResponse.json(result, { status: 200 }),
161
+ session
162
+ );
158
163
  } catch (error) {
159
164
  return (0, import_operation_common.handleOperationError)(error, catchHandler);
160
165
  }
@@ -29,6 +29,7 @@ var import_zod = require("zod");
29
29
  var import_open_api_operation = require("./open-api-operation");
30
30
  var import_operation_common = require("./operation-common");
31
31
  var import_scope = require("../scope");
32
+ var import_response_headers = require("../response-headers");
32
33
  function createPostOperationFactory({
33
34
  createAction,
34
35
  getTableName
@@ -93,7 +94,10 @@ function createPostOperationFactory({
93
94
  },
94
95
  req
95
96
  }) : raw;
96
- return import_next_rest_framework.TypedNextResponse.json(data, { status: 200 });
97
+ return (0, import_response_headers.withResponseHeaders)(
98
+ import_next_rest_framework.TypedNextResponse.json(data, { status: 200 }),
99
+ session
100
+ );
97
101
  } catch (error) {
98
102
  return (0, import_operation_common.handleOperationError)(error, catchHandler);
99
103
  }
@@ -130,7 +134,6 @@ function createPutOperationFactory({
130
134
  status: 200
131
135
  }
132
136
  ]).handler(async (req) => {
133
- var _a;
134
137
  try {
135
138
  const loadSession = (0, import_operation_common.sessionLoader)(getSession, req);
136
139
  const { scope } = await (0, import_operation_common.resolveAccess)({
@@ -142,7 +145,8 @@ function createPutOperationFactory({
142
145
  const extraBody = await (setBody == null ? void 0 : setBody(
143
146
  req
144
147
  )) || {};
145
- const editorId = (_a = await loadSession()) == null ? void 0 : _a.userId;
148
+ const session = await loadSession();
149
+ const editorId = session == null ? void 0 : session.userId;
146
150
  const params = { ...body, ...extraBody, editorId };
147
151
  const raw = table && db ? await createAction({
148
152
  bodySchema: schemas.body,
@@ -159,7 +163,10 @@ function createPutOperationFactory({
159
163
  },
160
164
  req
161
165
  }) : raw;
162
- return import_next_rest_framework.TypedNextResponse.json(data, { status: 200 });
166
+ return (0, import_response_headers.withResponseHeaders)(
167
+ import_next_rest_framework.TypedNextResponse.json(data, { status: 200 }),
168
+ session
169
+ );
163
170
  } catch (error) {
164
171
  return (0, import_operation_common.handleOperationError)(error, catchHandler);
165
172
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai0x0/utils",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "AI0x0 utils",
5
5
  "keywords": [
6
6
  "ai0x0"