@ai0x0/utils 0.9.0 → 0.11.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.
package/SKILL.md CHANGED
@@ -317,6 +317,8 @@ export const { GET } = route({
317
317
  - **Fail closed**: if the session is missing → 401; if `scope.value` returns empty → 403. A scope that cannot be resolved is **never** downgraded to "no filter". PUT/DELETE put the scope straight into the `UPDATE`/`DELETE` `where` (one statement, no read-then-write window) and return 404 when it affects zero rows — "does not exist" and "not yours" are deliberately indistinguishable, otherwise the endpoint becomes an existence oracle.
318
318
  - **Auto injection**: POST stamps the scope column with the scope value (by default that is `creatorId = session.userId`); PUT writes `editorId = session.userId`; GET/GET_LIST apply the scope as a mandatory `where`.
319
319
  - **Session shape** is yours: `getSession` is generic, so resolve "who + which space + what role" once and let `scope.value` / `can` read the same object instead of each querying again.
320
+ - **Built-in error copy** (`未登录` / `没有权限` / `缺少 id` / `未找到编辑(删除)对象,或没有权限` / scope-unresolved 403): thrown from inside the library, so the app's own i18n throw-sites never see them. Register `setHttpErrorMessageResolver(() => ({ unauthenticated: t("..."), ... }))` once at app startup to translate them per request — the resolver may be `async` (e.g. resolving the locale from `next/headers`), is called lazily at each throw site, and falls back to the Chinese defaults when absent or when it throws. Deliberately string-based, not error-code-based: CLI and agent consumers read these messages too and have no translation layer.
321
+
320
322
  - **Date coercion**: any body field ending in `At` is passed through `dayjs(value).toDate()` before write — accept ISO strings from clients.
321
323
  - **List filtering** (all querystring values are strings). **A column is filterable only if it appears in `schemas.response`** — the filter surface is the readable surface, so a list that returns `id / title` cannot be probed with `?secret=…` (unknown or unreadable keys are ignored, not rejected). Anything you want filterable belongs in the response schema.
322
324
  - `key=a,b,c` → `IN (...)` when the key is `id` or ends with `Id`; otherwise `OR ILIKE %a% OR ILIKE %b%`
@@ -26,7 +26,7 @@ function _asyncToGenerator(fn) { return function () { var self = this, args = ar
26
26
 
27
27
  import { and, eq } from "drizzle-orm";
28
28
  import { NO_SCOPE, scopeCondition } from "../scope.js";
29
- import { HttpError } from "../errors.js";
29
+ import { HttpError, httpErrorMessages } from "../errors.js";
30
30
  import { transformBody } from "./transform-body.js";
31
31
 
32
32
  /** 核心用得到表的哪一部分:只有 id。dialect 各自的 Table 类型不进来。 */
@@ -49,25 +49,35 @@ export function createPutActionCore(_ref) {
49
49
  scope = _ref2.scope;
50
50
  id = body.id;
51
51
  if (id) {
52
- _context.next = 4;
52
+ _context.next = 8;
53
53
  break;
54
54
  }
55
- throw new HttpError(400, "缺少 id");
56
- case 4:
55
+ _context.t0 = HttpError;
57
56
  _context.next = 6;
58
- return db.update(table).set(transformBody(body)).where(byId(table, id, scope)).returning();
57
+ return httpErrorMessages();
59
58
  case 6:
59
+ _context.t1 = _context.sent.missingId;
60
+ throw new _context.t0(400, _context.t1);
61
+ case 8:
62
+ _context.next = 10;
63
+ return db.update(table).set(transformBody(body)).where(byId(table, id, scope)).returning();
64
+ case 10:
60
65
  _yield$db$update$set$ = _context.sent;
61
66
  _yield$db$update$set$2 = _slicedToArray(_yield$db$update$set$, 1);
62
67
  data = _yield$db$update$set$2[0];
63
68
  if (!(!data && scope !== NO_SCOPE)) {
64
- _context.next = 11;
69
+ _context.next = 19;
65
70
  break;
66
71
  }
67
- throw new HttpError(404, "未找到编辑对象,或没有权限");
68
- case 11:
72
+ _context.t2 = HttpError;
73
+ _context.next = 17;
74
+ return httpErrorMessages();
75
+ case 17:
76
+ _context.t3 = _context.sent.editNotFound;
77
+ throw new _context.t2(404, _context.t3);
78
+ case 19:
69
79
  return _context.abrupt("return", data);
70
- case 12:
80
+ case 20:
71
81
  case "end":
72
82
  return _context.stop();
73
83
  }
@@ -90,23 +100,33 @@ export function createDeleteActionCore(_ref4) {
90
100
  id = _ref5.id;
91
101
  scope = _ref6.scope;
92
102
  if (id) {
93
- _context2.next = 4;
103
+ _context2.next = 8;
94
104
  break;
95
105
  }
96
- throw new HttpError(400, "缺少 id");
97
- case 4:
106
+ _context2.t0 = HttpError;
98
107
  _context2.next = 6;
99
- return db.delete(table).where(byId(table, id, scope)).returning();
108
+ return httpErrorMessages();
100
109
  case 6:
110
+ _context2.t1 = _context2.sent.missingId;
111
+ throw new _context2.t0(400, _context2.t1);
112
+ case 8:
113
+ _context2.next = 10;
114
+ return db.delete(table).where(byId(table, id, scope)).returning();
115
+ case 10:
101
116
  rows = _context2.sent;
102
117
  if (!(rows.length === 0 && scope !== NO_SCOPE)) {
103
- _context2.next = 9;
118
+ _context2.next = 17;
104
119
  break;
105
120
  }
106
- throw new HttpError(404, "未找到删除对象,或没有权限");
107
- case 9:
121
+ _context2.t2 = HttpError;
122
+ _context2.next = 15;
123
+ return httpErrorMessages();
124
+ case 15:
125
+ _context2.t3 = _context2.sent.deleteNotFound;
126
+ throw new _context2.t2(404, _context2.t3);
127
+ case 17:
108
128
  return _context2.abrupt("return", rows);
109
- case 10:
129
+ case 18:
110
130
  case "end":
111
131
  return _context2.stop();
112
132
  }
@@ -2,3 +2,33 @@ export declare class HttpError extends Error {
2
2
  status: number;
3
3
  constructor(status: number, message: string);
4
4
  }
5
+ export interface HttpErrorMessages {
6
+ /** 401:会话不存在。 */
7
+ unauthenticated: string;
8
+ /** 403:access.can 拒绝且没给自定义文案。 */
9
+ forbidden: string;
10
+ /** 400:PUT / DELETE 请求体缺 id。 */
11
+ missingId: string;
12
+ /** 404:PUT 影响 0 行(不存在或不属于调用者)。 */
13
+ editNotFound: string;
14
+ /** 404:DELETE 影响 0 行。 */
15
+ deleteNotFound: string;
16
+ /** 403:作用域值解析不出来 —— 多半是配错了列名,但请求照样要被拒绝。 */
17
+ scopeUnresolved: (column: string) => string;
18
+ }
19
+ export declare const defaultHttpErrorMessages: HttpErrorMessages;
20
+ type HttpErrorMessageResolver = () => Partial<HttpErrorMessages> | Promise<Partial<HttpErrorMessages>>;
21
+ /**
22
+ * 注册文案解析器,应用启动时调一次(再次调用覆盖;传 undefined 恢复默认)。
23
+ * 不注册就一直是 defaultHttpErrorMessages 那几句中文 —— 没做国际化的项目零感知。
24
+ */
25
+ export declare function setHttpErrorMessageResolver(next: HttpErrorMessageResolver | undefined): void;
26
+ /**
27
+ * 取出当前生效的整份文案。**在抛出点现调**,别在模块顶层缓存 —— 解析器是按请求判
28
+ * 语言的,缓存住就把上一个请求的语言漏给下一个。
29
+ *
30
+ * 解析器自己抛了错(比如语言包没加载到)就回落默认中文:报错文案出不来不该把
31
+ * 原本那个 401/403 也吞掉。
32
+ */
33
+ export declare function httpErrorMessages(): Promise<HttpErrorMessages>;
34
+ export {};
@@ -1,4 +1,9 @@
1
+ function _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function _regeneratorRuntime() { return e; }; var t, e = {}, r = Object.prototype, n = r.hasOwnProperty, o = Object.defineProperty || function (t, e, r) { t[e] = r.value; }, i = "function" == typeof Symbol ? Symbol : {}, a = i.iterator || "@@iterator", c = i.asyncIterator || "@@asyncIterator", u = i.toStringTag || "@@toStringTag"; function define(t, e, r) { return Object.defineProperty(t, e, { value: r, enumerable: !0, configurable: !0, writable: !0 }), t[e]; } try { define({}, ""); } catch (t) { define = function define(t, e, r) { return t[e] = r; }; } function wrap(t, e, r, n) { var i = e && e.prototype instanceof Generator ? e : Generator, a = Object.create(i.prototype), c = new Context(n || []); return o(a, "_invoke", { value: makeInvokeMethod(t, r, c) }), a; } function tryCatch(t, e, r) { try { return { type: "normal", arg: t.call(e, r) }; } catch (t) { return { type: "throw", arg: t }; } } e.wrap = wrap; var h = "suspendedStart", l = "suspendedYield", f = "executing", s = "completed", y = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var p = {}; define(p, a, function () { return this; }); var d = Object.getPrototypeOf, v = d && d(d(values([]))); v && v !== r && n.call(v, a) && (p = v); var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); function defineIteratorMethods(t) { ["next", "throw", "return"].forEach(function (e) { define(t, e, function (t) { return this._invoke(e, t); }); }); } function AsyncIterator(t, e) { function invoke(r, o, i, a) { var c = tryCatch(t[r], t, o); if ("throw" !== c.type) { var u = c.arg, h = u.value; return h && "object" == _typeof(h) && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { invoke("next", t, i, a); }, function (t) { invoke("throw", t, i, a); }) : e.resolve(h).then(function (t) { u.value = t, i(u); }, function (t) { return invoke("throw", t, i, a); }); } a(c.arg); } var r; o(this, "_invoke", { value: function value(t, n) { function callInvokeWithMethodAndArg() { return new e(function (e, r) { invoke(t, n, e, r); }); } return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(e, r, n) { var o = h; return function (i, a) { if (o === f) throw new Error("Generator is already running"); if (o === s) { if ("throw" === i) throw a; return { value: t, done: !0 }; } for (n.method = i, n.arg = a;;) { var c = n.delegate; if (c) { var u = maybeInvokeDelegate(c, n); if (u) { if (u === y) continue; return u; } } if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { if (o === h) throw o = s, n.arg; n.dispatchException(n.arg); } else "return" === n.method && n.abrupt("return", n.arg); o = f; var p = tryCatch(e, r, n); if ("normal" === p.type) { if (o = n.done ? s : l, p.arg === y) continue; return { value: p.arg, done: n.done }; } "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); } }; } function maybeInvokeDelegate(e, r) { var n = r.method, o = e.iterator[n]; if (o === t) return r.delegate = null, "throw" === n && e.iterator.return && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; var i = tryCatch(o, e.iterator, r.arg); if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; var a = i.arg; return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); } function pushTryEntry(t) { var e = { tryLoc: t[0] }; 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); } function resetTryEntry(t) { var e = t.completion || {}; e.type = "normal", delete e.arg, t.completion = e; } function Context(t) { this.tryEntries = [{ tryLoc: "root" }], t.forEach(pushTryEntry, this), this.reset(!0); } function values(e) { if (e || "" === e) { var r = e[a]; if (r) return r.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) { var o = -1, i = function next() { for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; return next.value = t, next.done = !0, next; }; return i.next = i; } } throw new TypeError(_typeof(e) + " is not iterable"); } return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), o(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { var e = "function" == typeof t && t.constructor; return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); }, e.mark = function (t) { return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; }, e.awrap = function (t) { return { __await: t }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { return this; }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { void 0 === i && (i = Promise); var a = new AsyncIterator(wrap(t, r, n, o), i); return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { return t.done ? t.value : a.next(); }); }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { return this; }), define(g, "toString", function () { return "[object Generator]"; }), e.keys = function (t) { var e = Object(t), r = []; for (var n in e) r.push(n); return r.reverse(), function next() { for (; r.length;) { var t = r.pop(); if (t in e) return next.value = t, next.done = !1, next; } return next.done = !0, next; }; }, e.values = values, Context.prototype = { constructor: Context, reset: function reset(e) { if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); }, stop: function stop() { this.done = !0; var t = this.tryEntries[0].completion; if ("throw" === t.type) throw t.arg; return this.rval; }, dispatchException: function dispatchException(e) { if (this.done) throw e; var r = this; function handle(n, o) { return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; } for (var o = this.tryEntries.length - 1; o >= 0; --o) { var i = this.tryEntries[o], a = i.completion; if ("root" === i.tryLoc) return handle("end"); if (i.tryLoc <= this.prev) { var c = n.call(i, "catchLoc"), u = n.call(i, "finallyLoc"); if (c && u) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } else if (c) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); } else { if (!u) throw new Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } } } }, abrupt: function abrupt(t, e) { for (var r = this.tryEntries.length - 1; r >= 0; --r) { var o = this.tryEntries[r]; if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { var i = o; break; } } i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); var a = i ? i.completion : {}; return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); }, complete: function complete(t, e) { if ("throw" === t.type) throw t.arg; return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; }, finish: function finish(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; } }, catch: function _catch(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.tryLoc === t) { var n = r.completion; if ("throw" === n.type) { var o = n.arg; resetTryEntry(r); } return o; } } throw new Error("illegal catch attempt"); }, delegateYield: function delegateYield(e, r, n) { return this.delegate = { iterator: values(e), resultName: r, nextLoc: n }, "next" === this.method && (this.arg = t), y; } }, e; }
2
+ 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; }
3
+ 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; }
1
4
  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); }
5
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
6
+ function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
2
7
  function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
3
8
  function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
4
9
  function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
@@ -28,4 +33,78 @@ export var HttpError = /*#__PURE__*/function (_Error) {
28
33
  return _this;
29
34
  }
30
35
  return _createClass(HttpError);
31
- }( /*#__PURE__*/_wrapNativeSuper(Error));
36
+ }( /*#__PURE__*/_wrapNativeSuper(Error));
37
+
38
+ // =============================================================================
39
+ // 框架内建报错的文案 —— 默认中文,调用方可以按请求语言换掉
40
+ // =============================================================================
41
+ // 这几句是从**库内部**抛出去的(准入判定、写动作的兜底),业务自己的报错在业务代码里
42
+ // 渲染(do-tv 是 (backend)/utils/i18n 的 serverT,抛出点就地翻成请求的语言)。库不知道
43
+ // 也不该知道各项目的 i18n 栈(next-intl、react-i18next、自研…),所以不给库塞字典,
44
+ // 只留一个「抛出前问一次」的钩子:谁做国际化,谁在应用启动时注册一个解析器。
45
+ //
46
+ // 为什么不回错误码让前端翻:这些 message 的读者不只是浏览器 —— CLI 与 agent 也读它,
47
+ // 它们没有前端那套翻译层,回个码等于让它们打印一串 `errors.UNAUTHENTICATED`。在服务端
48
+ // 渲染成字符串,所有读者拿到的都是人话。
49
+ //
50
+ // 解析器按**请求**解析(语言藏在 cookie / Accept-Language 里),所以它是每次抛出时现调
51
+ // 的函数,而不是启动时钉死的一份字符串。允许 async —— 各抛出点本来就在 async 函数里,
52
+ // 而「按当前请求取翻译」在 Next 里就得 await(do-tv 的 requestText() 走 next/headers)。
53
+
54
+ export var defaultHttpErrorMessages = {
55
+ unauthenticated: "未登录",
56
+ forbidden: "没有权限",
57
+ missingId: "缺少 id",
58
+ editNotFound: "未找到编辑对象,或没有权限",
59
+ deleteNotFound: "未找到删除对象,或没有权限",
60
+ scopeUnresolved: function scopeUnresolved(column) {
61
+ return "\u65E0\u6CD5\u786E\u5B9A\u8FD9\u6B21\u8BF7\u6C42\u7684\u5F52\u5C5E\uFF08".concat(column, "\uFF09\u3002\u8FD9\u4E0D\u662F\u300C\u4E0D\u505A\u9694\u79BB\u300D\u2014\u2014\u8BF7\u6C42\u88AB\u62D2\u7EDD\u3002");
62
+ }
63
+ };
64
+ var resolver;
65
+
66
+ /**
67
+ * 注册文案解析器,应用启动时调一次(再次调用覆盖;传 undefined 恢复默认)。
68
+ * 不注册就一直是 defaultHttpErrorMessages 那几句中文 —— 没做国际化的项目零感知。
69
+ */
70
+ export function setHttpErrorMessageResolver(next) {
71
+ resolver = next;
72
+ }
73
+
74
+ /**
75
+ * 取出当前生效的整份文案。**在抛出点现调**,别在模块顶层缓存 —— 解析器是按请求判
76
+ * 语言的,缓存住就把上一个请求的语言漏给下一个。
77
+ *
78
+ * 解析器自己抛了错(比如语言包没加载到)就回落默认中文:报错文案出不来不该把
79
+ * 原本那个 401/403 也吞掉。
80
+ */
81
+ export function httpErrorMessages() {
82
+ return _httpErrorMessages.apply(this, arguments);
83
+ }
84
+ function _httpErrorMessages() {
85
+ _httpErrorMessages = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
86
+ var overrides, _resolver;
87
+ return _regeneratorRuntime().wrap(function _callee$(_context) {
88
+ while (1) switch (_context.prev = _context.next) {
89
+ case 0:
90
+ _context.prev = 0;
91
+ _context.next = 3;
92
+ return (_resolver = resolver) === null || _resolver === void 0 ? void 0 : _resolver();
93
+ case 3:
94
+ overrides = _context.sent;
95
+ _context.next = 9;
96
+ break;
97
+ case 6:
98
+ _context.prev = 6;
99
+ _context.t0 = _context["catch"](0);
100
+ overrides = undefined;
101
+ case 9:
102
+ return _context.abrupt("return", _objectSpread(_objectSpread({}, defaultHttpErrorMessages), overrides));
103
+ case 10:
104
+ case "end":
105
+ return _context.stop();
106
+ }
107
+ }, _callee, null, [[0, 6]]);
108
+ }));
109
+ return _httpErrorMessages.apply(this, arguments);
110
+ }
@@ -8,7 +8,7 @@ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" ==
8
8
  function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
