@ai0x0/utils 0.1.1 → 0.1.3
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 +3 -2
- package/es/backend/route-operation/write-operation-core.js +55 -52
- package/es/backend/schemas/index.d.ts +245 -235
- package/es/backend/schemas/index.js +15 -5
- package/es/backend/sqlite/schemas/index.d.ts +3 -0
- package/es/backend/sqlite/schemas/index.js +15 -5
- package/lib/backend/route-operation/write-operation-core.js +3 -8
- package/lib/backend/schemas/index.d.ts +245 -235
- package/lib/backend/schemas/index.js +7 -6
- package/lib/backend/sqlite/schemas/index.d.ts +3 -0
- package/lib/backend/sqlite/schemas/index.js +6 -2
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -68,7 +68,7 @@ export const getListOperation = createGetListOperation({ db, getSession });
|
|
|
68
68
|
|
|
69
69
|
| Export | Purpose | Required options | Notable optional options |
|
|
70
70
|
| ------------------- | -------------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
71
|
-
| `createTableSchema` | pg `Table` + 5 zod schemas | `name`, `columns` | `refineSchema`, `extraConfig`
|
|
71
|
+
| `createTableSchema` | pg `Table` + 5 zod schemas | `name`, `columns` | `serverColumns`, `refineSchema`, `extraConfig` |
|
|
72
72
|
| `getOperation` | GET one by filters | `schemas.query`, `schemas.response` | `table`, `setParams`, `relations`, `jsonArrayFields`, `access.byCreator`, `handler`, `catch`, `openApiOperation` |
|
|
73
73
|
| `getListOperation` | GET paginated list | `schemas.query`, `schemas.response` | `table`, `setParams`, `relations`, `jsonArrayFields`, `handler`, `catch`, `openApiOperation` |
|
|
74
74
|
| `postOperation` | POST create | `schemas.body` | `table`, `contentType`, `parseBody`, `schemas.response`, `setBody`, `handler`, `catch`, `openApiOperation` |
|
|
@@ -308,7 +308,8 @@ export const { GET } = route({
|
|
|
308
308
|
|
|
309
309
|
## Built-in Conventions
|
|
310
310
|
|
|
311
|
-
- **Base fields** auto-added by `createTableSchema`: `id` (uuid, pk, default random) / `creatorId` / `editorId` / `accessedAt` / `createdAt` / `updatedAt`.
|
|
311
|
+
- **Base fields** auto-added by `createTableSchema`: `id` (uuid, pk, default random) / `creatorId` / `editorId` / `accessedAt` / `createdAt` / `updatedAt`. They never appear in the insert/update schemas — the server writes them.
|
|
312
|
+
- **Server-owned business columns** (`serverColumns: { ownerId: uuid("owner_id").notNull() }`): a second column bucket, alongside `columns`. Everything in it goes on the table and into `selectSchema` (you can read it), but **not** into `insertSchema` / `updateSchema` — exactly how the base fields already behave, just opened up to columns you declare yourself. Use it for anything carrying ownership (`ownerId`, `tenantId`): letting a client send that column is letting it choose whose row this is. Pair it with `access.scope.column` — that decides _which rows you see_, this decides _that you cannot write the column_. Which bucket a column sits in is the whole rule, so there is nothing to remember and nothing to forget; `insertSchema.omit(...)` per table would work too, but missing one table is not a compile error, it is a silent privilege escalation.
|
|
312
313
|
- **Row-level scope** (`access`, on by default for every verb): the request may only see / touch rows whose scope column matches. Three knobs:
|
|
313
314
|
- `byCreator?: boolean` — the old spelling, equivalent to `scope: { column: "creatorId", value: (s) => s.userId }`. Default `true`.
|
|
314
315
|
- `scope?: { column?, value }` — pick the column (default `creatorId`) and derive the value from the session. Return an array for `IN (...)`. Use this when a row can belong to something other than one user (a team, a workspace).
|
|
@@ -87,9 +87,16 @@ export function createPostOperationFactory(_ref) {
|
|
|
87
87
|
case 13:
|
|
88
88
|
extraBody = _context.t0;
|
|
89
89
|
// 新建的行归属这次请求的作用域。默认情形下作用域就是 creatorId = 当前用户,
|
|
90
|
-
//
|
|
90
|
+
// 换了归属列(比如 ownerId)时它自动跟着换。
|
|
91
91
|
// 想再记一个「谁建的」用 setBody 补 —— 那是调用方的语义,库不猜。
|
|
92
|
-
|
|
92
|
+
//
|
|
93
|
+
// **归属戳必须最后展开**,这是一条安全不变量,别为了「让调用方能覆盖」而调顺序:
|
|
94
|
+
// `.input({ body })` 只校验、**不替换**请求体(next-rest-framework 交给 handler 的
|
|
95
|
+
// clone 会重新解析原始 JSON),而 createPostAction 直接 insert,中间没有第二道
|
|
96
|
+
// schema 过滤。所以只要 body 排在戳后面,客户端在 JSON 里塞一个 `ownerId` /
|
|
97
|
+
// `creatorId` 就能把行写到别人名下 —— 调用方把该列从 schema 里 omit 掉也拦不住。
|
|
98
|
+
// 见 __tests__/route-operation-scope-stamp。
|
|
99
|
+
params = _objectSpread(_objectSpread(_objectSpread({}, body), extraBody), scopeStamp(scope));
|
|
93
100
|
if (!(table && db)) {
|
|
94
101
|
_context.next = 21;
|
|
95
102
|
break;
|
|
@@ -183,7 +190,7 @@ export function createPutOperationFactory(_ref5) {
|
|
|
183
190
|
status: 200
|
|
184
191
|
}]).handler( /*#__PURE__*/function () {
|
|
185
192
|
var _ref8 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(req) {
|
|
186
|
-
var _yield$loadSession, loadSession, _yield$resolveAccess2, scope, body, extraBody, params, raw, data;
|
|
193
|
+
var _yield$loadSession, loadSession, _yield$resolveAccess2, scope, body, extraBody, editorId, params, raw, data;
|
|
187
194
|
return _regeneratorRuntime().wrap(function _callee2$(_context2) {
|
|
188
195
|
while (1) switch (_context2.prev = _context2.next) {
|
|
189
196
|
case 0:
|
|
@@ -213,42 +220,38 @@ export function createPutOperationFactory(_ref5) {
|
|
|
213
220
|
_context2.t0 = {};
|
|
214
221
|
case 14:
|
|
215
222
|
extraBody = _context2.t0;
|
|
216
|
-
_context2.
|
|
217
|
-
_context2.t2 = _objectSpread;
|
|
218
|
-
_context2.next = 19;
|
|
223
|
+
_context2.next = 17;
|
|
219
224
|
return loadSession();
|
|
220
|
-
case
|
|
221
|
-
_context2.
|
|
222
|
-
_context2.
|
|
223
|
-
if (_context2.
|
|
224
|
-
_context2.next =
|
|
225
|
+
case 17:
|
|
226
|
+
_context2.t2 = _yield$loadSession = _context2.sent;
|
|
227
|
+
_context2.t1 = _context2.t2 === null;
|
|
228
|
+
if (_context2.t1) {
|
|
229
|
+
_context2.next = 21;
|
|
225
230
|
break;
|
|
226
231
|
}
|
|
227
|
-
_context2.
|
|
228
|
-
case
|
|
229
|
-
if (!_context2.
|
|
230
|
-
_context2.next =
|
|
232
|
+
_context2.t1 = _yield$loadSession === void 0;
|
|
233
|
+
case 21:
|
|
234
|
+
if (!_context2.t1) {
|
|
235
|
+
_context2.next = 25;
|
|
231
236
|
break;
|
|
232
237
|
}
|
|
233
|
-
_context2.
|
|
234
|
-
_context2.next =
|
|
238
|
+
_context2.t3 = void 0;
|
|
239
|
+
_context2.next = 26;
|
|
235
240
|
break;
|
|
236
|
-
case
|
|
237
|
-
_context2.
|
|
238
|
-
case
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
_context2.t10 = extraBody;
|
|
246
|
-
params = (0, _context2.t1)(_context2.t9, _context2.t10);
|
|
241
|
+
case 25:
|
|
242
|
+
_context2.t3 = _yield$loadSession.userId;
|
|
243
|
+
case 26:
|
|
244
|
+
editorId = _context2.t3;
|
|
245
|
+
// editorId 同样**最后**展开:它是审计字段,让请求体覆盖等于让人随便写「是谁改的」。
|
|
246
|
+
// 理由与 POST 那边的归属戳一样,见那里的说明。
|
|
247
|
+
params = _objectSpread(_objectSpread(_objectSpread({}, body), extraBody), {}, {
|
|
248
|
+
editorId: editorId
|
|
249
|
+
});
|
|
247
250
|
if (!(table && db)) {
|
|
248
|
-
_context2.next =
|
|
251
|
+
_context2.next = 34;
|
|
249
252
|
break;
|
|
250
253
|
}
|
|
251
|
-
_context2.next =
|
|
254
|
+
_context2.next = 31;
|
|
252
255
|
return createAction({
|
|
253
256
|
bodySchema: schemas.body,
|
|
254
257
|
db: db,
|
|
@@ -256,44 +259,44 @@ export function createPutOperationFactory(_ref5) {
|
|
|
256
259
|
})(params, {
|
|
257
260
|
scope: scope
|
|
258
261
|
});
|
|
259
|
-
case
|
|
260
|
-
_context2.
|
|
261
|
-
_context2.next =
|
|
262
|
+
case 31:
|
|
263
|
+
_context2.t4 = _context2.sent;
|
|
264
|
+
_context2.next = 35;
|
|
262
265
|
break;
|
|
263
|
-
case
|
|
264
|
-
_context2.
|
|
265
|
-
case
|
|
266
|
-
raw = _context2.
|
|
266
|
+
case 34:
|
|
267
|
+
_context2.t4 = params;
|
|
268
|
+
case 35:
|
|
269
|
+
raw = _context2.t4;
|
|
267
270
|
if (!handler) {
|
|
268
|
-
_context2.next =
|
|
271
|
+
_context2.next = 42;
|
|
269
272
|
break;
|
|
270
273
|
}
|
|
271
|
-
_context2.next =
|
|
274
|
+
_context2.next = 39;
|
|
272
275
|
return handler({
|
|
273
276
|
data: raw,
|
|
274
277
|
params: params,
|
|
275
278
|
req: req
|
|
276
279
|
});
|
|
277
|
-
case
|
|
278
|
-
_context2.
|
|
279
|
-
_context2.next =
|
|
280
|
+
case 39:
|
|
281
|
+
_context2.t5 = _context2.sent;
|
|
282
|
+
_context2.next = 43;
|
|
280
283
|
break;
|
|
281
|
-
case
|
|
282
|
-
_context2.
|
|
283
|
-
case
|
|
284
|
-
data = _context2.
|
|
284
|
+
case 42:
|
|
285
|
+
_context2.t5 = raw;
|
|
286
|
+
case 43:
|
|
287
|
+
data = _context2.t5;
|
|
285
288
|
return _context2.abrupt("return", TypedNextResponse.json(data, {
|
|
286
289
|
status: 200
|
|
287
290
|
}));
|
|
288
|
-
case
|
|
289
|
-
_context2.prev =
|
|
290
|
-
_context2.
|
|
291
|
-
return _context2.abrupt("return", handleOperationError(_context2.
|
|
292
|
-
case
|
|
291
|
+
case 47:
|
|
292
|
+
_context2.prev = 47;
|
|
293
|
+
_context2.t6 = _context2["catch"](0);
|
|
294
|
+
return _context2.abrupt("return", handleOperationError(_context2.t6, catchHandler));
|
|
295
|
+
case 50:
|
|
293
296
|
case "end":
|
|
294
297
|
return _context2.stop();
|
|
295
298
|
}
|
|
296
|
-
}, _callee2, null, [[0,
|
|
299
|
+
}, _callee2, null, [[0, 47]]);
|
|
297
300
|
}));
|
|
298
301
|
return function (_x2) {
|
|
299
302
|
return _ref8.apply(this, arguments);
|