nicot 1.2.4 → 1.2.5
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/README-CN.md +234 -0
- package/README.md +258 -0
- package/dist/index.cjs +294 -22
- package/dist/index.cjs.map +3 -3
- package/dist/index.mjs +291 -22
- package/dist/index.mjs.map +3 -3
- package/dist/src/crud-base.d.ts +31 -0
- package/dist/src/decorators/binding.d.ts +7 -0
- package/dist/src/decorators/index.d.ts +1 -0
- package/dist/src/restful.d.ts +30 -1
- package/dist/src/utility/metadata.d.ts +3 -0
- package/package.json +1 -1
package/README-CN.md
CHANGED
|
@@ -197,6 +197,240 @@ meta: SomeJSONType; // ?meta={"foo":"bar"} → 对象
|
|
|
197
197
|
|
|
198
198
|
在 OpenAPI 里,这些字段仍以 string 展示;在实际运行时,它们已经被转换为你想要的类型。
|
|
199
199
|
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 🔐 Binding Context(数据绑定 / 多租户隔离)
|
|
203
|
+
|
|
204
|
+
在实际的业务系统中,后端经常需要根据“当前用户 / 当前租户 / 当前 App”等上下文,对数据进行自动隔离:
|
|
205
|
+
|
|
206
|
+
- 一个用户只能看到自己的数据
|
|
207
|
+
- 不同 App 的数据不能相互越界
|
|
208
|
+
- 更新 / 删除操作必须自动附带权限条件
|
|
209
|
+
- 不希望每个 Controller/Service 都写重复的 `qb.andWhere(...)`
|
|
210
|
+
|
|
211
|
+
NICOT 提供了 **BindingColumn / BindingValue / useBinding / beforeSuper / RequestScope Provider**,
|
|
212
|
+
让多租户隔离变成 **实体级声明**,和 DTO / Query / Lifecycle 保持一致。
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
### 1. BindingColumn — 声明“这个字段必须被绑定”
|
|
217
|
+
|
|
218
|
+
当某个字段的值应该由后端上下文(而不是前端请求)决定时,应使用 `@BindingColumn`。
|
|
219
|
+
|
|
220
|
+
示例:
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
@Entity()
|
|
224
|
+
class Article extends IdBase() {
|
|
225
|
+
@BindingColumn() // 默认 bindingKey: "default"
|
|
226
|
+
@IntColumn('int')
|
|
227
|
+
userId: number;
|
|
228
|
+
|
|
229
|
+
@BindingColumn('app') // bindingKey: "app"
|
|
230
|
+
@IntColumn('int')
|
|
231
|
+
appId: number;
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
含义:
|
|
236
|
+
|
|
237
|
+
- **Create**:NICOT 会自动写入绑定值,无需前端提供
|
|
238
|
+
- **FindAll**:NICOT 会自动在 WHERE 中加入 userId/appId 条件
|
|
239
|
+
- **Update/Delete**:NICOT 会自动加上绑定条件,防止越权修改
|
|
240
|
+
- 这是“多租户字段”或“业务隔离字段”的最直接声明方式
|
|
241
|
+
|
|
242
|
+
这样做的好处:
|
|
243
|
+
|
|
244
|
+
- 权限隔离逻辑不会散落在 controller/service 里
|
|
245
|
+
- Entity = Contract → 数据隔离是实体的一部分
|
|
246
|
+
- 自动生成的控制器天然具备隔离能力
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
### 2. BindingValue — 绑定值的来源(Service 层)
|
|
251
|
+
|
|
252
|
+
BindingColumn 声明了“需要绑定的字段”,
|
|
253
|
+
BindingValue 声明“绑定值从哪里来”。
|
|
254
|
+
|
|
255
|
+
示例:
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
@Injectable()
|
|
259
|
+
class ArticleService extends CrudService(Article) {
|
|
260
|
+
@BindingValue() // 对应 BindingColumn()
|
|
261
|
+
get currentUserId() {
|
|
262
|
+
return this.ctx.userId;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
@BindingValue('app')
|
|
266
|
+
get currentAppId() {
|
|
267
|
+
return this.ctx.appId;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
BindingValue 可以定义成:
|
|
273
|
+
|
|
274
|
+
- 方法(NICOT 会自动调用)
|
|
275
|
+
- getter 属性
|
|
276
|
+
|
|
277
|
+
它们会在 CRUD pre-phase 被收集成:
|
|
278
|
+
|
|
279
|
+
- create:强制写入字段
|
|
280
|
+
- findAll/update/delete:用于 WHERE 条件
|
|
281
|
+
|
|
282
|
+
优先级高于前端传入值。
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
### 3. useBinding — 本次调用临时覆盖绑定值
|
|
287
|
+
|
|
288
|
+
适合:
|
|
289
|
+
|
|
290
|
+
- 测试
|
|
291
|
+
- CLI 脚本
|
|
292
|
+
- 内部批处理任务
|
|
293
|
+
- 覆盖默认绑定行为
|
|
294
|
+
|
|
295
|
+
示例:
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
service
|
|
299
|
+
.useBinding(7) // 覆盖 bindingKey = default
|
|
300
|
+
.useBinding(44, 'app') // 覆盖 bindingKey = "app"
|
|
301
|
+
.findAll({});
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
特点:
|
|
305
|
+
|
|
306
|
+
- 覆盖值仅对当前一次方法调用有效
|
|
307
|
+
- 不影响同一 service 的其他并发请求
|
|
308
|
+
- 可与 BindingValue 合并
|
|
309
|
+
- 可用于 request-scope provider 不存在时的替代方案
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
### 4. beforeSuper — Override 场景的并发安全机制(高级用法)
|
|
314
|
+
|
|
315
|
+
如果你 override `findAll` / `update` / `delete` 并插入 `await`,
|
|
316
|
+
可能打乱绑定上下文的使用时序(因为 Service 是 singleton)。
|
|
317
|
+
|
|
318
|
+
NICOT 提供 `beforeSuper` 方法,确保绑定上下文在 override 内不会被并发污染:
|
|
319
|
+
|
|
320
|
+
```ts
|
|
321
|
+
override async findAll(...args) {
|
|
322
|
+
await this.beforeSuper(async () => {
|
|
323
|
+
await doSomethingSlow();
|
|
324
|
+
});
|
|
325
|
+
return super.findAll(...args);
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
机制:
|
|
330
|
+
|
|
331
|
+
1. freeze 当前 binding 上下文
|
|
332
|
+
2. 执行 override 的 async 逻辑
|
|
333
|
+
3. restore binding
|
|
334
|
+
4. 再交给 CrudBase 做正式的 CRUD 处理
|
|
335
|
+
|
|
336
|
+
这是一个高级能力,不是普通用户需要接触的 API。
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
### 5. Request-scope Provider(推荐的绑定来源模式)
|
|
341
|
+
|
|
342
|
+
推荐使用 NestJS 的 request-scope provider 自动提供绑定上下文。
|
|
343
|
+
绑定值自然来自当前 HTTP 请求:
|
|
344
|
+
|
|
345
|
+
- userId 来自认证信息
|
|
346
|
+
- appId 来自 header
|
|
347
|
+
- tenantId 来自域名
|
|
348
|
+
- ……
|
|
349
|
+
|
|
350
|
+
#### 5.1 使用 `createProvider` 构造 request-scope binding provider
|
|
351
|
+
|
|
352
|
+
```ts
|
|
353
|
+
export const BindingContextProvider = createProvider(
|
|
354
|
+
{
|
|
355
|
+
provide: 'BindingContext',
|
|
356
|
+
scope: Scope.REQUEST, // ⭐ 每个请求一份独立上下文
|
|
357
|
+
inject: [REQUEST, AuthService] as const,
|
|
358
|
+
},
|
|
359
|
+
async (req, auth) => {
|
|
360
|
+
const user = await auth.getUserFromRequest(req);
|
|
361
|
+
return {
|
|
362
|
+
userId: user.id,
|
|
363
|
+
appId: Number(req.headers['x-app-id']),
|
|
364
|
+
};
|
|
365
|
+
},
|
|
366
|
+
);
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
`createProvider` 会自动推断 `(req, auth)` 的类型。
|
|
370
|
+
|
|
371
|
+
#### 5.2 在 Service 中注入 BindingContext
|
|
372
|
+
|
|
373
|
+
```ts
|
|
374
|
+
@Injectable()
|
|
375
|
+
class ArticleService extends CrudService(Article) {
|
|
376
|
+
constructor(
|
|
377
|
+
@Inject('BindingContext')
|
|
378
|
+
private readonly ctx: { userId: number; appId: number },
|
|
379
|
+
) {
|
|
380
|
+
super(repo);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
@BindingValue()
|
|
384
|
+
get currentUserId() {
|
|
385
|
+
return this.ctx.userId;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
@BindingValue('app')
|
|
389
|
+
get currentAppId() {
|
|
390
|
+
return this.ctx.appId;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
效果:
|
|
396
|
+
|
|
397
|
+
- Service 仍然可以是 singleton
|
|
398
|
+
- BindingValue 一律从 per-request binding context 读取
|
|
399
|
+
- 完全并发安全
|
|
400
|
+
|
|
401
|
+
这是 NICOT 官方推荐的绑定方式。
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
### 6. Binding 工作流程(流程概览)
|
|
406
|
+
|
|
407
|
+
1. 用户调用 Service(可能使用 `useBinding` 覆盖)
|
|
408
|
+
2. CrudBase pre-phase:收集所有 BindingValue
|
|
409
|
+
3. 合并 request-scope provider / useBinding / 默认值
|
|
410
|
+
4. 构造 PartialEntity(绑定字段 → 绑定值)
|
|
411
|
+
5. create:强制写入字段
|
|
412
|
+
6. findAll/update/delete:自动注入 WHERE 条件
|
|
413
|
+
7. 执行实体生命周期钩子
|
|
414
|
+
8. 返回经过 ResultDTO 剪裁的结果
|
|
415
|
+
|
|
416
|
+
Binding 系统与 NICOT 的 CRUD 生命周期保持一致,也可自由组合和继承。
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
### 小结
|
|
421
|
+
|
|
422
|
+
Binding 系统提供了:
|
|
423
|
+
|
|
424
|
+
- `@BindingColumn`:声明需要绑定的字段
|
|
425
|
+
- `@BindingValue`:绑定值的来源
|
|
426
|
+
- `useBinding`:单次调用级覆盖
|
|
427
|
+
- `beforeSuper`:override 时保证并发安全
|
|
428
|
+
- request-scope provider:推荐的绑定上下文提供方式,彻底避免并发污染
|
|
429
|
+
|
|
430
|
+
这套机制让 NICOT 在保持自动化 CRUD 的同时,也能优雅支持多租户隔离、权限隔离与上下文驱动业务逻辑。
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
|
|
200
434
|
---
|
|
201
435
|
|
|
202
436
|
## Relations 与 @RelationComputed
|
package/README.md
CHANGED
|
@@ -550,6 +550,264 @@ Recommended:
|
|
|
550
550
|
|
|
551
551
|
---
|
|
552
552
|
|
|
553
|
+
## Binding Context (Data Binding & Multi-Tenant Isolation)
|
|
554
|
+
|
|
555
|
+
In real systems, you often need to isolate data by *context*:
|
|
556
|
+
|
|
557
|
+
- current user
|
|
558
|
+
- current tenant / app
|
|
559
|
+
- current organization / project
|
|
560
|
+
|
|
561
|
+
Typical rules:
|
|
562
|
+
|
|
563
|
+
- A user can only see their own rows.
|
|
564
|
+
- Updates/deletes must be scoped by ownership.
|
|
565
|
+
- You don’t want to copy-paste `qb.andWhere('userId = :id', ...)` everywhere.
|
|
566
|
+
|
|
567
|
+
NICOT provides **BindingColumn / BindingValue / useBinding / beforeSuper** on top of `CrudBase` so that
|
|
568
|
+
*multi-tenant isolation* becomes part of the **entity contract**, not scattered per-controller logic.
|
|
569
|
+
|
|
570
|
+
---
|
|
571
|
+
|
|
572
|
+
### BindingColumn — declare “this field must be bound”
|
|
573
|
+
|
|
574
|
+
Use `@BindingColumn` on entity fields that should be filled and filtered by the backend context,
|
|
575
|
+
instead of coming from the client payload.
|
|
576
|
+
|
|
577
|
+
```ts
|
|
578
|
+
@Entity()
|
|
579
|
+
export class Article extends IdBase() {
|
|
580
|
+
@BindingColumn() // default bindingKey: "default"
|
|
581
|
+
@IntColumn('int', { unsigned: true })
|
|
582
|
+
userId: number;
|
|
583
|
+
|
|
584
|
+
@BindingColumn('app') // bindingKey: "app"
|
|
585
|
+
@IntColumn('int', { unsigned: true })
|
|
586
|
+
appId: number;
|
|
587
|
+
}
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
NICOT will:
|
|
591
|
+
|
|
592
|
+
- on `create`:
|
|
593
|
+
- write binding values into `userId` / `appId` (if provided)
|
|
594
|
+
- on `findAll`:
|
|
595
|
+
- automatically add `WHERE userId = :value` / `appId = :value`
|
|
596
|
+
- on `update` / `delete`:
|
|
597
|
+
- add the same binding conditions, preventing cross-tenant access
|
|
598
|
+
|
|
599
|
+
Effectively: **binding columns are your “ownership / tenant” fields**.
|
|
600
|
+
|
|
601
|
+
---
|
|
602
|
+
|
|
603
|
+
### BindingValue — where the binding values come from
|
|
604
|
+
|
|
605
|
+
`@BindingValue` is placed on service properties or methods that provide the actual binding values.
|
|
606
|
+
|
|
607
|
+
```ts
|
|
608
|
+
@Injectable()
|
|
609
|
+
class ArticleService extends CrudService(Article) {
|
|
610
|
+
constructor(@InjectRepository(Article) repo: Repository<Article>) {
|
|
611
|
+
super(repo);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
@BindingValue() // for BindingColumn()
|
|
615
|
+
get currentUserId() {
|
|
616
|
+
return this.ctx.userId;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
@BindingValue('app') // for BindingColumn('app')
|
|
620
|
+
get currentAppId() {
|
|
621
|
+
return this.ctx.appId;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
At runtime, NICOT will:
|
|
627
|
+
|
|
628
|
+
- collect all `BindingValue` metadata
|
|
629
|
+
- build a partial entity `{ userId, appId, ... }`
|
|
630
|
+
- use it to:
|
|
631
|
+
- fill fields on `create`
|
|
632
|
+
- add `WHERE` conditions on `findAll`, `update`, `delete`
|
|
633
|
+
|
|
634
|
+
If both client payload and BindingValue provide a value, **BindingValue wins** for binding columns.
|
|
635
|
+
|
|
636
|
+
> You can use:
|
|
637
|
+
> - properties (sync)
|
|
638
|
+
> - getters
|
|
639
|
+
> - methods (sync)
|
|
640
|
+
> - async methods
|
|
641
|
+
> NICOT will await async BindingValues when necessary.
|
|
642
|
+
|
|
643
|
+
---
|
|
644
|
+
|
|
645
|
+
### Request-scoped context provider (recommended)
|
|
646
|
+
|
|
647
|
+
The “canonical” way to provide binding values in a web app is:
|
|
648
|
+
|
|
649
|
+
1. Extract context (user, app, tenant, etc.) from the incoming request.
|
|
650
|
+
2. Put it into a **request-scoped provider**.
|
|
651
|
+
3. Have `@BindingValue` simply read from that provider.
|
|
652
|
+
|
|
653
|
+
This keeps:
|
|
654
|
+
|
|
655
|
+
- context lifetime = request lifetime
|
|
656
|
+
- services as singletons
|
|
657
|
+
- binding logic centralized and testable
|
|
658
|
+
|
|
659
|
+
#### 1) Define a request-scoped binding context
|
|
660
|
+
|
|
661
|
+
Using `createProvider` from **nesties**, you can declare a strongly-typed request-scoped provider:
|
|
662
|
+
|
|
663
|
+
```ts
|
|
664
|
+
export const BindingContextProvider = createProvider(
|
|
665
|
+
{
|
|
666
|
+
provide: 'BindingContext',
|
|
667
|
+
scope: Scope.REQUEST, // ⭐ one instance per HTTP request
|
|
668
|
+
inject: [REQUEST, AuthService] as const,
|
|
669
|
+
},
|
|
670
|
+
async (req, auth) => {
|
|
671
|
+
const user = await auth.getUserFromRequest(req);
|
|
672
|
+
return {
|
|
673
|
+
userId: user.id,
|
|
674
|
+
appId: Number(req.headers['x-app-id']),
|
|
675
|
+
};
|
|
676
|
+
},
|
|
677
|
+
);
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
Key points:
|
|
681
|
+
|
|
682
|
+
- `scope: Scope.REQUEST` → each request has its own context instance.
|
|
683
|
+
- `inject: [REQUEST, AuthService]` → you can pull anything you need to compute bindings.
|
|
684
|
+
- `createProvider` infers `(req, auth)` types automatically.
|
|
685
|
+
|
|
686
|
+
#### 2) Inject the context into your service and expose BindingValues
|
|
687
|
+
|
|
688
|
+
```ts
|
|
689
|
+
@Injectable()
|
|
690
|
+
class ArticleService extends CrudService(Article) {
|
|
691
|
+
constructor(
|
|
692
|
+
@InjectRepository(Article) repo: Repository<Article>,
|
|
693
|
+
@Inject('BindingContext')
|
|
694
|
+
private readonly ctx: { userId: number; appId: number },
|
|
695
|
+
) {
|
|
696
|
+
super(repo);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
@BindingValue()
|
|
700
|
+
get currentUserId() {
|
|
701
|
+
return this.ctx.userId;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
@BindingValue('app')
|
|
705
|
+
get currentAppId() {
|
|
706
|
+
return this.ctx.appId;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
With this setup:
|
|
712
|
+
|
|
713
|
+
- each request gets its own `{ userId, appId }` context
|
|
714
|
+
- `@BindingValue` simply reads from that context
|
|
715
|
+
- `CrudBase` applies bindings for create / findAll / update / delete automatically
|
|
716
|
+
- controllers do **not** need to repeat `userId` conditions
|
|
717
|
+
|
|
718
|
+
This is the **recommended** way to use binding in a NestJS HTTP app.
|
|
719
|
+
|
|
720
|
+
---
|
|
721
|
+
|
|
722
|
+
### useBinding — override binding per call
|
|
723
|
+
|
|
724
|
+
For tests, scripts, or some internal flows, you may want to override binding values *per call*
|
|
725
|
+
instead of relying on `@BindingValue`.
|
|
726
|
+
|
|
727
|
+
Use `useBinding` for this:
|
|
728
|
+
|
|
729
|
+
```ts
|
|
730
|
+
// create with explicit binding
|
|
731
|
+
const res = await articleService
|
|
732
|
+
.useBinding(7) // bindingKey: "default"
|
|
733
|
+
.useBinding(44, 'app') // bindingKey: "app"
|
|
734
|
+
.create({ name: 'Article 1' });
|
|
735
|
+
|
|
736
|
+
// query in the same binding scope
|
|
737
|
+
const list = await articleService
|
|
738
|
+
.useBinding(7)
|
|
739
|
+
.useBinding(44, 'app')
|
|
740
|
+
.findAll({});
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
Key properties:
|
|
744
|
+
|
|
745
|
+
- override is **per call**, not global
|
|
746
|
+
- multiple concurrent calls with different `useBinding` values are isolated
|
|
747
|
+
- merges with `@BindingValue` (explicit `useBinding` can override default BindingValue)
|
|
748
|
+
|
|
749
|
+
This is particularly handy in unit tests and CLI scripts.
|
|
750
|
+
|
|
751
|
+
---
|
|
752
|
+
|
|
753
|
+
### beforeSuper — safe overrides with async logic (advanced)
|
|
754
|
+
|
|
755
|
+
`CrudService` subclasses are singletons, but bindings are *per call*.
|
|
756
|
+
|
|
757
|
+
If you override `findAll` / `update` / `delete` and add `await` **before** calling `super`,
|
|
758
|
+
you can accidentally mess with binding order / concurrency.
|
|
759
|
+
|
|
760
|
+
NICOT offers `beforeSuper` as a small helper:
|
|
761
|
+
|
|
762
|
+
```ts
|
|
763
|
+
@Injectable()
|
|
764
|
+
class SlowArticleService extends ArticleService {
|
|
765
|
+
override async findAll(
|
|
766
|
+
...args: Parameters<typeof ArticleService.prototype.findAll>
|
|
767
|
+
) {
|
|
768
|
+
await this.beforeSuper(async () => {
|
|
769
|
+
// any async work before delegating to CrudBase
|
|
770
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
771
|
+
});
|
|
772
|
+
return super.findAll(...args);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
What `beforeSuper` ensures:
|
|
778
|
+
|
|
779
|
+
1. capture (freeze) current binding state
|
|
780
|
+
2. run your async pre-logic
|
|
781
|
+
3. restore binding state
|
|
782
|
+
4. continue into `CrudBase` with the correct bindings
|
|
783
|
+
|
|
784
|
+
This is an **advanced** hook; most users don’t need it. For typical per-request isolation, prefer request-scoped context + `@BindingValue`.
|
|
785
|
+
|
|
786
|
+
---
|
|
787
|
+
|
|
788
|
+
### How Binding works inside CrudBase
|
|
789
|
+
|
|
790
|
+
On each CRUD operation, NICOT does roughly:
|
|
791
|
+
|
|
792
|
+
1. collect `BindingValue` from the service (properties / getters / methods / async methods)
|
|
793
|
+
2. merge with `useBinding(...)` overlays
|
|
794
|
+
3. build a “binding partial entity”
|
|
795
|
+
4. apply it to:
|
|
796
|
+
- `create`: force binding fields
|
|
797
|
+
- `findAll` / `update` / `delete`: add binding-based `WHERE` conditions
|
|
798
|
+
5. continue with:
|
|
799
|
+
- `beforeGet` / `beforeUpdate` / `beforeCreate`
|
|
800
|
+
- query decorators (`@QueryXXX`)
|
|
801
|
+
- pagination
|
|
802
|
+
- relations
|
|
803
|
+
|
|
804
|
+
You can think of Binding as **“automatic ownership filters”** configured declaratively on:
|
|
805
|
+
|
|
806
|
+
- entities (`@BindingColumn`)
|
|
807
|
+
- services (`@BindingValue`, `useBinding`, `beforeSuper`, request-scoped context)
|
|
808
|
+
|
|
809
|
+
---
|
|
810
|
+
|
|
553
811
|
## Pagination
|
|
554
812
|
|
|
555
813
|
### Offset pagination (default)
|