9
9
  function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
10
10
  import { TypedNextResponse } from "@ai0x0/next-rest-framework";
11
- import { HttpError } from "../errors.js";
11
+ import { HttpError, httpErrorMessages } from "../errors.js";
12
12
  import { isEmptyScopeValue, NO_SCOPE } from "../scope.js";
13
13
 
14
14
  // ==============================================================================
@@ -100,52 +100,83 @@ function _resolveAccess() {
100
100
  case 9:
101
101
  session = _context.t0;
102
102
  if (!(access !== null && access !== void 0 && access.can)) {
103
- _context.next = 19;
103
+ _context.next = 33;
104
104
  break;
105
105
  }
106
106
  if (session) {
107
- _context.next = 13;
107
+ _context.next = 17;
108
108
  break;
109
109
  }
110
- throw new HttpError(401, "未登录");
111
- case 13:
110
+ _context.t1 = HttpError;
112
111
  _context.next = 15;
112
+ return httpErrorMessages();
113
+ case 15:
114
+ _context.t2 = _context.sent.unauthenticated;
115
+ throw new _context.t1(401, _context.t2);
116
+ case 17:
117
+ _context.next = 19;
113
118
  return access.can({
114
119
  method: method,
115
120
  session: session
116
121
  });
117
- case 15:
122
+ case 19:
118
123
  verdict = _context.sent;
119
124
  if (!(verdict !== true)) {
120
- _context.next = 19;
125
+ _context.next = 33;
121
126
  break;
122
127
  }
123
128
  denial = _typeof(verdict) === "object" ? verdict : {};
124
- throw new HttpError((_denial$status = denial.status) !== null && _denial$status !== void 0 ? _denial$status : 403, (_denial$message = denial.message) !== null && _denial$message !== void 0 ? _denial$message : "没有权限");
125
- case 19:
129
+ _context.t3 = HttpError;
130
+ _context.t4 = (_denial$status = denial.status) !== null && _denial$status !== void 0 ? _denial$status : 403;
131
+ if (!((_denial$message = denial.message) !== null && _denial$message !== void 0)) {
132
+ _context.next = 28;
133
+ break;
134
+ }
135
+ _context.t5 = _denial$message;
136
+ _context.next = 31;
137
+ break;
138
+ case 28:
139
+ _context.next = 30;
140
+ return httpErrorMessages();
141
+ case 30:
142
+ _context.t5 = _context.sent.forbidden;
143
+ case 31:
144
+ _context.t6 = _context.t5;
145
+ throw new _context.t3(_context.t4, _context.t6);
146
+ case 33:
126
147
  if (wantsScope) {
127
- _context.next = 21;
148
+ _context.next = 35;
128
149
  break;
129
150
  }
130
151
  return _context.abrupt("return", {
131
152
  scope: NO_SCOPE,
132
153
  session: session
133
154
  });
134
- case 21:
155
+ case 35:
135
156
  if (session) {
136
- _context.next = 23;
157
+ _context.next = 41;
137
158
  break;
138
159
  }
139
- throw new HttpError(401, "未登录");
140
- case 23:
160
+ _context.t7 = HttpError;
161
+ _context.next = 39;
162
+ return httpErrorMessages();
163
+ case 39:
164
+ _context.t8 = _context.sent.unauthenticated;
165
+ throw new _context.t7(401, _context.t8);
166
+ case 41:
141
167
  column = (_access$scope$column = access === null || access === void 0 || (_access$scope = access.scope) === null || _access$scope === void 0 ? void 0 : _access$scope.column) !== null && _access$scope$column !== void 0 ? _access$scope$column : DEFAULT_SCOPE_COLUMN;
142
168
  value = access !== null && access !== void 0 && access.scope ? access.scope.value(session) : session.userId;
143
169
  if (!isEmptyScopeValue(value)) {
144
- _context.next = 27;
170
+ _context.next = 49;
145
171
  break;
146
172
  }
147
- throw new HttpError(403, "\u65E0\u6CD5\u786E\u5B9A\u8FD9\u6B21\u8BF7\u6C42\u7684\u5F52\u5C5E\uFF08".concat(column, "\uFF09\u3002\u8FD9\u4E0D\u662F\u300C\u4E0D\u505A\u9694\u79BB\u300D\u2014\u2014\u8BF7\u6C42\u88AB\u62D2\u7EDD\u3002"));
148
- case 27:
173
+ _context.t9 = HttpError;
174
+ _context.next = 47;
175
+ return httpErrorMessages();
176
+ case 47:
177
+ _context.t10 = _context.sent.scopeUnresolved(column);
178
+ throw new _context.t9(403, _context.t10);
179
+ case 49:
149
180
  return _context.abrupt("return", {
150
181
  scope: {
151
182
  column: column,
@@ -153,7 +184,7 @@ function _resolveAccess() {
153
184
  },
154
185
  session: session
155
186
  });
156
- case 28:
187
+ case 50:
157
188
  case "end":
158
189
  return _context.stop();
159
190
  }
@@ -14,6 +14,7 @@ import maxLines from "./max-lines.js";
14
14
  import apiRouteViaNrf from "./api-route-via-nrf.js";
15
15
  import noDirectApiUrl from "./no-direct-api-url.js";
16
16
  import noHardcodedText from "./no-hardcoded-text.js";
17
+ import noAccentBorder from "./no-accent-border.js";
17
18
 
18
19
  const rules = {
19
20
  "require-section-divider": requireSectionDivider,
@@ -29,6 +30,7 @@ const rules = {
29
30
  "api-route-via-nrf": apiRouteViaNrf,
30
31
  "no-direct-api-url": noDirectApiUrl,
31
32
  "no-hardcoded-text": noHardcodedText,
33
+ "no-accent-border": noAccentBorder,
32
34
  };
33
35
 
34
36
  // 这三条只管「特定位置」的代码,不能跟着 recommended 全局开:
@@ -0,0 +1,246 @@
1
+ /**
2
+ * no-accent-border
3
+ * ----------------
4
+ * **悬停 / 聚焦 / 按下的反馈不许描亮边**,走填充底色;也别显式写回带描边的那几个变体。
5
+ *
6
+ * 拦两类:
7
+ *
8
+ * 1. css`` 或 style 对象里,**交互态选择器内**(:hover / :focus / :focus-within /
9
+ * :focus-visible / :active)把 `border` / `border-color` 设成强调色 token
10
+ * (colorPrimary* / colorInfo* / colorSuccess* / colorWarning* / colorError*);
11
+ * 2. JSX 上显式写 `variant="outlined"` / `variant="dashed"` / `type="default"`。
12
+ *
13
+ * **为什么是「交互态」而不是所有亮边**:常态的亮边多半是状态语言 —— 选中的那一格、框选的
14
+ * 那一块、裁剪区、报错块的红框、拖拽的落点。那些是「这一块此刻不一样」的记号,本来就该跳出
15
+ * 版面。而悬停只是「鼠标恰好停在这儿」,给它一圈亮边,一排控件里就总有一个在抢视线。
16
+ *
17
+ * **第 2 类为什么连 `type="default"` 一起拦**:项目一般会用 ConfigProvider 把全站默认变体
18
+ * 设成 filled(antd v6 的 button/input/select 都收 variant)。而显式写 `type="default"`
19
+ * 会走 antd 的 legacy type 映射、解析成 outlined,**把那个全站默认顶掉** —— 写的人以为自己
20
+ * 只是「要一颗普通按钮」,拿到的却是描边的那一档。`variant="outlined"` 同理,只是更直白。
21
+ *
22
+ * **放行**:常态与选中态的任何边框、中性色描边(colorBorder / colorBorderSecondary /
23
+ * colorSplit / colorText*)、`transparent`、`none`,以及交互态里改底色、改阴影、改文字色。
24
+ *
25
+ * **真要在悬停时描亮边**(输入框聚焦、画布里的落点提示这类),就地写一行
26
+ * `eslint-disable-next-line ai0x0/no-accent-border` 并注明为什么。
27
+ */
28
+
29
+ // ==============================================================================
30
+ // 什么算「强调色」
31
+ // ==============================================================================
32
+ // 按 antd 的色名族判,不看具体色值:这几族是主题里用来「抢眼」的那批,而 colorBorder /
33
+ // colorSplit / colorText 那些是结构色。`colorPrimaryBorder` 也在内 —— 它的用途正是描一圈
34
+ // 主色边,恰恰是这条规则要拦的东西。
35
+ const ACCENT_TOKEN =
36
+ /\b(colorPrimary|colorInfo|colorSuccess|colorWarning|colorError|colorLink|colorHighlight)/;
37
+
38
+ // 要检查的属性名。只管颜色那一半:border-width / border-radius / border-style 与这条无关。
39
+ // 驼峰(style 对象)先拆成连字符再比,两种写法共用这一条。
40
+ const BORDER_PROPERTY = /^border(-(top|right|bottom|left))?(-color)?$/;
41
+
42
+ const isBorderProperty = (name) =>
43
+ BORDER_PROPERTY.test(
44
+ String(name)
45
+ .replace(/([a-z])([A-Z])/g, "$1-$2")
46
+ .toLowerCase(),
47
+ );
48
+
49
+ // ==============================================================================
50
+ // css`` 模板:把插值编号后按声明切开
51
+ // ==============================================================================
52
+ // 模板里 `${token.colorPrimary}` 是一个 AST 表达式、不是文本,所以先把它换成一个编号占位符
53
+ // 拼出整段文本,命中某条声明时再顺着编号回去取那个表达式的源码。
54
+ // 占位符取私有区字符:CSS 正文里不会出现它,也不是控制字符。
55
+ const MARK = "";
56
+
57
+ function templateText(node) {
58
+ return node.quasi.quasis
59
+ .map((quasi, index) =>
60
+ index < node.quasi.expressions.length
61
+ ? `${quasi.value.raw}${MARK}${String(index)}${MARK}`
62
+ : quasi.value.raw,
63
+ )
64
+ .join("");
65
+ }
66
+
67
+ /** 一段文本里引用到的插值编号。 */
68
+ function marksIn(text) {
69
+ return [...text.matchAll(new RegExp(`${MARK}(\\d+)${MARK}`, "gu"))].map(
70
+ (match) => Number(match[1]),
71
+ );
72
+ }
73
+
74
+ // 交互态的伪类。`:focus-within` 与 `:focus-visible` 都算:它们是同一件事的不同精度。
75
+ const INTERACTIVE = /:(hover|focus|focus-within|focus-visible|active)\b/;
76
+
77
+ /**
78
+ * 这条声明落在哪几层选择器里。
79
+ *
80
+ * 扫一遍文本、拿 `{}` 当栈:遇到 `{` 把它前面那段(上一个 `}` 或 `;` 之后的部分)当作选择器
81
+ * 压进去,遇到 `}` 弹出。于是任何位置都能问「我现在在哪几层里」——嵌套写法(`&:hover { … }`
82
+ * 套在别的块里)也答得出来。
83
+ */
84
+ function selectorStackAt(text, index) {
85
+ const stack = [];
86
+ let head = 0;
87
+ for (let at = 0; at < index; at += 1) {
88
+ const char = text[at];
89
+ if (char === "{") {
90
+ stack.push(text.slice(head, at));
91
+ head = at + 1;
92
+ } else if (char === "}") {
93
+ stack.pop();
94
+ head = at + 1;
95
+ } else if (char === ";") {
96
+ head = at + 1;
97
+ }
98
+ }
99
+ return stack;
100
+ }
101
+
102
+ function checkCssTemplate(context, node) {
103
+ const text = templateText(node);
104
+ // 逐条声明看:属性名 + 值。嵌套选择器与注释不会被当成声明(它们前面没有 `属性名:`)。
105
+ for (const match of text.matchAll(/([a-zA-Z-]+)\s*:\s*([^;{}]+)/gu)) {
106
+ const [, property, value] = match;
107
+ if (!isBorderProperty(property)) {
108
+ continue;
109
+ }
110
+ // 常态的亮边是状态语言(选中 / 框选 / 报错),放行;只管交互态里的那种。
111
+ if (
112
+ !selectorStackAt(text, match.index).some((one) => INTERACTIVE.test(one))
113
+ ) {
114
+ continue;
115
+ }
116
+ for (const index of marksIn(value)) {
117
+ const expression = node.quasi.expressions[index];
118
+ if (!expression) {
119
+ continue;
120
+ }
121
+ const source = context.sourceCode.getText(expression);
122
+ if (ACCENT_TOKEN.test(source)) {
123
+ // **报在整只模板上,不是那个插值上**:模板内部的 `/* */` 是 CSS 注释、不是 JS 注释节点,
124
+ // 落在里头的报错没法就地 `eslint-disable-next-line`。报在模板这一行,豁免就写在
125
+ // `xxx: css\`` 前面一行 —— 与 no-hardcoded-style 同一个落点(它也这么报)。
126
+ // 代价是同一只模板里的多条违规都指到同一行,所以消息里带上属性名与那个 token。
127
+ context.report({
128
+ node,
129
+ messageId: "accentBorder",
130
+ data: { property: property.trim(), token: source },
131
+ });
132
+ }
133
+ }
134
+ }
135
+ }
136
+
137
+ // ==============================================================================
138
+ // style 对象 / createStyles 的对象写法
139
+ // ==============================================================================
140
+
141
+ function checkStyleObject(context, object, interactive = false) {
142
+ for (const prop of object.properties) {
143
+ if (prop.type !== "Property") {
144
+ continue;
145
+ }
146
+ const key =
147
+ prop.key.type === "Identifier"
148
+ ? prop.key.name
149
+ : prop.key.type === "Literal"
150
+ ? String(prop.key.value)
151
+ : "";
152
+ // 嵌套一层(伪类、子选择器)照样要看,并把「进没进交互态」带下去。
153
+ if (prop.value.type === "ObjectExpression") {
154
+ checkStyleObject(
155
+ context,
156
+ prop.value,
157
+ interactive || INTERACTIVE.test(key),
158
+ );
159
+ continue;
160
+ }
161
+ if (!interactive || !key || !isBorderProperty(key)) {
162
+ continue;
163
+ }
164
+ const source = context.sourceCode.getText(prop.value);
165
+ if (ACCENT_TOKEN.test(source)) {
166
+ context.report({
167
+ node: prop.value,
168
+ messageId: "accentBorder",
169
+ data: { property: key, token: source },
170
+ });
171
+ }
172
+ }
173
+ }
174
+
175
+ // ==============================================================================
176
+ // JSX:带描边的那两个变体
177
+ // ==============================================================================
178
+
179
+ function attributeValue(attribute) {
180
+ const value = attribute.value;
181
+ if (!value) {
182
+ return "";
183
+ }
184
+ if (value.type === "Literal") {
185
+ return String(value.value);
186
+ }
187
+ if (
188
+ value.type === "JSXExpressionContainer" &&
189
+ value.expression.type === "Literal"
190
+ ) {
191
+ return String(value.expression.value);
192
+ }
193
+ return "";
194
+ }
195
+
196
+ /** @type {import('eslint').Rule.RuleModule} */
197
+ const rule = {
198
+ meta: {
199
+ type: "suggestion",
200
+ docs: {
201
+ description:
202
+ "边框不许用强调色;悬停 / 选中的反馈走填充底色,别用带描边的按钮变体。",
203
+ },
204
+ schema: [],
205
+ messages: {
206
+ accentBorder:
207
+ "悬停 / 聚焦时把 `{{property}}` 描成强调色({{token}})。一圈亮边的视觉重量远超它该有的分量,而悬停只是「鼠标恰好停在这儿」—— 反馈改走填充底色(token.colorFill / colorFillSecondary,按钮走 buttonSurface 那类配方)。常态与选中态的亮边不受这条管;输入框聚焦这种确实要描边的,就地 disable 并写明理由。",
208
+ outlinedVariant:
209
+ '显式写 `{{attribute}}="{{value}}"` 会把全站的 filled 默认顶掉,拿到的是描边那一档(`type="default"` 走 antd 的 legacy 映射,解析成 outlined)。要一颗普通按钮就什么都别写;次要按钮写 `color="default" variant="filled"`,要强调的用 `variant="solid"`。确实需要描边的(如浅色行里的输入框)就地 disable 并写明理由。',
210
+ },
211
+ },
212
+ create(context) {
213
+ return {
214
+ JSXAttribute(node) {
215
+ const name = node.name.type === "JSXIdentifier" ? node.name.name : "";
216
+ if (name === "style") {
217
+ const expr = node.value?.expression;
218
+ if (expr?.type === "ObjectExpression") {
219
+ checkStyleObject(context, expr);
220
+ }
221
+ return;
222
+ }
223
+ const value = attributeValue(node);
224
+ const outlined =
225
+ (name === "variant" &&
226
+ (value === "outlined" || value === "dashed")) ||
227
+ (name === "type" && value === "default");
228
+ if (outlined) {
229
+ context.report({
230
+ node,
231
+ messageId: "outlinedVariant",
232
+ data: { attribute: name, value },
233
+ });
234
+ }
235
+ },
236
+
237
+ TaggedTemplateExpression(node) {
238
+ if (node.tag.type === "Identifier" && node.tag.name === "css") {
239
+ checkCssTemplate(context, node);
240
+ }
241
+ },
242
+ };
243
+ },
244
+ };
245
+
246
+ export default rule;
@@ -39,11 +39,11 @@ function createPutActionCore({
39
39
  return async (body, { scope }) => {
40
40
  const id = body.id;
41
41
  if (!id) {
42
- throw new import_errors.HttpError(400, "缺少 id");
42
+ throw new import_errors.HttpError(400, (await (0, import_errors.httpErrorMessages)()).missingId);
43
43
  }
44
44
  const [data] = await db.update(table).set((0, import_transform_body.transformBody)(body)).where(byId(table, id, scope)).returning();
45
45
  if (!data && scope !== import_scope.NO_SCOPE) {
46
- throw new import_errors.HttpError(404, "未找到编辑对象,或没有权限");
46
+ throw new import_errors.HttpError(404, (await (0, import_errors.httpErrorMessages)()).editNotFound);
47
47
  }
48
48
  return data;
49
49
  };
@@ -54,11 +54,11 @@ function createDeleteActionCore({
54
54
  }) {
55
55
  return async ({ id }, { scope }) => {
56
56
  if (!id) {
57
- throw new import_errors.HttpError(400, "缺少 id");
57
+ throw new import_errors.HttpError(400, (await (0, import_errors.httpErrorMessages)()).missingId);
58
58
  }
59
59
  const rows = await db.delete(table).where(byId(table, id, scope)).returning();
60
60
  if (rows.length === 0 && scope !== import_scope.NO_SCOPE) {
61
- throw new import_errors.HttpError(404, "未找到删除对象,或没有权限");
61
+ throw new import_errors.HttpError(404, (await (0, import_errors.httpErrorMessages)()).deleteNotFound);
62
62
  }
63
63
  return rows;
64
64
  };
@@ -2,3 +2,33 @@ export declare class HttpError extends Error {
2
2
  status: number;
3
3
  constructor(status: number, message: string);
4
4
  }
5
+ export interface HttpErrorMessages {
6
+ /** 401:会话不存在。 */
7
+ unauthenticated: string;
8
+ /** 403:access.can 拒绝且没给自定义文案。 */
9
+ forbidden: string;
10
+ /** 400:PUT / DELETE 请求体缺 id。 */
11
+ missingId: string;
12
+ /** 404:PUT 影响 0 行(不存在或不属于调用者)。 */
13
+ editNotFound: string;
14
+ /** 404:DELETE 影响 0 行。 */
15
+ deleteNotFound: string;
16
+ /** 403:作用域值解析不出来 —— 多半是配错了列名,但请求照样要被拒绝。 */
17
+ scopeUnresolved: (column: string) => string;
18
+ }
19
+ export declare const defaultHttpErrorMessages: HttpErrorMessages;
20
+ type HttpErrorMessageResolver = () => Partial<HttpErrorMessages> | Promise<Partial<HttpErrorMessages>>;
21
+ /**
22
+ * 注册文案解析器,应用启动时调一次(再次调用覆盖;传 undefined 恢复默认)。
23
+ * 不注册就一直是 defaultHttpErrorMessages 那几句中文 —— 没做国际化的项目零感知。
24
+ */
25
+ export declare function setHttpErrorMessageResolver(next: HttpErrorMessageResolver | undefined): void;
26
+ /**
27
+ * 取出当前生效的整份文案。**在抛出点现调**,别在模块顶层缓存 —— 解析器是按请求判
28
+ * 语言的,缓存住就把上一个请求的语言漏给下一个。
29
+ *
30
+ * 解析器自己抛了错(比如语言包没加载到)就回落默认中文:报错文案出不来不该把
31
+ * 原本那个 401/403 也吞掉。
32
+ */
33
+ export declare function httpErrorMessages(): Promise<HttpErrorMessages>;
34
+ export {};
@@ -19,7 +19,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  // src/backend/errors.ts
20
20
  var errors_exports = {};
21
21
  __export(errors_exports, {
22
- HttpError: () => HttpError
22
+ HttpError: () => HttpError,
23
+ defaultHttpErrorMessages: () => defaultHttpErrorMessages,
24
+ httpErrorMessages: () => httpErrorMessages,
25
+ setHttpErrorMessageResolver: () => setHttpErrorMessageResolver
23
26
  });
24
27
  module.exports = __toCommonJS(errors_exports);
25
28
  var HttpError = class extends Error {
@@ -29,7 +32,31 @@ var HttpError = class extends Error {
29
32
  this.status = status;
30
33
  }
31
34
  };
35
+ var defaultHttpErrorMessages = {
36
+ unauthenticated: "未登录",
37
+ forbidden: "没有权限",
38
+ missingId: "缺少 id",
39
+ editNotFound: "未找到编辑对象,或没有权限",
40
+ deleteNotFound: "未找到删除对象,或没有权限",
41
+ scopeUnresolved: (column) => `无法确定这次请求的归属(${column})。这不是「不做隔离」——请求被拒绝。`
42
+ };
43
+ var resolver;
44
+ function setHttpErrorMessageResolver(next) {
45
+ resolver = next;
46
+ }
47
+ async function httpErrorMessages() {
48
+ let overrides;
49
+ try {
50
+ overrides = await (resolver == null ? void 0 : resolver());
51
+ } catch {
52
+ overrides = void 0;
53
+ }
54
+ return { ...defaultHttpErrorMessages, ...overrides };
55
+ }
32
56
  // Annotate the CommonJS export names for ESM import in node:
33
57
  0 && (module.exports = {
34
- HttpError
58
+ HttpError,
59
+ defaultHttpErrorMessages,
60
+ httpErrorMessages,
61
+ setHttpErrorMessageResolver
35
62
  });
@@ -49,26 +49,29 @@ async function resolveAccess({
49
49
  const session = wantsScope || (access == null ? void 0 : access.can) || (access == null ? void 0 : access.stamp) ? await loadSession() : void 0;
50
50
  if (access == null ? void 0 : access.can) {
51
51
  if (!session) {
52
- throw new import_errors.HttpError(401, "未登录");
52
+ throw new import_errors.HttpError(401, (await (0, import_errors.httpErrorMessages)()).unauthenticated);
53
53
  }
54
54
  const verdict = await access.can({ method, session });
55
55
  if (verdict !== true) {
56
56
  const denial = typeof verdict === "object" ? verdict : {};
57
- throw new import_errors.HttpError(denial.status ?? 403, denial.message ?? "没有权限");
57
+ throw new import_errors.HttpError(
58
+ denial.status ?? 403,
59
+ denial.message ?? (await (0, import_errors.httpErrorMessages)()).forbidden
60
+ );
58
61
  }
59
62
  }
60
63
  if (!wantsScope) {
61
64
  return { scope: import_scope.NO_SCOPE, session };
62
65
  }
63
66
  if (!session) {
64
- throw new import_errors.HttpError(401, "未登录");
67
+ throw new import_errors.HttpError(401, (await (0, import_errors.httpErrorMessages)()).unauthenticated);
65
68
  }
66
69
  const column = ((_a = access == null ? void 0 : access.scope) == null ? void 0 : _a.column) ?? DEFAULT_SCOPE_COLUMN;
67
70
  const value = (access == null ? void 0 : access.scope) ? access.scope.value(session) : session.userId;
68
71
  if ((0, import_scope.isEmptyScopeValue)(value)) {
69
72
  throw new import_errors.HttpError(
70
73
  403,
71
- `无法确定这次请求的归属(${column})。这不是「不做隔离」——请求被拒绝。`
74
+ (await (0, import_errors.httpErrorMessages)()).scopeUnresolved(column)
72
75
  );
73
76
  }
74
77
  return { scope: { column, value }, session };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai0x0/utils",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "AI0x0 utils",
5
5
  "keywords": [
6
6
  "ai0x0"