@web-ts-toolkit/access-router 0.47.1 → 0.48.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/README.md +22 -1
- package/advanced.d.mts +62 -1
- package/advanced.d.ts +62 -1
- package/advanced.js +28 -8
- package/advanced.mjs +28 -8
- package/index.d.mts +2 -2
- package/index.d.ts +2 -2
- package/index.js +283 -100
- package/index.mjs +283 -100
- package/llms.txt +2 -1
- package/package.json +3 -3
- package/{parsers-BidvJUu7.d.mts → parsers-D6iK8khR.d.mts} +131 -1
- package/{parsers-BidvJUu7.d.ts → parsers-D6iK8khR.d.ts} +131 -1
package/llms.txt
CHANGED
|
@@ -171,7 +171,8 @@ Business boundary contracts (see the corresponding README sections):
|
|
|
171
171
|
- `Service.exists`: terminal explicit/base/override false now returns a Forbidden ErrorResult before adapter dispatch in both includeId modes (previously successful false/null). Allowed matches/misses remain true/false, or `{ _id }`/null with includeId. Check success before data.
|
|
172
172
|
- Public `_read`/`_readFilter` tryList never retries Forbidden/BadRequest. Authorized misses retain list fallback and its operation guard. `_upsert` with _id propagates denied exists as Forbidden (previously Unauthorized); allowed miss remains Unauthorized, with no create in either case. Generated direct/root-entry denial status is 403 (root envelope 200). Custom Express routes must map ErrorResult codes to HTTP status themselves; res.json does not do this automatically.
|
|
173
173
|
- Shared-request cross-runtime composition keeps independent state: runtime B never reuses runtime A's permissions or base-filter cache, even with the same `requestPermissionField` name. Same-runtime middleware repeats reuse state without rerunning `globalPermissions`. Pre-populated request permission fields are application-supplied and preserved.
|
|
174
|
-
- Advanced mutation routes persist the final parsed body: nested `data` validators run before the whole-body (`default`) validator, and only final `data`/`select`/`populate`/`tasks` plus allowed `options` (`includePermissions`/`populateAccess`, plus `returningAll` for update/upsert) reach the service. Missing body options fall back to `returning_all`/`include_permissions` query params.
|
|
174
|
+
- Advanced mutation routes persist the final parsed body: nested `data` validators run before the whole-body (`default`) validator, and only final `data`/`select`/`populate`/`tasks` plus allowed `options` (`includePermissions`/`includeFieldPermissions`/`populateAccess`, plus `returningAll` for update/upsert) reach the service. Missing body options fall back to `returning_all`/`include_permissions` query params. Simple GET routes also accept `include_field_permissions` query params.
|
|
175
|
+
- Router permission-output postures (absolute over client input and per-operation `defaults`): `stripPermissionsField: true` removes the whole permissions field from outputs (output matches `includePermissions: false` with the field, including its placeholder, deleted; enforcement still runs); `disableFieldPermissions: true` never computes `_view`/`_edit` maps while `includePermissions` still controls `docPermissions`-hook keys.
|
|
175
176
|
- `package.json` declares `sideEffects` array listing the bundled runtime entries that run the lazy `mongoose-schema-jsonschema` patch on first runtime construction — bundlers must retain those entries
|
|
176
177
|
|
|
177
178
|
## Pointers
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@web-ts-toolkit/access-router",
|
|
3
3
|
"description": "ACL-aware Express routers and in-memory data services for Mongoose-backed APIs",
|
|
4
4
|
"homepage": "https://web-ts-toolkit.pages.dev/docs/packages/access-router",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.48.0",
|
|
6
6
|
"sideEffects": [
|
|
7
7
|
"./**/index.js",
|
|
8
8
|
"./**/index.mjs",
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
"node": ">=22"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@web-ts-toolkit/express-json-router": "0.
|
|
61
|
-
"@web-ts-toolkit/utils": "0.
|
|
60
|
+
"@web-ts-toolkit/express-json-router": "0.48.0",
|
|
61
|
+
"@web-ts-toolkit/utils": "0.48.0",
|
|
62
62
|
"just-diff": "^6.0.2",
|
|
63
63
|
"mongoose-schema-jsonschema": "^4.0.1",
|
|
64
64
|
"sift": "^17.1.3",
|
|
@@ -39,8 +39,28 @@ declare class Service<TModel = unknown> extends Base<TModel> {
|
|
|
39
39
|
}): any;
|
|
40
40
|
protected createModelAdapter(modelName: string): any;
|
|
41
41
|
protected getModelRouterOptions(modelName: string): ModelRouterOptions<TModel>;
|
|
42
|
+
/**
|
|
43
|
+
* Router-level permission output postures (see `ModelRouterOptions`).
|
|
44
|
+
* Absolute: they win over client input and per-operation `defaults`.
|
|
45
|
+
*/
|
|
46
|
+
getPermissionsPosture(): {
|
|
47
|
+
stripPermissionsField: boolean;
|
|
48
|
+
disableFieldPermissions: boolean;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Router posture `stripPermissionsField`: drop the whole document
|
|
52
|
+
* permissions field (including the empty placeholder) from output instead
|
|
53
|
+
* of emitting permission metadata. Returns true when applied.
|
|
54
|
+
*/
|
|
55
|
+
private stripPermissionsOutput;
|
|
42
56
|
private asServiceHookContext;
|
|
43
57
|
private getEffectiveAllowedSortFields;
|
|
58
|
+
/**
|
|
59
|
+
* With `requireExplicitSelect`, an omitted client select behaves as an
|
|
60
|
+
* `_id`-only projection, yielding field-less rows. Explicit selects pass
|
|
61
|
+
* through untouched.
|
|
62
|
+
*/
|
|
63
|
+
protected resolveEffectiveSelect(select: Projection | undefined): Projection | undefined;
|
|
44
64
|
private beginOp;
|
|
45
65
|
private completeOp;
|
|
46
66
|
constructor(req: ModelRequest, modelName: string);
|
|
@@ -54,6 +74,7 @@ declare class Service<TModel = unknown> extends Base<TModel> {
|
|
|
54
74
|
}, options?: {
|
|
55
75
|
skim?: boolean;
|
|
56
76
|
includePermissions?: boolean;
|
|
77
|
+
includeFieldPermissions?: boolean;
|
|
57
78
|
}): Promise<SingleResult<TModel>>;
|
|
58
79
|
/**
|
|
59
80
|
* Update at authorized policy paths: admitted leaves preserve omitted siblings;
|
|
@@ -157,6 +178,7 @@ declare class PublicService<TModel = unknown> extends Service<TModel> {
|
|
|
157
178
|
}, options?: {
|
|
158
179
|
skim?: boolean;
|
|
159
180
|
includePermissions?: boolean;
|
|
181
|
+
includeFieldPermissions?: boolean;
|
|
160
182
|
}): Promise<SingleResult<PublicOutput<TModel>>>;
|
|
161
183
|
_read<TSelect extends Projection | undefined = undefined, TPopulate extends Populate[] | string | undefined = undefined>(id: string, args?: Omit<PublicReadArgs, 'select' | 'populate'> & {
|
|
162
184
|
select?: TSelect;
|
|
@@ -398,22 +420,28 @@ type ListQueryInput = {
|
|
|
398
420
|
page_size?: string;
|
|
399
421
|
skim?: 'true' | 'false';
|
|
400
422
|
include_permissions?: 'true' | 'false';
|
|
423
|
+
include_field_permissions?: 'true' | 'false';
|
|
401
424
|
include_count?: 'true' | 'false';
|
|
402
425
|
include_extra_headers?: 'true' | 'false';
|
|
403
426
|
};
|
|
404
427
|
type CreateQueryInput = {
|
|
405
428
|
include_permissions?: 'true' | 'false';
|
|
429
|
+
include_field_permissions?: 'true' | 'false';
|
|
406
430
|
};
|
|
407
431
|
type ReadQueryInput = {
|
|
408
432
|
include_permissions?: 'true' | 'false';
|
|
433
|
+
include_field_permissions?: 'true' | 'false';
|
|
409
434
|
try_list?: 'true' | 'false';
|
|
410
435
|
};
|
|
411
436
|
type UpdateQueryInput = {
|
|
412
437
|
returning_all?: 'true' | 'false';
|
|
438
|
+
include_permissions?: 'true' | 'false';
|
|
439
|
+
include_field_permissions?: 'true' | 'false';
|
|
413
440
|
};
|
|
414
441
|
type UpsertQueryInput = {
|
|
415
442
|
returning_all?: 'true' | 'false';
|
|
416
443
|
include_permissions?: 'true' | 'false';
|
|
444
|
+
include_field_permissions?: 'true' | 'false';
|
|
417
445
|
};
|
|
418
446
|
type AdvancedListBody = {
|
|
419
447
|
filter?: Filter | unknown[];
|
|
@@ -429,6 +457,7 @@ type AdvancedListBody = {
|
|
|
429
457
|
options?: {
|
|
430
458
|
skim?: boolean;
|
|
431
459
|
includePermissions?: boolean;
|
|
460
|
+
includeFieldPermissions?: boolean;
|
|
432
461
|
includeCount?: boolean;
|
|
433
462
|
includeExtraHeaders?: boolean;
|
|
434
463
|
populateAccess?: unknown;
|
|
@@ -447,6 +476,7 @@ type AdvancedReadFilterBody = {
|
|
|
447
476
|
options?: {
|
|
448
477
|
skim?: boolean;
|
|
449
478
|
includePermissions?: boolean;
|
|
479
|
+
includeFieldPermissions?: boolean;
|
|
450
480
|
tryList?: boolean;
|
|
451
481
|
populateAccess?: unknown;
|
|
452
482
|
};
|
|
@@ -459,6 +489,7 @@ type AdvancedReadBody = {
|
|
|
459
489
|
options?: {
|
|
460
490
|
skim?: boolean;
|
|
461
491
|
includePermissions?: boolean;
|
|
492
|
+
includeFieldPermissions?: boolean;
|
|
462
493
|
tryList?: boolean;
|
|
463
494
|
populateAccess?: unknown;
|
|
464
495
|
};
|
|
@@ -470,6 +501,7 @@ type AdvancedCreateBody = {
|
|
|
470
501
|
tasks?: Task | Task[];
|
|
471
502
|
options?: {
|
|
472
503
|
includePermissions?: boolean;
|
|
504
|
+
includeFieldPermissions?: boolean;
|
|
473
505
|
populateAccess?: unknown;
|
|
474
506
|
};
|
|
475
507
|
};
|
|
@@ -481,6 +513,7 @@ type AdvancedUpdateBody = {
|
|
|
481
513
|
options?: {
|
|
482
514
|
returningAll?: boolean;
|
|
483
515
|
includePermissions?: boolean;
|
|
516
|
+
includeFieldPermissions?: boolean;
|
|
484
517
|
populateAccess?: unknown;
|
|
485
518
|
};
|
|
486
519
|
};
|
|
@@ -492,6 +525,7 @@ type AdvancedUpsertBody = {
|
|
|
492
525
|
options?: {
|
|
493
526
|
returningAll?: boolean;
|
|
494
527
|
includePermissions?: boolean;
|
|
528
|
+
includeFieldPermissions?: boolean;
|
|
495
529
|
populateAccess?: unknown;
|
|
496
530
|
};
|
|
497
531
|
};
|
|
@@ -952,6 +986,15 @@ interface CreateArgs extends Omit<PublicCreateArgs, 'select' | 'tasks'> {
|
|
|
952
986
|
interface PublicCreateOptions {
|
|
953
987
|
skim?: boolean;
|
|
954
988
|
includePermissions?: boolean;
|
|
989
|
+
/**
|
|
990
|
+
* Include field-level `_view`/`_edit` maps. Defaults to `includePermissions`
|
|
991
|
+
* when omitted. `includePermissions: true` + `includeFieldPermissions: false`
|
|
992
|
+
* keeps `docPermissions`-hook keys but omits `_view`/`_edit` entirely.
|
|
993
|
+
* Enabling field maps also computes doc permissions as grant input (even
|
|
994
|
+
* under skim or when `includePermissions` is false), so the maps match the
|
|
995
|
+
* `includePermissions: true` output.
|
|
996
|
+
*/
|
|
997
|
+
includeFieldPermissions?: boolean;
|
|
955
998
|
populateAccess?: PopulateAccess;
|
|
956
999
|
}
|
|
957
1000
|
interface CreateOptions extends PublicCreateOptions {
|
|
@@ -971,6 +1014,15 @@ interface PublicListArgs {
|
|
|
971
1014
|
interface PublicListOptions {
|
|
972
1015
|
skim?: boolean;
|
|
973
1016
|
includePermissions?: boolean;
|
|
1017
|
+
/**
|
|
1018
|
+
* Include field-level `_view`/`_edit` maps. Defaults to `includePermissions`
|
|
1019
|
+
* when omitted. `includePermissions: true` + `includeFieldPermissions: false`
|
|
1020
|
+
* keeps `docPermissions`-hook keys but omits `_view`/`_edit` entirely.
|
|
1021
|
+
* Enabling field maps also computes doc permissions as grant input (even
|
|
1022
|
+
* under skim or when `includePermissions` is false), so the maps match the
|
|
1023
|
+
* `includePermissions: true` output.
|
|
1024
|
+
*/
|
|
1025
|
+
includeFieldPermissions?: boolean;
|
|
974
1026
|
includeCount?: boolean;
|
|
975
1027
|
populateAccess?: PopulateAccess;
|
|
976
1028
|
lean?: boolean;
|
|
@@ -987,6 +1039,15 @@ interface PublicReadOptions {
|
|
|
987
1039
|
populateAccess?: PopulateAccess;
|
|
988
1040
|
lean?: boolean;
|
|
989
1041
|
includePermissions?: boolean;
|
|
1042
|
+
/**
|
|
1043
|
+
* Include field-level `_view`/`_edit` maps. Defaults to `includePermissions`
|
|
1044
|
+
* when omitted. `includePermissions: true` + `includeFieldPermissions: false`
|
|
1045
|
+
* keeps `docPermissions`-hook keys but omits `_view`/`_edit` entirely.
|
|
1046
|
+
* Enabling field maps also computes doc permissions as grant input (even
|
|
1047
|
+
* under skim or when `includePermissions` is false), so the maps match the
|
|
1048
|
+
* `includePermissions: true` output.
|
|
1049
|
+
*/
|
|
1050
|
+
includeFieldPermissions?: boolean;
|
|
990
1051
|
/** Retry an authorized read miss with list access (default true); requires list operation access. Forbidden/BadRequest never retry. */
|
|
991
1052
|
tryList?: boolean;
|
|
992
1053
|
}
|
|
@@ -1018,6 +1079,15 @@ interface PublicUpdateOptions {
|
|
|
1018
1079
|
skim?: boolean;
|
|
1019
1080
|
returningAll?: boolean;
|
|
1020
1081
|
includePermissions?: boolean;
|
|
1082
|
+
/**
|
|
1083
|
+
* Include field-level `_view`/`_edit` maps. Defaults to `includePermissions`
|
|
1084
|
+
* when omitted. `includePermissions: true` + `includeFieldPermissions: false`
|
|
1085
|
+
* keeps `docPermissions`-hook keys but omits `_view`/`_edit` entirely.
|
|
1086
|
+
* Enabling field maps also computes doc permissions as grant input (even
|
|
1087
|
+
* under skim or when `includePermissions` is false), so the maps match the
|
|
1088
|
+
* `includePermissions: true` output.
|
|
1089
|
+
*/
|
|
1090
|
+
includeFieldPermissions?: boolean;
|
|
1021
1091
|
populateAccess?: PopulateAccess;
|
|
1022
1092
|
}
|
|
1023
1093
|
interface PublicUpsertOptions extends PublicUpdateOptions {
|
|
@@ -1302,6 +1372,7 @@ interface RootModelNewQueryEntry extends RootQueryEntryBase<'model', 'new'> {
|
|
|
1302
1372
|
options?: {
|
|
1303
1373
|
skim?: boolean;
|
|
1304
1374
|
includePermissions?: boolean;
|
|
1375
|
+
includeFieldPermissions?: boolean;
|
|
1305
1376
|
};
|
|
1306
1377
|
}
|
|
1307
1378
|
interface RootModelListQueryEntry extends RootQueryEntryBase<'model', 'list'> {
|
|
@@ -1431,7 +1502,7 @@ interface Task {
|
|
|
1431
1502
|
options?: Record<string, unknown>;
|
|
1432
1503
|
}
|
|
1433
1504
|
interface AccessRouterBaseRequest extends AccessRouterRequest {
|
|
1434
|
-
query: Record<'skip' | 'limit' | 'page' | 'page_size' | 'try_list' | 'skim' | 'include_permissions' | 'include_count' | 'include_extra_headers' | 'returning_all', string>;
|
|
1505
|
+
query: Record<'skip' | 'limit' | 'page' | 'page_size' | 'try_list' | 'skim' | 'include_permissions' | 'include_field_permissions' | 'include_count' | 'include_extra_headers' | 'returning_all', string>;
|
|
1435
1506
|
}
|
|
1436
1507
|
interface ModelRequest extends AccessRouterBaseRequest {
|
|
1437
1508
|
macl: Core;
|
|
@@ -1522,6 +1593,15 @@ interface FindArgs<T = unknown> {
|
|
|
1522
1593
|
interface FindOptions {
|
|
1523
1594
|
skim?: boolean;
|
|
1524
1595
|
includePermissions?: boolean;
|
|
1596
|
+
/**
|
|
1597
|
+
* Include field-level `_view`/`_edit` maps. Defaults to `includePermissions`
|
|
1598
|
+
* when omitted. `includePermissions: true` + `includeFieldPermissions: false`
|
|
1599
|
+
* keeps `docPermissions`-hook keys but omits `_view`/`_edit` entirely.
|
|
1600
|
+
* Enabling field maps also computes doc permissions as grant input (even
|
|
1601
|
+
* under skim or when `includePermissions` is false), so the maps match the
|
|
1602
|
+
* `includePermissions: true` output.
|
|
1603
|
+
*/
|
|
1604
|
+
includeFieldPermissions?: boolean;
|
|
1525
1605
|
includeCount?: boolean;
|
|
1526
1606
|
populateAccess?: PopulateAccess;
|
|
1527
1607
|
lean?: boolean;
|
|
@@ -1544,6 +1624,15 @@ interface FindOneOptions {
|
|
|
1544
1624
|
skim?: boolean;
|
|
1545
1625
|
lean?: boolean;
|
|
1546
1626
|
includePermissions?: boolean;
|
|
1627
|
+
/**
|
|
1628
|
+
* Include field-level `_view`/`_edit` maps. Defaults to `includePermissions`
|
|
1629
|
+
* when omitted. `includePermissions: true` + `includeFieldPermissions: false`
|
|
1630
|
+
* keeps `docPermissions`-hook keys but omits `_view`/`_edit` entirely.
|
|
1631
|
+
* Enabling field maps also computes doc permissions as grant input (even
|
|
1632
|
+
* under skim or when `includePermissions` is false), so the maps match the
|
|
1633
|
+
* `includePermissions: true` output.
|
|
1634
|
+
*/
|
|
1635
|
+
includeFieldPermissions?: boolean;
|
|
1547
1636
|
}
|
|
1548
1637
|
interface FindByIdArgs<T = unknown> {
|
|
1549
1638
|
select?: Projection;
|
|
@@ -1716,6 +1805,18 @@ interface DefaultModelRouterOptions<TModel = unknown> {
|
|
|
1716
1805
|
mutationRouteSegment?: string;
|
|
1717
1806
|
operationAccess?: Validation | OperationAccess;
|
|
1718
1807
|
modelPermissionPrefix?: string;
|
|
1808
|
+
/**
|
|
1809
|
+
* When `true`, list/read/update requests without an explicit `select` return
|
|
1810
|
+
* field-less rows (only `_id`, plus the permission field when requested)
|
|
1811
|
+
* instead of all allowed fields. Applies to plain and advanced PATCH
|
|
1812
|
+
* (returned updated record honors `select`, including `?select=` on plain
|
|
1813
|
+
* PATCH and body `select` on advanced PATCH). Only an omitted (`undefined`)
|
|
1814
|
+
* select triggers this; explicit selects — including backend `defaults` and
|
|
1815
|
+
* an explicitly empty string/array (which keep their current meaning) — are
|
|
1816
|
+
* unaffected. Counts, ordering, includes and permission enforcement are
|
|
1817
|
+
* unchanged. Defaults to `false`.
|
|
1818
|
+
*/
|
|
1819
|
+
requireExplicitSelect?: boolean;
|
|
1719
1820
|
/**
|
|
1720
1821
|
* Extra fields allowed in `sort` regardless of `list`/`read` field policy.
|
|
1721
1822
|
* Merged as a union with permission-derived allowed fields (`id`/`_id` are
|
|
@@ -1729,6 +1830,25 @@ interface DefaultModelRouterOptions<TModel = unknown> {
|
|
|
1729
1830
|
* Defaults to `false` (strict: disallowed sort returns `BadRequest`).
|
|
1730
1831
|
*/
|
|
1731
1832
|
stripDisallowedSort?: boolean;
|
|
1833
|
+
/**
|
|
1834
|
+
* When `true`, the document permissions field (`documentPermissionField`,
|
|
1835
|
+
* default `_permissions`) is removed from all outputs — including its empty
|
|
1836
|
+
* placeholder — and client `includePermissions`/`includeFieldPermissions`
|
|
1837
|
+
* input (query params, body/root-batch options and service-direct options)
|
|
1838
|
+
* as well as per-operation `defaults` are ignored (both resolve to `false`).
|
|
1839
|
+
* Output matches `includePermissions: false` with the permissions field
|
|
1840
|
+
* removed entirely. Enforcement still runs; only output metadata is
|
|
1841
|
+
* affected. Defaults to `false`.
|
|
1842
|
+
*/
|
|
1843
|
+
stripPermissionsField?: boolean;
|
|
1844
|
+
/**
|
|
1845
|
+
* When `true`, client `includeFieldPermissions` input (query params,
|
|
1846
|
+
* body/root-batch options and service-direct options) as well as
|
|
1847
|
+
* per-operation `defaults` are ignored and field maps (`_view`/`_edit`)
|
|
1848
|
+
* are never computed. `docPermissions`-hook keys still follow
|
|
1849
|
+
* `includePermissions`. Defaults to `false`.
|
|
1850
|
+
*/
|
|
1851
|
+
disableFieldPermissions?: boolean;
|
|
1732
1852
|
}
|
|
1733
1853
|
interface ExtendedDefaultModelRouterOptions<TModel = unknown> extends DefaultModelRouterOptions<TModel> {
|
|
1734
1854
|
'operationAccess.default'?: Validation;
|
|
@@ -1754,6 +1874,16 @@ interface ModelRouterOptions<TModel = unknown> extends DefaultModelRouterOptions
|
|
|
1754
1874
|
permissionSchema?: PermissionSchema<AccessRouterFieldKey<TModel>>;
|
|
1755
1875
|
alwaysSelectFields?: string[];
|
|
1756
1876
|
docPermissions?: DocPermissions | ModelDocPermissionsHook;
|
|
1877
|
+
/**
|
|
1878
|
+
* Allowlist of `docPermissions`-hook keys exposed in API responses.
|
|
1879
|
+
* Enforcement always uses the full hook map; only serialization is reduced,
|
|
1880
|
+
* after all grant computations and decorate hooks have run. `_view`/`_edit`
|
|
1881
|
+
* are always kept. `undefined` (default) exposes everything (current
|
|
1882
|
+
* behavior); any array (including `[]`) exposes `_view`/`_edit` plus the
|
|
1883
|
+
* listed keys. Note `guard()` conditions on stripped doc-level keys fail
|
|
1884
|
+
* closed — allowlist any key a custom route guard evaluates.
|
|
1885
|
+
*/
|
|
1886
|
+
exposedDocPermissionKeys?: string[];
|
|
1757
1887
|
baseFilter?: ModelBaseFilterHook | Record<string, ModelBaseFilterHook>;
|
|
1758
1888
|
overrideFilter?: ModelOverrideFilterHook | Record<string, ModelOverrideFilterHook>;
|
|
1759
1889
|
decorate?: ModelHook<TModel> | Record<string, ModelHook<TModel>>;
|
|
@@ -39,8 +39,28 @@ declare class Service<TModel = unknown> extends Base<TModel> {
|
|
|
39
39
|
}): any;
|
|
40
40
|
protected createModelAdapter(modelName: string): any;
|
|
41
41
|
protected getModelRouterOptions(modelName: string): ModelRouterOptions<TModel>;
|
|
42
|
+
/**
|
|
43
|
+
* Router-level permission output postures (see `ModelRouterOptions`).
|
|
44
|
+
* Absolute: they win over client input and per-operation `defaults`.
|
|
45
|
+
*/
|
|
46
|
+
getPermissionsPosture(): {
|
|
47
|
+
stripPermissionsField: boolean;
|
|
48
|
+
disableFieldPermissions: boolean;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Router posture `stripPermissionsField`: drop the whole document
|
|
52
|
+
* permissions field (including the empty placeholder) from output instead
|
|
53
|
+
* of emitting permission metadata. Returns true when applied.
|
|
54
|
+
*/
|
|
55
|
+
private stripPermissionsOutput;
|
|
42
56
|
private asServiceHookContext;
|
|
43
57
|
private getEffectiveAllowedSortFields;
|
|
58
|
+
/**
|
|
59
|
+
* With `requireExplicitSelect`, an omitted client select behaves as an
|
|
60
|
+
* `_id`-only projection, yielding field-less rows. Explicit selects pass
|
|
61
|
+
* through untouched.
|
|
62
|
+
*/
|
|
63
|
+
protected resolveEffectiveSelect(select: Projection | undefined): Projection | undefined;
|
|
44
64
|
private beginOp;
|
|
45
65
|
private completeOp;
|
|
46
66
|
constructor(req: ModelRequest, modelName: string);
|
|
@@ -54,6 +74,7 @@ declare class Service<TModel = unknown> extends Base<TModel> {
|
|
|
54
74
|
}, options?: {
|
|
55
75
|
skim?: boolean;
|
|
56
76
|
includePermissions?: boolean;
|
|
77
|
+
includeFieldPermissions?: boolean;
|
|
57
78
|
}): Promise<SingleResult<TModel>>;
|
|
58
79
|
/**
|
|
59
80
|
* Update at authorized policy paths: admitted leaves preserve omitted siblings;
|
|
@@ -157,6 +178,7 @@ declare class PublicService<TModel = unknown> extends Service<TModel> {
|
|
|
157
178
|
}, options?: {
|
|
158
179
|
skim?: boolean;
|
|
159
180
|
includePermissions?: boolean;
|
|
181
|
+
includeFieldPermissions?: boolean;
|
|
160
182
|
}): Promise<SingleResult<PublicOutput<TModel>>>;
|
|
161
183
|
_read<TSelect extends Projection | undefined = undefined, TPopulate extends Populate[] | string | undefined = undefined>(id: string, args?: Omit<PublicReadArgs, 'select' | 'populate'> & {
|
|
162
184
|
select?: TSelect;
|
|
@@ -398,22 +420,28 @@ type ListQueryInput = {
|
|
|
398
420
|
page_size?: string;
|
|
399
421
|
skim?: 'true' | 'false';
|
|
400
422
|
include_permissions?: 'true' | 'false';
|
|
423
|
+
include_field_permissions?: 'true' | 'false';
|
|
401
424
|
include_count?: 'true' | 'false';
|
|
402
425
|
include_extra_headers?: 'true' | 'false';
|
|
403
426
|
};
|
|
404
427
|
type CreateQueryInput = {
|
|
405
428
|
include_permissions?: 'true' | 'false';
|
|
429
|
+
include_field_permissions?: 'true' | 'false';
|
|
406
430
|
};
|
|
407
431
|
type ReadQueryInput = {
|
|
408
432
|
include_permissions?: 'true' | 'false';
|
|
433
|
+
include_field_permissions?: 'true' | 'false';
|
|
409
434
|
try_list?: 'true' | 'false';
|
|
410
435
|
};
|
|
411
436
|
type UpdateQueryInput = {
|
|
412
437
|
returning_all?: 'true' | 'false';
|
|
438
|
+
include_permissions?: 'true' | 'false';
|
|
439
|
+
include_field_permissions?: 'true' | 'false';
|
|
413
440
|
};
|
|
414
441
|
type UpsertQueryInput = {
|
|
415
442
|
returning_all?: 'true' | 'false';
|
|
416
443
|
include_permissions?: 'true' | 'false';
|
|
444
|
+
include_field_permissions?: 'true' | 'false';
|
|
417
445
|
};
|
|
418
446
|
type AdvancedListBody = {
|
|
419
447
|
filter?: Filter | unknown[];
|
|
@@ -429,6 +457,7 @@ type AdvancedListBody = {
|
|
|
429
457
|
options?: {
|
|
430
458
|
skim?: boolean;
|
|
431
459
|
includePermissions?: boolean;
|
|
460
|
+
includeFieldPermissions?: boolean;
|
|
432
461
|
includeCount?: boolean;
|
|
433
462
|
includeExtraHeaders?: boolean;
|
|
434
463
|
populateAccess?: unknown;
|
|
@@ -447,6 +476,7 @@ type AdvancedReadFilterBody = {
|
|
|
447
476
|
options?: {
|
|
448
477
|
skim?: boolean;
|
|
449
478
|
includePermissions?: boolean;
|
|
479
|
+
includeFieldPermissions?: boolean;
|
|
450
480
|
tryList?: boolean;
|
|
451
481
|
populateAccess?: unknown;
|
|
452
482
|
};
|
|
@@ -459,6 +489,7 @@ type AdvancedReadBody = {
|
|
|
459
489
|
options?: {
|
|
460
490
|
skim?: boolean;
|
|
461
491
|
includePermissions?: boolean;
|
|
492
|
+
includeFieldPermissions?: boolean;
|
|
462
493
|
tryList?: boolean;
|
|
463
494
|
populateAccess?: unknown;
|
|
464
495
|
};
|
|
@@ -470,6 +501,7 @@ type AdvancedCreateBody = {
|
|
|
470
501
|
tasks?: Task | Task[];
|
|
471
502
|
options?: {
|
|
472
503
|
includePermissions?: boolean;
|
|
504
|
+
includeFieldPermissions?: boolean;
|
|
473
505
|
populateAccess?: unknown;
|
|
474
506
|
};
|
|
475
507
|
};
|
|
@@ -481,6 +513,7 @@ type AdvancedUpdateBody = {
|
|
|
481
513
|
options?: {
|
|
482
514
|
returningAll?: boolean;
|
|
483
515
|
includePermissions?: boolean;
|
|
516
|
+
includeFieldPermissions?: boolean;
|
|
484
517
|
populateAccess?: unknown;
|
|
485
518
|
};
|
|
486
519
|
};
|
|
@@ -492,6 +525,7 @@ type AdvancedUpsertBody = {
|
|
|
492
525
|
options?: {
|
|
493
526
|
returningAll?: boolean;
|
|
494
527
|
includePermissions?: boolean;
|
|
528
|
+
includeFieldPermissions?: boolean;
|
|
495
529
|
populateAccess?: unknown;
|
|
496
530
|
};
|
|
497
531
|
};
|
|
@@ -952,6 +986,15 @@ interface CreateArgs extends Omit<PublicCreateArgs, 'select' | 'tasks'> {
|
|
|
952
986
|
interface PublicCreateOptions {
|
|
953
987
|
skim?: boolean;
|
|
954
988
|
includePermissions?: boolean;
|
|
989
|
+
/**
|
|
990
|
+
* Include field-level `_view`/`_edit` maps. Defaults to `includePermissions`
|
|
991
|
+
* when omitted. `includePermissions: true` + `includeFieldPermissions: false`
|
|
992
|
+
* keeps `docPermissions`-hook keys but omits `_view`/`_edit` entirely.
|
|
993
|
+
* Enabling field maps also computes doc permissions as grant input (even
|
|
994
|
+
* under skim or when `includePermissions` is false), so the maps match the
|
|
995
|
+
* `includePermissions: true` output.
|
|
996
|
+
*/
|
|
997
|
+
includeFieldPermissions?: boolean;
|
|
955
998
|
populateAccess?: PopulateAccess;
|
|
956
999
|
}
|
|
957
1000
|
interface CreateOptions extends PublicCreateOptions {
|
|
@@ -971,6 +1014,15 @@ interface PublicListArgs {
|
|
|
971
1014
|
interface PublicListOptions {
|
|
972
1015
|
skim?: boolean;
|
|
973
1016
|
includePermissions?: boolean;
|
|
1017
|
+
/**
|
|
1018
|
+
* Include field-level `_view`/`_edit` maps. Defaults to `includePermissions`
|
|
1019
|
+
* when omitted. `includePermissions: true` + `includeFieldPermissions: false`
|
|
1020
|
+
* keeps `docPermissions`-hook keys but omits `_view`/`_edit` entirely.
|
|
1021
|
+
* Enabling field maps also computes doc permissions as grant input (even
|
|
1022
|
+
* under skim or when `includePermissions` is false), so the maps match the
|
|
1023
|
+
* `includePermissions: true` output.
|
|
1024
|
+
*/
|
|
1025
|
+
includeFieldPermissions?: boolean;
|
|
974
1026
|
includeCount?: boolean;
|
|
975
1027
|
populateAccess?: PopulateAccess;
|
|
976
1028
|
lean?: boolean;
|
|
@@ -987,6 +1039,15 @@ interface PublicReadOptions {
|
|
|
987
1039
|
populateAccess?: PopulateAccess;
|
|
988
1040
|
lean?: boolean;
|
|
989
1041
|
includePermissions?: boolean;
|
|
1042
|
+
/**
|
|
1043
|
+
* Include field-level `_view`/`_edit` maps. Defaults to `includePermissions`
|
|
1044
|
+
* when omitted. `includePermissions: true` + `includeFieldPermissions: false`
|
|
1045
|
+
* keeps `docPermissions`-hook keys but omits `_view`/`_edit` entirely.
|
|
1046
|
+
* Enabling field maps also computes doc permissions as grant input (even
|
|
1047
|
+
* under skim or when `includePermissions` is false), so the maps match the
|
|
1048
|
+
* `includePermissions: true` output.
|
|
1049
|
+
*/
|
|
1050
|
+
includeFieldPermissions?: boolean;
|
|
990
1051
|
/** Retry an authorized read miss with list access (default true); requires list operation access. Forbidden/BadRequest never retry. */
|
|
991
1052
|
tryList?: boolean;
|
|
992
1053
|
}
|
|
@@ -1018,6 +1079,15 @@ interface PublicUpdateOptions {
|
|
|
1018
1079
|
skim?: boolean;
|
|
1019
1080
|
returningAll?: boolean;
|
|
1020
1081
|
includePermissions?: boolean;
|
|
1082
|
+
/**
|
|
1083
|
+
* Include field-level `_view`/`_edit` maps. Defaults to `includePermissions`
|
|
1084
|
+
* when omitted. `includePermissions: true` + `includeFieldPermissions: false`
|
|
1085
|
+
* keeps `docPermissions`-hook keys but omits `_view`/`_edit` entirely.
|
|
1086
|
+
* Enabling field maps also computes doc permissions as grant input (even
|
|
1087
|
+
* under skim or when `includePermissions` is false), so the maps match the
|
|
1088
|
+
* `includePermissions: true` output.
|
|
1089
|
+
*/
|
|
1090
|
+
includeFieldPermissions?: boolean;
|
|
1021
1091
|
populateAccess?: PopulateAccess;
|
|
1022
1092
|
}
|
|
1023
1093
|
interface PublicUpsertOptions extends PublicUpdateOptions {
|
|
@@ -1302,6 +1372,7 @@ interface RootModelNewQueryEntry extends RootQueryEntryBase<'model', 'new'> {
|
|
|
1302
1372
|
options?: {
|
|
1303
1373
|
skim?: boolean;
|
|
1304
1374
|
includePermissions?: boolean;
|
|
1375
|
+
includeFieldPermissions?: boolean;
|
|
1305
1376
|
};
|
|
1306
1377
|
}
|
|
1307
1378
|
interface RootModelListQueryEntry extends RootQueryEntryBase<'model', 'list'> {
|
|
@@ -1431,7 +1502,7 @@ interface Task {
|
|
|
1431
1502
|
options?: Record<string, unknown>;
|
|
1432
1503
|
}
|
|
1433
1504
|
interface AccessRouterBaseRequest extends AccessRouterRequest {
|
|
1434
|
-
query: Record<'skip' | 'limit' | 'page' | 'page_size' | 'try_list' | 'skim' | 'include_permissions' | 'include_count' | 'include_extra_headers' | 'returning_all', string>;
|
|
1505
|
+
query: Record<'skip' | 'limit' | 'page' | 'page_size' | 'try_list' | 'skim' | 'include_permissions' | 'include_field_permissions' | 'include_count' | 'include_extra_headers' | 'returning_all', string>;
|
|
1435
1506
|
}
|
|
1436
1507
|
interface ModelRequest extends AccessRouterBaseRequest {
|
|
1437
1508
|
macl: Core;
|
|
@@ -1522,6 +1593,15 @@ interface FindArgs<T = unknown> {
|
|
|
1522
1593
|
interface FindOptions {
|
|
1523
1594
|
skim?: boolean;
|
|
1524
1595
|
includePermissions?: boolean;
|
|
1596
|
+
/**
|
|
1597
|
+
* Include field-level `_view`/`_edit` maps. Defaults to `includePermissions`
|
|
1598
|
+
* when omitted. `includePermissions: true` + `includeFieldPermissions: false`
|
|
1599
|
+
* keeps `docPermissions`-hook keys but omits `_view`/`_edit` entirely.
|
|
1600
|
+
* Enabling field maps also computes doc permissions as grant input (even
|
|
1601
|
+
* under skim or when `includePermissions` is false), so the maps match the
|
|
1602
|
+
* `includePermissions: true` output.
|
|
1603
|
+
*/
|
|
1604
|
+
includeFieldPermissions?: boolean;
|
|
1525
1605
|
includeCount?: boolean;
|
|
1526
1606
|
populateAccess?: PopulateAccess;
|
|
1527
1607
|
lean?: boolean;
|
|
@@ -1544,6 +1624,15 @@ interface FindOneOptions {
|
|
|
1544
1624
|
skim?: boolean;
|
|
1545
1625
|
lean?: boolean;
|
|
1546
1626
|
includePermissions?: boolean;
|
|
1627
|
+
/**
|
|
1628
|
+
* Include field-level `_view`/`_edit` maps. Defaults to `includePermissions`
|
|
1629
|
+
* when omitted. `includePermissions: true` + `includeFieldPermissions: false`
|
|
1630
|
+
* keeps `docPermissions`-hook keys but omits `_view`/`_edit` entirely.
|
|
1631
|
+
* Enabling field maps also computes doc permissions as grant input (even
|
|
1632
|
+
* under skim or when `includePermissions` is false), so the maps match the
|
|
1633
|
+
* `includePermissions: true` output.
|
|
1634
|
+
*/
|
|
1635
|
+
includeFieldPermissions?: boolean;
|
|
1547
1636
|
}
|
|
1548
1637
|
interface FindByIdArgs<T = unknown> {
|
|
1549
1638
|
select?: Projection;
|
|
@@ -1716,6 +1805,18 @@ interface DefaultModelRouterOptions<TModel = unknown> {
|
|
|
1716
1805
|
mutationRouteSegment?: string;
|
|
1717
1806
|
operationAccess?: Validation | OperationAccess;
|
|
1718
1807
|
modelPermissionPrefix?: string;
|
|
1808
|
+
/**
|
|
1809
|
+
* When `true`, list/read/update requests without an explicit `select` return
|
|
1810
|
+
* field-less rows (only `_id`, plus the permission field when requested)
|
|
1811
|
+
* instead of all allowed fields. Applies to plain and advanced PATCH
|
|
1812
|
+
* (returned updated record honors `select`, including `?select=` on plain
|
|
1813
|
+
* PATCH and body `select` on advanced PATCH). Only an omitted (`undefined`)
|
|
1814
|
+
* select triggers this; explicit selects — including backend `defaults` and
|
|
1815
|
+
* an explicitly empty string/array (which keep their current meaning) — are
|
|
1816
|
+
* unaffected. Counts, ordering, includes and permission enforcement are
|
|
1817
|
+
* unchanged. Defaults to `false`.
|
|
1818
|
+
*/
|
|
1819
|
+
requireExplicitSelect?: boolean;
|
|
1719
1820
|
/**
|
|
1720
1821
|
* Extra fields allowed in `sort` regardless of `list`/`read` field policy.
|
|
1721
1822
|
* Merged as a union with permission-derived allowed fields (`id`/`_id` are
|
|
@@ -1729,6 +1830,25 @@ interface DefaultModelRouterOptions<TModel = unknown> {
|
|
|
1729
1830
|
* Defaults to `false` (strict: disallowed sort returns `BadRequest`).
|
|
1730
1831
|
*/
|
|
1731
1832
|
stripDisallowedSort?: boolean;
|
|
1833
|
+
/**
|
|
1834
|
+
* When `true`, the document permissions field (`documentPermissionField`,
|
|
1835
|
+
* default `_permissions`) is removed from all outputs — including its empty
|
|
1836
|
+
* placeholder — and client `includePermissions`/`includeFieldPermissions`
|
|
1837
|
+
* input (query params, body/root-batch options and service-direct options)
|
|
1838
|
+
* as well as per-operation `defaults` are ignored (both resolve to `false`).
|
|
1839
|
+
* Output matches `includePermissions: false` with the permissions field
|
|
1840
|
+
* removed entirely. Enforcement still runs; only output metadata is
|
|
1841
|
+
* affected. Defaults to `false`.
|
|
1842
|
+
*/
|
|
1843
|
+
stripPermissionsField?: boolean;
|
|
1844
|
+
/**
|
|
1845
|
+
* When `true`, client `includeFieldPermissions` input (query params,
|
|
1846
|
+
* body/root-batch options and service-direct options) as well as
|
|
1847
|
+
* per-operation `defaults` are ignored and field maps (`_view`/`_edit`)
|
|
1848
|
+
* are never computed. `docPermissions`-hook keys still follow
|
|
1849
|
+
* `includePermissions`. Defaults to `false`.
|
|
1850
|
+
*/
|
|
1851
|
+
disableFieldPermissions?: boolean;
|
|
1732
1852
|
}
|
|
1733
1853
|
interface ExtendedDefaultModelRouterOptions<TModel = unknown> extends DefaultModelRouterOptions<TModel> {
|
|
1734
1854
|
'operationAccess.default'?: Validation;
|
|
@@ -1754,6 +1874,16 @@ interface ModelRouterOptions<TModel = unknown> extends DefaultModelRouterOptions
|
|
|
1754
1874
|
permissionSchema?: PermissionSchema<AccessRouterFieldKey<TModel>>;
|
|
1755
1875
|
alwaysSelectFields?: string[];
|
|
1756
1876
|
docPermissions?: DocPermissions | ModelDocPermissionsHook;
|
|
1877
|
+
/**
|
|
1878
|
+
* Allowlist of `docPermissions`-hook keys exposed in API responses.
|
|
1879
|
+
* Enforcement always uses the full hook map; only serialization is reduced,
|
|
1880
|
+
* after all grant computations and decorate hooks have run. `_view`/`_edit`
|
|
1881
|
+
* are always kept. `undefined` (default) exposes everything (current
|
|
1882
|
+
* behavior); any array (including `[]`) exposes `_view`/`_edit` plus the
|
|
1883
|
+
* listed keys. Note `guard()` conditions on stripped doc-level keys fail
|
|
1884
|
+
* closed — allowlist any key a custom route guard evaluates.
|
|
1885
|
+
*/
|
|
1886
|
+
exposedDocPermissionKeys?: string[];
|
|
1757
1887
|
baseFilter?: ModelBaseFilterHook | Record<string, ModelBaseFilterHook>;
|
|
1758
1888
|
overrideFilter?: ModelOverrideFilterHook | Record<string, ModelOverrideFilterHook>;
|
|
1759
1889
|
decorate?: ModelHook<TModel> | Record<string, ModelHook<TModel>>;
|