@web-ts-toolkit/access-router 0.47.1 → 0.49.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 +529 -1
- package/advanced.d.mts +110 -13
- package/advanced.d.ts +110 -13
- package/advanced.js +422 -20
- package/advanced.mjs +424 -21
- package/index.d.mts +134 -45
- package/index.d.ts +134 -45
- package/index.js +8837 -4773
- package/index.mjs +8786 -4712
- package/llms.txt +138 -2
- package/package.json +3 -3
- package/{parsers-BidvJUu7.d.mts → parsers-DhUuvj9B.d.mts} +941 -55
- package/{parsers-BidvJUu7.d.ts → parsers-DhUuvj9B.d.ts} +941 -55
|
@@ -24,9 +24,128 @@ declare class Permission {
|
|
|
24
24
|
type Permissions = Permission;
|
|
25
25
|
type AccessRouterPermissions = Permission;
|
|
26
26
|
|
|
27
|
-
declare class
|
|
27
|
+
declare class RequestConcurrencyScheduler {
|
|
28
|
+
readonly limit: number;
|
|
29
|
+
private activeWork;
|
|
30
|
+
private readonly waiters;
|
|
31
|
+
constructor(limit: number);
|
|
32
|
+
map<TInput, TOutput>(items: TInput[], iteratee: (item: TInput, index: number, scheduled: true) => Promise<TOutput>): Promise<TOutput[]>;
|
|
33
|
+
run<TOutput>(iteratee: (scheduled: true) => Promise<TOutput>): Promise<TOutput>;
|
|
34
|
+
work<TOutput>(operation: () => TOutput | PromiseLike<TOutput>): Promise<TOutput>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Shared hook-orchestration gate for VIRT-03 finalization (VIRT-00A D7).
|
|
38
|
+
*
|
|
39
|
+
* Distinct from {@link RequestConcurrencyScheduler.work} (leaf persistence
|
|
40
|
+
* permits): this gate admits virtual getter bodies only, never persistence
|
|
41
|
+
* ops. A single gate instance is shared across all rows of a top-level list
|
|
42
|
+
* so peak active getter work stays at or below the configured limit instead
|
|
43
|
+
* of multiplying by row count. Callers hold the gate only for the getter body
|
|
44
|
+
* itself — never while awaiting descendant finalization — so limit-1
|
|
45
|
+
* configurations complete without deadlock. FIFO admission keeps completion
|
|
46
|
+
* order independent of input order; the surrounding row map preserves stable
|
|
47
|
+
* index-keyed output association regardless of completion order.
|
|
48
|
+
*/
|
|
49
|
+
declare class SharedHookGate {
|
|
50
|
+
readonly limit: number;
|
|
51
|
+
private active;
|
|
52
|
+
private readonly waiters;
|
|
53
|
+
constructor(limit: number);
|
|
54
|
+
run<TOutput>(operation: () => TOutput | PromiseLike<TOutput>): Promise<TOutput>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Internal effective data policy, including trusted custom accesses; virtual configuration keys stay narrow. */
|
|
58
|
+
type VirtualProjectionAccess = 'list' | 'create' | 'read' | 'update' | (string & {});
|
|
59
|
+
type VirtualCandidateAuthState = 'granted' | 'deferred';
|
|
60
|
+
interface VirtualProjectionSnapshot {
|
|
61
|
+
permissionSchema?: Record<string, unknown> | null;
|
|
62
|
+
virtuals?: Record<string, unknown> | null;
|
|
63
|
+
/** Raw `alwaysSelectFields` option: array or per-access record. */
|
|
64
|
+
alwaysSelectFields?: string[] | Record<string, string[]> | null;
|
|
65
|
+
modelPermissionPrefix?: string;
|
|
66
|
+
requireExplicitSelect?: boolean;
|
|
67
|
+
documentPermissionField?: string;
|
|
68
|
+
}
|
|
69
|
+
interface PlanVirtualCandidate {
|
|
70
|
+
name: string;
|
|
71
|
+
descriptor: {
|
|
72
|
+
get: (...args: never[]) => unknown;
|
|
73
|
+
dependsOn: string[];
|
|
74
|
+
};
|
|
75
|
+
authState: VirtualCandidateAuthState;
|
|
76
|
+
dependsOn: string[];
|
|
77
|
+
virtualAccess: VirtualProjectionAccess;
|
|
78
|
+
outputAccess: VirtualProjectionAccess;
|
|
79
|
+
}
|
|
80
|
+
interface VirtualProjectionPlan {
|
|
81
|
+
receivingModelName: string;
|
|
82
|
+
scopePath: string[];
|
|
83
|
+
virtualAccess: VirtualProjectionAccess;
|
|
84
|
+
outputAccess: VirtualProjectionAccess;
|
|
85
|
+
docPermissionsAccess: VirtualProjectionAccess;
|
|
86
|
+
selectionMode: 'all' | 'include' | 'exclude';
|
|
87
|
+
/** Normalized effective select (empty = omitted/effectively empty). */
|
|
88
|
+
effectiveSelectNorm: string[];
|
|
89
|
+
/** Persisted output eligibility + virtual names eligible for output. */
|
|
90
|
+
outputSelection: string[];
|
|
91
|
+
outputPersistedFields: string[];
|
|
92
|
+
outputVirtualNames: string[];
|
|
93
|
+
/** Safe Mongoose projection: no virtuals, no `.sub` paths, no `-_id`. */
|
|
94
|
+
persistedFetchSelection: string[];
|
|
95
|
+
candidates: PlanVirtualCandidate[];
|
|
96
|
+
virtualOnlyDeps: string[];
|
|
97
|
+
internalOnlyFields: string[];
|
|
98
|
+
depProvenance: Record<string, string[]>;
|
|
99
|
+
outputIdExcluded: boolean;
|
|
100
|
+
fetchIdRetained: boolean;
|
|
101
|
+
internalIdentityFields: string[];
|
|
102
|
+
internalJoinFields: string[];
|
|
103
|
+
childPlans: Record<string, VirtualProjectionPlan>;
|
|
104
|
+
relatedPlans: VirtualProjectionPlan[];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
type VirtualFinalizeAccess = VirtualProjectionAccess;
|
|
108
|
+
interface FinalizeSnapshot extends VirtualProjectionSnapshot {
|
|
109
|
+
documentPermissionField?: string;
|
|
110
|
+
exposedDocPermissionKeys?: string[] | undefined;
|
|
111
|
+
stripPermissionsField?: boolean;
|
|
112
|
+
disableFieldPermissions?: boolean;
|
|
113
|
+
}
|
|
114
|
+
interface FinalizeRelatedTarget {
|
|
115
|
+
/** Output path in the parent scope (top-level name for v1; dotted supported). */
|
|
116
|
+
path: string;
|
|
117
|
+
plan: VirtualProjectionPlan;
|
|
118
|
+
snapshot: FinalizeSnapshot;
|
|
119
|
+
virtualAccess: VirtualFinalizeAccess;
|
|
120
|
+
outputAccess: VirtualFinalizeAccess;
|
|
121
|
+
docPermissionsAccess: VirtualFinalizeAccess;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* VIRT-04 direct-operation virtual wiring (VIRT-00A D1/D6/D7/D8).
|
|
126
|
+
*
|
|
127
|
+
* - Access matrix: list `list/list/list`; read/readFilter `read/read/read`
|
|
128
|
+
* (read using list fallback becomes `list/list/list` with
|
|
129
|
+
* `operation: 'read'` preserved); create/create-branch `create/read/create`;
|
|
130
|
+
* update/update-branch `update/read/update`; new template
|
|
131
|
+
* `create/create/create` (see `new()` note on ignored `args.select`).
|
|
132
|
+
* - Effective mutation output selection is carried on the internal plan
|
|
133
|
+
* into the shared finalizer before evaluation (explicit select,
|
|
134
|
+
* `returningAll: false` implicits, decorator/task selection preserved
|
|
135
|
+
* via existing public picks after decorate/tasks). Internal create/update
|
|
136
|
+
* args omit `select`; `overrides.effectiveSelect` is the deliberate
|
|
137
|
+
* transport (see `interfaces/service-create.ts`, `service-update.ts`).
|
|
138
|
+
* - Output-only admission runs before validation/persistence; computed
|
|
139
|
+
* values are assigned only to output copies (finalizer), never to
|
|
140
|
+
* persisted docs/snapshots.
|
|
141
|
+
* - `maxHookConcurrency` bounds active getter + row-finalization work via
|
|
142
|
+
* ONE bounded row map + shared `SharedHookGate` (stable order, limit-1
|
|
143
|
+
* completes, no persistence permits held).
|
|
144
|
+
*/
|
|
145
|
+
type VirtualOperationAccess = 'list' | 'create' | 'read' | 'update';
|
|
146
|
+
declare class Service<TModel = unknown, TVirtuals extends object = Record<never, never>> extends Base<TModel, TVirtuals> {
|
|
28
147
|
protected model: any;
|
|
29
|
-
protected options: ModelRouterOptions<TModel>;
|
|
148
|
+
protected options: ModelRouterOptions<TModel, TVirtuals>;
|
|
30
149
|
defaults: Defaults<TModel>;
|
|
31
150
|
protected baseFields: string[];
|
|
32
151
|
protected baseFieldsExt: string[];
|
|
@@ -38,9 +157,187 @@ declare class Service<TModel = unknown> extends Base<TModel> {
|
|
|
38
157
|
lean: boolean;
|
|
39
158
|
}): any;
|
|
40
159
|
protected createModelAdapter(modelName: string): any;
|
|
41
|
-
protected getModelRouterOptions(modelName: string): ModelRouterOptions<TModel>;
|
|
160
|
+
protected getModelRouterOptions(modelName: string): ModelRouterOptions<TModel, TVirtuals>;
|
|
161
|
+
/**
|
|
162
|
+
* Router-level permission output postures (see `ModelRouterOptions`).
|
|
163
|
+
* Absolute: they win over client input and per-operation `defaults`.
|
|
164
|
+
*/
|
|
165
|
+
getPermissionsPosture(): {
|
|
166
|
+
stripPermissionsField: boolean;
|
|
167
|
+
disableFieldPermissions: boolean;
|
|
168
|
+
};
|
|
169
|
+
/**
|
|
170
|
+
* Router posture `stripPermissionsField`: drop the whole document
|
|
171
|
+
* permissions field (including the empty placeholder) from output instead
|
|
172
|
+
* of emitting permission metadata. Returns true when applied.
|
|
173
|
+
*/
|
|
174
|
+
private stripPermissionsOutput;
|
|
42
175
|
private asServiceHookContext;
|
|
43
176
|
private getEffectiveAllowedSortFields;
|
|
177
|
+
/**
|
|
178
|
+
* With `requireExplicitSelect`, an omitted client select behaves as an
|
|
179
|
+
* `_id`-only projection, yielding field-less rows. Explicit selects pass
|
|
180
|
+
* through untouched.
|
|
181
|
+
*/
|
|
182
|
+
protected resolveEffectiveSelect(select: Projection | undefined): Projection | undefined;
|
|
183
|
+
/**
|
|
184
|
+
* Capture a coherent virtual snapshot for an operation (VIRT-00A D1/D2,
|
|
185
|
+
* VIRT-01 copy-on-write). Planner and finalizer use this same object for
|
|
186
|
+
* the operation so in-flight descriptor replacement cannot mix versions.
|
|
187
|
+
* Public for VIRT-06 subdocument routes (same coherence per sub op).
|
|
188
|
+
*/
|
|
189
|
+
captureVirtualSnapshot(): FinalizeSnapshot;
|
|
190
|
+
getVirtualGlobalPermissions(): {
|
|
191
|
+
has: (key: string) => boolean;
|
|
192
|
+
hasKey: (key: string) => boolean;
|
|
193
|
+
};
|
|
194
|
+
getVirtualHookConcurrencyLimit(): number;
|
|
195
|
+
buildVirtualPlan(args: {
|
|
196
|
+
snapshot: FinalizeSnapshot;
|
|
197
|
+
virtualAccess: VirtualOperationAccess;
|
|
198
|
+
outputAccess: VirtualOperationAccess;
|
|
199
|
+
docPermissionsAccess: VirtualOperationAccess;
|
|
200
|
+
requestedSelect?: Projection;
|
|
201
|
+
effectiveSelect?: Projection;
|
|
202
|
+
internalFetch?: {
|
|
203
|
+
baseFields?: string[];
|
|
204
|
+
joinFields?: string[];
|
|
205
|
+
identityFields?: string[];
|
|
206
|
+
extraPersistedFields?: string[];
|
|
207
|
+
trustedOverrideSelect?: Projection;
|
|
208
|
+
};
|
|
209
|
+
}): VirtualProjectionPlan;
|
|
210
|
+
/**
|
|
211
|
+
* VIRT-06 sub-scope planner.
|
|
212
|
+
*
|
|
213
|
+
* Builds a `planVirtualProjection` with `scopePath=[sub,'sub']` for
|
|
214
|
+
* dedicated subdocument routes (`virtuals.<field>.sub.<name>.<access>`).
|
|
215
|
+
* `dependsOn` resolves relative to that scope (VIRT-00A D5); literal
|
|
216
|
+
* `.sub` segments never become DB/response names (planner strips them).
|
|
217
|
+
* Uses the same coherent `snapshot` captured once per sub op.
|
|
218
|
+
*/
|
|
219
|
+
buildSubVirtualPlan(sub: string, snapshot: FinalizeSnapshot, virtualAccess: VirtualOperationAccess, outputAccess: VirtualOperationAccess, docPermissionsAccess: VirtualOperationAccess, requestedSelect?: Projection, effectiveSelect?: Projection): VirtualProjectionPlan;
|
|
220
|
+
/**
|
|
221
|
+
* VIRT-06 internal accessors for dedicated subdocument routes.
|
|
222
|
+
* `model-subdocument-service` uses these instead of reaching into
|
|
223
|
+
* protected `req`/`modelName`/`model`, preserving raw docs for
|
|
224
|
+
* persistence (never substituting trimmed DTOs) and keeping request
|
|
225
|
+
* ownership inside `Service`.
|
|
226
|
+
*/
|
|
227
|
+
getServiceRequest(): ModelRequest;
|
|
228
|
+
getServiceModelName(): string;
|
|
229
|
+
getServiceMongooseModel(): unknown;
|
|
230
|
+
/**
|
|
231
|
+
* VIRT-06 owning-parent internal grants (VIRT-00A D2).
|
|
232
|
+
*
|
|
233
|
+
* Dedicated subdocument routes reuse the owning parent's internally
|
|
234
|
+
* computed `read` grants with scoped `permissionSchema.<field>.sub` rules;
|
|
235
|
+
* v1 adds NO independent embedded `docPermissions` hook family. `operation`
|
|
236
|
+
* stays the initiating sub op (`subList`/`subRead`/`subCreate`/`subUpdate`/
|
|
237
|
+
* `subBulkUpdate`) for logs, while `docPermissionsAccess` is `read`.
|
|
238
|
+
* Never throws: hook failure yields `{}` (fail-closed downstream).
|
|
239
|
+
*/
|
|
240
|
+
getSubParentGrants(parentDoc: unknown, operation: string): Promise<Record<string, unknown>>;
|
|
241
|
+
/**
|
|
242
|
+
* VIRT-06 bounded sub-row finalization (VIRT-00A D7, VIRT-03 strategy).
|
|
243
|
+
*
|
|
244
|
+
* Finalizes already-filtered (visible-only) sub rows through the shared
|
|
245
|
+
* boundary with scope ` [sub,'sub']`, owning-parent `read` grants, and
|
|
246
|
+
* scoped rules. Children (deeper nested + related via `related`) finalize
|
|
247
|
+
* before sub getters; only finalized children appear in getter views.
|
|
248
|
+
* Uses ONE bounded row map + shared `SharedHookGate` (stable order,
|
|
249
|
+
* peak ≤ `maxHookConcurrency`, limit-1 completes, no persistence permits
|
|
250
|
+
* held). Callers must filter rows (existing row/operation filters) BEFORE
|
|
251
|
+
* calling; hidden rows never reach getters. Never mutates inputs.
|
|
252
|
+
*/
|
|
253
|
+
finalizeSubRowsWithVirtuals(args: {
|
|
254
|
+
sub: string;
|
|
255
|
+
rows: unknown[];
|
|
256
|
+
plan: VirtualProjectionPlan;
|
|
257
|
+
snapshot: FinalizeSnapshot;
|
|
258
|
+
parentGrants: Record<string, unknown>;
|
|
259
|
+
virtualAccess: VirtualOperationAccess;
|
|
260
|
+
outputAccess: VirtualOperationAccess;
|
|
261
|
+
docPermissionsAccess: VirtualOperationAccess;
|
|
262
|
+
operation: string;
|
|
263
|
+
related?: FinalizeRelatedTarget[];
|
|
264
|
+
}): Promise<Record<string, unknown>[]>;
|
|
265
|
+
/**
|
|
266
|
+
* VIRT-06 single sub-row finalization (same boundary/bounds as list).
|
|
267
|
+
* Returns `null` when input is nullish (caller maps to `null` for
|
|
268
|
+
* denied-post-persist single visibility, never a retryable error).
|
|
269
|
+
*/
|
|
270
|
+
finalizeSingleSubRowWithVirtuals(args: {
|
|
271
|
+
sub: string;
|
|
272
|
+
row: unknown;
|
|
273
|
+
plan: VirtualProjectionPlan;
|
|
274
|
+
snapshot: FinalizeSnapshot;
|
|
275
|
+
parentGrants: Record<string, unknown>;
|
|
276
|
+
virtualAccess: VirtualOperationAccess;
|
|
277
|
+
outputAccess: VirtualOperationAccess;
|
|
278
|
+
docPermissionsAccess: VirtualOperationAccess;
|
|
279
|
+
operation: string;
|
|
280
|
+
related?: FinalizeRelatedTarget[];
|
|
281
|
+
}): Promise<Record<string, unknown> | null>;
|
|
282
|
+
/**
|
|
283
|
+
* VIRT-06 populate finalization for sub parents (VIRT-05 path).
|
|
284
|
+
*
|
|
285
|
+
* After Mongoose `populate` resolves on the raw parent (e.g.
|
|
286
|
+
* `contacts.friend` via `genSubPopulate`), finalize each actual target
|
|
287
|
+
* with its own plan/context before sub getters. Handles null/scalar/array
|
|
288
|
+
* + dotted through embedded arrays; trims even without virtuals. Bounded
|
|
289
|
+
* via shared gate; limit-1 completes. No-op when no entries.
|
|
290
|
+
*/
|
|
291
|
+
finalizePopulateForSubParent(parentDoc: unknown, populateEntries: Populate[], parentOperation: string, hookLimit?: number, sharedGate?: SharedHookGate): Promise<void>;
|
|
292
|
+
/**
|
|
293
|
+
* Output-only write admission (VIRT-04 requirement 4). Excludes registered
|
|
294
|
+
* virtual names for any access (including bare `true` rules and inactive
|
|
295
|
+
* descriptors) and sanitizes registered embedded virtual keys inside
|
|
296
|
+
* whole-object/array grants. Trusted prepare/transform authority is
|
|
297
|
+
* preserved (this runs on client data before validation only); computed
|
|
298
|
+
* values are assigned only to output copies by the finalizer.
|
|
299
|
+
*/
|
|
300
|
+
private admitWriteDataForVirtuals;
|
|
301
|
+
private filterAllowedFieldsForVirtuals;
|
|
302
|
+
private stripVirtualsFromFilterForSnapshot;
|
|
303
|
+
/**
|
|
304
|
+
* Preserve the `permissions` Mongoose virtual (from `permissionsPlugin`) for
|
|
305
|
+
* hydrated outputs. `isolateDocForOutput` uses `toObject({ virtuals: false })`
|
|
306
|
+
* so Mongoose getter virtuals never leak into package-virtual planning;
|
|
307
|
+
* `lean: true` therefore drops them (explicit parity rule). For `lean: false`
|
|
308
|
+
* (hydrated Mongoose documents) the historical contract exposes
|
|
309
|
+
* `doc.permissions` as an alias of the document-permission field. The shared
|
|
310
|
+
* finalizer keeps only the document-permission field, so restore the alias
|
|
311
|
+
* here for backward compatibility when the input was hydrated and the
|
|
312
|
+
* virtual existed. Lean outputs stay without the virtual. Models without
|
|
313
|
+
* the plugin never had the virtual and stay unchanged.
|
|
314
|
+
*/
|
|
315
|
+
private preservePermissionsVirtual;
|
|
316
|
+
/**
|
|
317
|
+
* VIRT-05 populate + association helpers.
|
|
318
|
+
*
|
|
319
|
+
* - Populate targets finalize with their own plan/context before parent
|
|
320
|
+
* getters (target trimming even without virtuals). Handles null, scalar
|
|
321
|
+
* IDs when population is skipped, arrays, and dotted paths through
|
|
322
|
+
* embedded arrays (e.g. `contacts.friend`). One evaluation per logical
|
|
323
|
+
* target output, no in-place mutation of shared/cached instances (targets
|
|
324
|
+
* are replaced, never mutated; parents are fresh per-query docs, consistent
|
|
325
|
+
* with `includeDocs` in-place attachment), isolated copies per parent,
|
|
326
|
+
* bounded via the shared `SharedHookGate` + `mapWithConcurrencyLimit`
|
|
327
|
+
* (no unbounded fan-out, limit-1 completes, no persistence permits held).
|
|
328
|
+
* - Association transport stays service-only (symbols, outside serializers
|
|
329
|
+
* and finalized output input): `find` fetches `foreignField` fetch-only,
|
|
330
|
+
* captures immutable values before trim, and returns them aligned to rows.
|
|
331
|
+
*/
|
|
332
|
+
private getAssociationFieldFromOptions;
|
|
333
|
+
private buildDbSelectForAssociation;
|
|
334
|
+
private captureAssociationValues;
|
|
335
|
+
private captureTargetSnapshotForPopulate;
|
|
336
|
+
private resolvePopulateMeta;
|
|
337
|
+
private isScalarPopulateValue;
|
|
338
|
+
private finalizeSinglePopulateTarget;
|
|
339
|
+
private finalizePopulatePath;
|
|
340
|
+
private finalizePopulateForSingleDoc;
|
|
44
341
|
private beginOp;
|
|
45
342
|
private completeOp;
|
|
46
343
|
constructor(req: ModelRequest, modelName: string);
|
|
@@ -54,6 +351,7 @@ declare class Service<TModel = unknown> extends Base<TModel> {
|
|
|
54
351
|
}, options?: {
|
|
55
352
|
skim?: boolean;
|
|
56
353
|
includePermissions?: boolean;
|
|
354
|
+
includeFieldPermissions?: boolean;
|
|
57
355
|
}): Promise<SingleResult<TModel>>;
|
|
58
356
|
/**
|
|
59
357
|
* Update at authorized policy paths: admitted leaves preserve omitted siblings;
|
|
@@ -143,43 +441,52 @@ declare class Service<TModel = unknown> extends Base<TModel> {
|
|
|
143
441
|
getParentDoc(id: SubdocumentId, sub: SubdocumentName, args?: SubdocumentParentArgs, options?: SubdocumentParentOptions): Promise<any>;
|
|
144
442
|
}
|
|
145
443
|
|
|
146
|
-
declare class PublicService<TModel = unknown
|
|
444
|
+
declare class PublicService<TModel = unknown, TVirtuals extends object = Record<never, never>> extends Service<TModel, TVirtuals> {
|
|
147
445
|
_list<TSelect extends Projection | undefined = undefined, TPopulate extends Populate[] | string | undefined = undefined>(filter: Filter<TModel>, args?: Omit<PublicListArgs, 'select' | 'populate'> & {
|
|
148
446
|
select?: TSelect;
|
|
149
447
|
populate?: TPopulate;
|
|
150
|
-
}, options?: PublicListOptions): Promise<ListResult<SelectedPopulatedPublicOutput<TModel, TSelect, TPopulate>> | ErrorResult>;
|
|
448
|
+
}, options?: PublicListOptions): Promise<ListResult<SelectedPopulatedPublicOutput<TModel, TSelect, TPopulate, TVirtuals>> | ErrorResult>;
|
|
151
449
|
_create<TSelect extends Projection | undefined = undefined, TPopulate extends Populate[] | string | undefined = undefined>(data: unknown, args?: Omit<PublicCreateArgs, 'select' | 'populate'> & {
|
|
152
450
|
select?: TSelect;
|
|
153
451
|
populate?: TPopulate;
|
|
154
|
-
}, options?: PublicCreateOptions): Promise<ListResult<SelectedPopulatedPublicOutput<TModel, TSelect, TPopulate>> | ErrorResult>;
|
|
452
|
+
}, options?: PublicCreateOptions): Promise<ListResult<SelectedPopulatedPublicOutput<TModel, TSelect, TPopulate, TVirtuals>> | ErrorResult>;
|
|
155
453
|
_new(args?: {
|
|
156
454
|
select?: string[];
|
|
157
455
|
}, options?: {
|
|
158
456
|
skim?: boolean;
|
|
159
457
|
includePermissions?: boolean;
|
|
160
|
-
|
|
458
|
+
includeFieldPermissions?: boolean;
|
|
459
|
+
}): Promise<SingleResult<PublicOutput<TModel, TVirtuals>>>;
|
|
161
460
|
_read<TSelect extends Projection | undefined = undefined, TPopulate extends Populate[] | string | undefined = undefined>(id: string, args?: Omit<PublicReadArgs, 'select' | 'populate'> & {
|
|
162
461
|
select?: TSelect;
|
|
163
462
|
populate?: TPopulate;
|
|
164
|
-
}, options?: PublicReadOptions): Promise<SingleResult<SelectedPopulatedPublicOutput<TModel, TSelect, TPopulate>> | ErrorResult>;
|
|
463
|
+
}, options?: PublicReadOptions): Promise<SingleResult<SelectedPopulatedPublicOutput<TModel, TSelect, TPopulate, TVirtuals>> | ErrorResult>;
|
|
165
464
|
_readFilter<TSelect extends Projection | undefined = undefined, TPopulate extends Populate[] | string | undefined = undefined>(filter: Filter<TModel>, args?: Omit<PublicReadArgs, 'select' | 'populate'> & {
|
|
166
465
|
select?: TSelect;
|
|
167
466
|
populate?: TPopulate;
|
|
168
467
|
sort?: Sort;
|
|
169
|
-
}, options?: PublicReadOptions): Promise<SingleResult<SelectedPopulatedPublicOutput<TModel, TSelect, TPopulate>> | ErrorResult>;
|
|
468
|
+
}, options?: PublicReadOptions): Promise<SingleResult<SelectedPopulatedPublicOutput<TModel, TSelect, TPopulate, TVirtuals>> | ErrorResult>;
|
|
170
469
|
_update<TSelect extends Projection | undefined = undefined, TPopulate extends Populate[] | string | undefined = undefined>(id: string, data: Record<string, unknown>, args?: Omit<PublicUpdateArgs, 'select' | 'populate'> & {
|
|
171
470
|
select?: TSelect;
|
|
172
471
|
populate?: TPopulate;
|
|
173
|
-
}, options?: PublicUpdateOptions): Promise<SingleResult<SelectedPopulatedPublicOutput<TModel, TSelect, TPopulate>> | ErrorResult>;
|
|
472
|
+
}, options?: PublicUpdateOptions): Promise<SingleResult<SelectedPopulatedPublicOutput<TModel, TSelect, TPopulate, TVirtuals>> | ErrorResult>;
|
|
174
473
|
/**
|
|
175
474
|
* With an _id, check update-policy existence then update: terminal denial returns
|
|
176
475
|
* Forbidden, an allowed miss remains Unauthorized, and neither creates a row.
|
|
177
476
|
* Without an _id, use the create path.
|
|
477
|
+
*
|
|
478
|
+
* VIRT-04 D1/D6: both branches keep `operation: 'upsert'` while accesses
|
|
479
|
+
* follow the taken branch (update/read/update vs create/read/create).
|
|
480
|
+
* Effective selection is carried via internal `overrides.effectiveSelect`
|
|
481
|
+
* before evaluation; presentation picks after decorate/tasks are preserved.
|
|
482
|
+
* Internal `updateById`/`create` are called directly (not via `_update`/
|
|
483
|
+
* `_create`) so the initiating operation is not overwritten with the
|
|
484
|
+
* branch name.
|
|
178
485
|
*/
|
|
179
486
|
_upsert<TSelect extends Projection | undefined = undefined, TPopulate extends Populate[] | string | undefined = undefined>(data: Record<string, unknown>, args?: Omit<PublicUpsertArgs, 'select' | 'populate'> & {
|
|
180
487
|
select?: TSelect;
|
|
181
488
|
populate?: TPopulate;
|
|
182
|
-
}, options?: PublicUpsertOptions): Promise<ServiceResult<SelectedPopulatedPublicOutput<TModel, TSelect, TPopulate>> | ErrorResult>;
|
|
489
|
+
}, options?: PublicUpsertOptions): Promise<ServiceResult<SelectedPopulatedPublicOutput<TModel, TSelect, TPopulate, TVirtuals>> | ErrorResult>;
|
|
183
490
|
_delete(id: string): Promise<SingleResult<unknown> | ErrorResult>;
|
|
184
491
|
_distinct(field: string, options?: DistinctArgs<TModel>): Promise<ListResult<unknown> | ErrorResult>;
|
|
185
492
|
_count(filter: Filter<TModel>, access?: BaseFilterAccess): Promise<SingleResult<number> | ErrorResult>;
|
|
@@ -398,22 +705,28 @@ type ListQueryInput = {
|
|
|
398
705
|
page_size?: string;
|
|
399
706
|
skim?: 'true' | 'false';
|
|
400
707
|
include_permissions?: 'true' | 'false';
|
|
708
|
+
include_field_permissions?: 'true' | 'false';
|
|
401
709
|
include_count?: 'true' | 'false';
|
|
402
710
|
include_extra_headers?: 'true' | 'false';
|
|
403
711
|
};
|
|
404
712
|
type CreateQueryInput = {
|
|
405
713
|
include_permissions?: 'true' | 'false';
|
|
714
|
+
include_field_permissions?: 'true' | 'false';
|
|
406
715
|
};
|
|
407
716
|
type ReadQueryInput = {
|
|
408
717
|
include_permissions?: 'true' | 'false';
|
|
718
|
+
include_field_permissions?: 'true' | 'false';
|
|
409
719
|
try_list?: 'true' | 'false';
|
|
410
720
|
};
|
|
411
721
|
type UpdateQueryInput = {
|
|
412
722
|
returning_all?: 'true' | 'false';
|
|
723
|
+
include_permissions?: 'true' | 'false';
|
|
724
|
+
include_field_permissions?: 'true' | 'false';
|
|
413
725
|
};
|
|
414
726
|
type UpsertQueryInput = {
|
|
415
727
|
returning_all?: 'true' | 'false';
|
|
416
728
|
include_permissions?: 'true' | 'false';
|
|
729
|
+
include_field_permissions?: 'true' | 'false';
|
|
417
730
|
};
|
|
418
731
|
type AdvancedListBody = {
|
|
419
732
|
filter?: Filter | unknown[];
|
|
@@ -429,9 +742,10 @@ type AdvancedListBody = {
|
|
|
429
742
|
options?: {
|
|
430
743
|
skim?: boolean;
|
|
431
744
|
includePermissions?: boolean;
|
|
745
|
+
includeFieldPermissions?: boolean;
|
|
432
746
|
includeCount?: boolean;
|
|
433
747
|
includeExtraHeaders?: boolean;
|
|
434
|
-
populateAccess?:
|
|
748
|
+
populateAccess?: PopulateAccess;
|
|
435
749
|
};
|
|
436
750
|
};
|
|
437
751
|
type CountBody = {
|
|
@@ -447,8 +761,9 @@ type AdvancedReadFilterBody = {
|
|
|
447
761
|
options?: {
|
|
448
762
|
skim?: boolean;
|
|
449
763
|
includePermissions?: boolean;
|
|
764
|
+
includeFieldPermissions?: boolean;
|
|
450
765
|
tryList?: boolean;
|
|
451
|
-
populateAccess?:
|
|
766
|
+
populateAccess?: PopulateAccess;
|
|
452
767
|
};
|
|
453
768
|
};
|
|
454
769
|
type AdvancedReadBody = {
|
|
@@ -459,8 +774,9 @@ type AdvancedReadBody = {
|
|
|
459
774
|
options?: {
|
|
460
775
|
skim?: boolean;
|
|
461
776
|
includePermissions?: boolean;
|
|
777
|
+
includeFieldPermissions?: boolean;
|
|
462
778
|
tryList?: boolean;
|
|
463
|
-
populateAccess?:
|
|
779
|
+
populateAccess?: PopulateAccess;
|
|
464
780
|
};
|
|
465
781
|
};
|
|
466
782
|
type AdvancedCreateBody = {
|
|
@@ -470,7 +786,8 @@ type AdvancedCreateBody = {
|
|
|
470
786
|
tasks?: Task | Task[];
|
|
471
787
|
options?: {
|
|
472
788
|
includePermissions?: boolean;
|
|
473
|
-
|
|
789
|
+
includeFieldPermissions?: boolean;
|
|
790
|
+
populateAccess?: PopulateAccess;
|
|
474
791
|
};
|
|
475
792
|
};
|
|
476
793
|
type AdvancedUpdateBody = {
|
|
@@ -481,7 +798,8 @@ type AdvancedUpdateBody = {
|
|
|
481
798
|
options?: {
|
|
482
799
|
returningAll?: boolean;
|
|
483
800
|
includePermissions?: boolean;
|
|
484
|
-
|
|
801
|
+
includeFieldPermissions?: boolean;
|
|
802
|
+
populateAccess?: PopulateAccess;
|
|
485
803
|
};
|
|
486
804
|
};
|
|
487
805
|
type AdvancedUpsertBody = {
|
|
@@ -492,7 +810,8 @@ type AdvancedUpsertBody = {
|
|
|
492
810
|
options?: {
|
|
493
811
|
returningAll?: boolean;
|
|
494
812
|
includePermissions?: boolean;
|
|
495
|
-
|
|
813
|
+
includeFieldPermissions?: boolean;
|
|
814
|
+
populateAccess?: PopulateAccess;
|
|
496
815
|
};
|
|
497
816
|
};
|
|
498
817
|
type DistinctBody = {
|
|
@@ -524,6 +843,23 @@ interface RequestComplexityOptions {
|
|
|
524
843
|
* Not a process-wide connection limit or protection against competing edits.
|
|
525
844
|
*/
|
|
526
845
|
maxBulkConcurrency?: number;
|
|
846
|
+
/**
|
|
847
|
+
* Defaults to 10. Per-request/per-runtime ceiling on concurrently awaited
|
|
848
|
+
* virtual-getter + row-finalization orchestration work (VIRT-00A D7), NOT a
|
|
849
|
+
* per-row multiplier and NOT leaf persistence admission. Top-level lists
|
|
850
|
+
* finalize rows through ONE bounded map with a shared hook gate held only
|
|
851
|
+
* for getter bodies (stable index-keyed order regardless of completion
|
|
852
|
+
* order), so peak active getter work stays at or below this limit — never
|
|
853
|
+
* `rows × getters`. Nested include/populate/embedded work uses bounded
|
|
854
|
+
* child maps with a finite bound derived from the same limit; recursion
|
|
855
|
+
* depth is additionally bounded by `maxCorrelatedDepth` and the correlated
|
|
856
|
+
* budgets. Recursive orchestration never holds leaf persistence permits
|
|
857
|
+
* (`RequestConcurrencyScheduler.work`, which admits only leaf
|
|
858
|
+
* adapter/document persistence ops) while awaiting descendants or hooks,
|
|
859
|
+
* so limit-1 configurations complete. Trusted direct DB/network I/O issued
|
|
860
|
+
* by virtual getters themselves runs outside the leaf persistence ceiling
|
|
861
|
+
* (no dataloader/batching in v1). Not a process-wide connection limit.
|
|
862
|
+
*/
|
|
527
863
|
maxHookConcurrency?: number;
|
|
528
864
|
/** Cumulative correlated target executions per request/runtime (default 100); permit release does not refund this budget. */
|
|
529
865
|
maxCorrelatedQueries?: number;
|
|
@@ -698,21 +1034,49 @@ declare class AccessRuntime {
|
|
|
698
1034
|
getDefaultModelOption<K extends keyof ExtendedDefaultModelRouterOptions>(key: K, defaultValue?: ExtendedDefaultModelRouterOptions[K]): ExtendedDefaultModelRouterOptions<unknown>[K];
|
|
699
1035
|
private createModelOptions;
|
|
700
1036
|
private getOrCreateModelOptions;
|
|
701
|
-
setModelOptions<TModel = unknown
|
|
702
|
-
setModelOption<K extends keyof ExtendedModelRouterOptions<TModel>, TModel = unknown
|
|
703
|
-
getModelOptions<TModel = unknown
|
|
704
|
-
getModelOption<K extends keyof ExtendedModelRouterOptions<TModel>, TModel = unknown
|
|
705
|
-
getExactModelOption<K extends keyof ExtendedModelRouterOptions<TModel>, TModel = unknown
|
|
1037
|
+
setModelOptions<TModel = unknown, TVirtuals extends object = Record<never, never>>(modelName: string, options: ModelRouterOptions<TModel, TVirtuals>): void;
|
|
1038
|
+
setModelOption<K extends keyof ExtendedModelRouterOptions<TModel, TVirtuals>, TModel = unknown, TVirtuals extends object = Record<never, never>>(modelName: string, key: K, value: ExtendedModelRouterOptions<TModel, TVirtuals>[K]): void;
|
|
1039
|
+
getModelOptions<TModel = unknown, TVirtuals extends object = Record<never, never>>(modelName: string): ModelRouterOptions<TModel, TVirtuals>;
|
|
1040
|
+
getModelOption<K extends keyof ExtendedModelRouterOptions<TModel, TVirtuals>, TModel = unknown, TVirtuals extends object = Record<never, never>>(modelName: string, key: K | string, defaultValue?: ExtendedModelRouterOptions<TModel, TVirtuals>[K]): ExtendedModelRouterOptions<TModel, TVirtuals>[K];
|
|
1041
|
+
getExactModelOption<K extends keyof ExtendedModelRouterOptions<TModel, TVirtuals>, TModel = unknown, TVirtuals extends object = Record<never, never>>(modelName: string, key: K | string): ExtendedModelRouterOptions<TModel, TVirtuals>[K];
|
|
1042
|
+
/**
|
|
1043
|
+
* List registered virtual leaf names for a scope (VIRT-01).
|
|
1044
|
+
* Scope containers (`{ sub }`) are not leaves. Inapplicable names are
|
|
1045
|
+
* still listed here; applicability is decided per-access by
|
|
1046
|
+
* {@link resolveVirtualDescriptor}. Request-controlled include collisions
|
|
1047
|
+
* are validated by VIRT-05, not guessed here.
|
|
1048
|
+
*/
|
|
1049
|
+
getVirtualNames(modelName: string, scopePath?: string[]): string[];
|
|
1050
|
+
/**
|
|
1051
|
+
* Whether a field is a registered virtual leaf in a scope (VIRT-01).
|
|
1052
|
+
* Inapplicable registered names stay virtual (never persisted), even when
|
|
1053
|
+
* no getter applies for the current access. Containers and persisted
|
|
1054
|
+
* fields return false.
|
|
1055
|
+
*/
|
|
1056
|
+
isVirtualField(modelName: string, fieldName: string, scopePath?: string[]): boolean;
|
|
1057
|
+
/**
|
|
1058
|
+
* Resolve an applicable virtual descriptor with exact → `.default` → bare
|
|
1059
|
+
* semantics (VIRT-01, VIRT-00A D1/D5). Returns `undefined` when no getter
|
|
1060
|
+
* applies for `access`. Crucially, an access-record object is NEVER
|
|
1061
|
+
* interpreted as a getter descriptor when the access is absent: an
|
|
1062
|
+
* inapplicable registered name stays virtual (excluded from persisted
|
|
1063
|
+
* projections, write admission, and DB sort/filter/distinct).
|
|
1064
|
+
* Shared record-level `dependsOn` is inherited when a leaf lacks its own.
|
|
1065
|
+
*/
|
|
1066
|
+
resolveVirtualDescriptor(modelName: string, virtualName: string, access: string, scopePath?: string[]): {
|
|
1067
|
+
get: (...args: never[]) => unknown;
|
|
1068
|
+
dependsOn: string[];
|
|
1069
|
+
} | undefined;
|
|
706
1070
|
getModelNames(): string[];
|
|
707
1071
|
hasModel(modelName: string): boolean;
|
|
708
1072
|
getModelJsonSchema(modelName: string): Record<string, unknown>;
|
|
709
1073
|
private createDataOptions;
|
|
710
1074
|
private getOrCreateDataOptions;
|
|
711
1075
|
setDataOptions<TData = unknown>(dataName: string, options: DataRouterOptions<TData>): void;
|
|
712
|
-
setDataOption<K extends keyof
|
|
1076
|
+
setDataOption<K extends keyof ExtendedDataRouterOptions<TData>, TData = unknown>(dataName: string, key: K, value: ExtendedDataRouterOptions<TData>[K]): void;
|
|
713
1077
|
getDataOptions<TData = unknown>(dataName: string): DataRouterOptions<TData>;
|
|
714
1078
|
getDataSnapshot<TData = unknown>(dataName: string): readonly TData[];
|
|
715
|
-
getDataOption<K extends keyof
|
|
1079
|
+
getDataOption<K extends keyof ExtendedDataRouterOptions<TData>, TData = unknown>(dataName: string, key: K | string, defaultValue?: ExtendedDataRouterOptions<TData>[K]): ExtendedDataRouterOptions<TData>[K];
|
|
716
1080
|
getExactDataOption<K extends keyof ExtendedDataRouterOptions<TData>, TData = unknown>(dataName: string, key: K | string): ExtendedDataRouterOptions<TData>[K];
|
|
717
1081
|
getDataNames(): string[];
|
|
718
1082
|
ensureModelMeta(modelName: string): void;
|
|
@@ -722,22 +1086,12 @@ declare class AccessRuntime {
|
|
|
722
1086
|
}
|
|
723
1087
|
declare const defaultRuntime: AccessRuntime;
|
|
724
1088
|
|
|
725
|
-
declare class RequestConcurrencyScheduler {
|
|
726
|
-
readonly limit: number;
|
|
727
|
-
private activeWork;
|
|
728
|
-
private readonly waiters;
|
|
729
|
-
constructor(limit: number);
|
|
730
|
-
map<TInput, TOutput>(items: TInput[], iteratee: (item: TInput, index: number, scheduled: true) => Promise<TOutput>): Promise<TOutput[]>;
|
|
731
|
-
run<TOutput>(iteratee: (scheduled: true) => Promise<TOutput>): Promise<TOutput>;
|
|
732
|
-
work<TOutput>(operation: () => TOutput | PromiseLike<TOutput>): Promise<TOutput>;
|
|
733
|
-
}
|
|
734
|
-
|
|
735
1089
|
type CrossResourceModelOperation = 'list' | 'read' | 'count';
|
|
736
1090
|
interface CorrelatedExecState {
|
|
737
1091
|
totalQueries: number;
|
|
738
1092
|
scheduler: RequestConcurrencyScheduler;
|
|
739
1093
|
}
|
|
740
|
-
declare class Base<TModel = unknown
|
|
1094
|
+
declare class Base<TModel = unknown, TVirtuals extends object = Record<never, never>> {
|
|
741
1095
|
protected req: ModelRequest;
|
|
742
1096
|
protected modelName: string;
|
|
743
1097
|
protected readonly requestRuntime: AccessRuntime;
|
|
@@ -772,7 +1126,7 @@ declare class Base<TModel = unknown> {
|
|
|
772
1126
|
getRequestComplexity(): Required<RequestComplexityOptions>;
|
|
773
1127
|
protected getClientRequestErrorResult(error: unknown): ErrorResult | null;
|
|
774
1128
|
protected throwClientRequestError(code: ErrorResult['code'], detail: string): never;
|
|
775
|
-
protected getAuthorizedTargetService(modelName: string, op: CrossResourceModelOperation): Promise<PublicService<unknown
|
|
1129
|
+
protected getAuthorizedTargetService(modelName: string, op: CrossResourceModelOperation): Promise<PublicService<unknown, Record<never, never>>>;
|
|
776
1130
|
protected processInclude(include: Include | Include[]): {
|
|
777
1131
|
includes: LegacyInclude[];
|
|
778
1132
|
correlatedIncludes: CorrelatedInclude[];
|
|
@@ -781,6 +1135,17 @@ declare class Base<TModel = unknown> {
|
|
|
781
1135
|
correlatedReferenceFields: string[];
|
|
782
1136
|
};
|
|
783
1137
|
private validateIncludeOutputPath;
|
|
1138
|
+
/**
|
|
1139
|
+
* VIRT-05 preflight: request include output paths vs registered virtual
|
|
1140
|
+
* paths with ancestor/descendant checks per receiving scope, before any
|
|
1141
|
+
* target dispatch. Registered virtual names remain virtual even when no
|
|
1142
|
+
* getter applies; an include that would overwrite a virtual (equal,
|
|
1143
|
+
* ancestor, or descendant, including bracket equivalents) fails closed
|
|
1144
|
+
* with `BadRequest`. Ordinary non-virtual collisions stay permitted and
|
|
1145
|
+
* existing permission-metadata protections above are preserved.
|
|
1146
|
+
*/
|
|
1147
|
+
private collectVirtualDbPathsForPreflight;
|
|
1148
|
+
private validateIncludeVirtualCollision;
|
|
784
1149
|
private assertIncludeOutputPaths;
|
|
785
1150
|
private getForeignKeyValues;
|
|
786
1151
|
private getForeignKey;
|
|
@@ -853,6 +1218,20 @@ declare class Core {
|
|
|
853
1218
|
beforeDelete<T>(modelName: string, doc: T, context: ModelHookContext): Promise<void>;
|
|
854
1219
|
afterDelete<T>(modelName: string, doc: T, context: ModelHookContext): Promise<void>;
|
|
855
1220
|
genDocPermissions(modelName: string, doc: unknown, access: DocPermissionsAccess, context: ModelHookContext): Promise<{}>;
|
|
1221
|
+
/**
|
|
1222
|
+
* VIRT-03 internal grant resolution (VIRT-00A D1/D2).
|
|
1223
|
+
*
|
|
1224
|
+
* Returns full internal document grants for virtual authorization without
|
|
1225
|
+
* consulting response-metadata switches (`includePermissions`,
|
|
1226
|
+
* `includeFieldPermissions`, `skim`, `stripPermissionsField`,
|
|
1227
|
+
* `exposedDocPermissionKeys`, `disableFieldPermissions`): those switches
|
|
1228
|
+
* affect serialization at the caller boundary only, never authorization
|
|
1229
|
+
* inputs. When `supplied` is a plain object it is reused as-is (no second
|
|
1230
|
+
* hook invocation for the same output-stage access); otherwise the correct
|
|
1231
|
+
* `docPermissionsAccess` hook runs once. The result populates scope-aware
|
|
1232
|
+
* `context.docPermissions` (full grants, distinct from serialized metadata).
|
|
1233
|
+
*/
|
|
1234
|
+
resolveFinalizerDocPermissions(modelName: string, doc: unknown, access: DocPermissionsAccess, context: ModelHookContext, supplied?: unknown): Promise<Record<string, unknown>>;
|
|
856
1235
|
addEmptyPermissions<T>(modelName: string, doc: T): T;
|
|
857
1236
|
addDocPermissions<T>(modelName: string, doc: T, access: DocPermissionsAccess, context: ModelHookContext): Promise<T>;
|
|
858
1237
|
addFieldPermissions<T extends {
|
|
@@ -865,10 +1244,12 @@ declare class Core {
|
|
|
865
1244
|
setPermissions(): Promise<void>;
|
|
866
1245
|
canActivate(routeGuard: Validation): Promise<boolean>;
|
|
867
1246
|
isAllowed(modelName: string, access: RouteGuardAccess | string): Promise<boolean>;
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
1247
|
+
/** Authorize route entry using explicit server-owned basic/advanced metadata. */
|
|
1248
|
+
isAllowedRoute(modelName: string, baseAccess: RouteGuardAccess | string, variant: RouteVariant): Promise<boolean>;
|
|
1249
|
+
getService<TModel = unknown, TVirtuals extends object = Record<never, never>>(modelName: string): Service<TModel, TVirtuals>;
|
|
1250
|
+
getPublicService<TModel = unknown, TVirtuals extends object = Record<never, never>>(modelName: string): PublicService<TModel, TVirtuals>;
|
|
1251
|
+
service<TModel = unknown, TVirtuals extends object = Record<never, never>>(modelName: string): PublicService<TModel, TVirtuals>;
|
|
1252
|
+
svc<TModel = unknown, TVirtuals extends object = Record<never, never>>(modelName: string): PublicService<TModel, TVirtuals>;
|
|
872
1253
|
private getGlobalPermissions;
|
|
873
1254
|
}
|
|
874
1255
|
|
|
@@ -888,6 +1269,8 @@ declare class DataCore {
|
|
|
888
1269
|
setPermissions(): Promise<void>;
|
|
889
1270
|
canActivate(routeGuard: Validation): Promise<boolean>;
|
|
890
1271
|
isAllowed(dataName: string, access: RouteGuardAccess | string): Promise<boolean>;
|
|
1272
|
+
/** Authorize route entry using explicit server-owned basic/advanced metadata. */
|
|
1273
|
+
isAllowedRoute(dataName: string, baseAccess: RouteGuardAccess | string, variant: RouteVariant): Promise<boolean>;
|
|
891
1274
|
getService<TData = unknown>(dataName: string): DataService<TData>;
|
|
892
1275
|
service<TData = unknown>(dataName: string): DataService<TData>;
|
|
893
1276
|
svc<TData = unknown>(dataName: string): DataService<TData>;
|
|
@@ -948,10 +1331,37 @@ interface PublicCreateArgs {
|
|
|
948
1331
|
tasks?: Task | Task[];
|
|
949
1332
|
}
|
|
950
1333
|
interface CreateArgs extends Omit<PublicCreateArgs, 'select' | 'tasks'> {
|
|
1334
|
+
overrides?: {
|
|
1335
|
+
populate?: Populate[] | string;
|
|
1336
|
+
/**
|
|
1337
|
+
* VIRT-04 internal plan transport (VIRT-00A D6).
|
|
1338
|
+
* Effective mutation output selection carried into internal finalization
|
|
1339
|
+
* before virtual evaluation. Internal create args omit `select` publicly;
|
|
1340
|
+
* this trusted transport supplies the effective selection (explicit
|
|
1341
|
+
* `select` or `undefined` for all) without changing public presentation
|
|
1342
|
+
* (public `_create` still picks after decorate/tasks).
|
|
1343
|
+
*/
|
|
1344
|
+
effectiveSelect?: Projection;
|
|
1345
|
+
/**
|
|
1346
|
+
* VIRT-04 initiating operation (VIRT-00A D1). Upsert branches keep
|
|
1347
|
+
* `operation: 'upsert'` while virtual/output/doc accesses follow the
|
|
1348
|
+
* taken branch. Defaults to `'create'` when absent.
|
|
1349
|
+
*/
|
|
1350
|
+
operation?: string;
|
|
1351
|
+
};
|
|
951
1352
|
}
|
|
952
1353
|
interface PublicCreateOptions {
|
|
953
1354
|
skim?: boolean;
|
|
954
1355
|
includePermissions?: boolean;
|
|
1356
|
+
/**
|
|
1357
|
+
* Include field-level `_view`/`_edit` maps. Defaults to `includePermissions`
|
|
1358
|
+
* when omitted. `includePermissions: true` + `includeFieldPermissions: false`
|
|
1359
|
+
* keeps `docPermissions`-hook keys but omits `_view`/`_edit` entirely.
|
|
1360
|
+
* Enabling field maps also computes doc permissions as grant input (even
|
|
1361
|
+
* under skim or when `includePermissions` is false), so the maps match the
|
|
1362
|
+
* `includePermissions: true` output.
|
|
1363
|
+
*/
|
|
1364
|
+
includeFieldPermissions?: boolean;
|
|
955
1365
|
populateAccess?: PopulateAccess;
|
|
956
1366
|
}
|
|
957
1367
|
interface CreateOptions extends PublicCreateOptions {
|
|
@@ -971,6 +1381,15 @@ interface PublicListArgs {
|
|
|
971
1381
|
interface PublicListOptions {
|
|
972
1382
|
skim?: boolean;
|
|
973
1383
|
includePermissions?: boolean;
|
|
1384
|
+
/**
|
|
1385
|
+
* Include field-level `_view`/`_edit` maps. Defaults to `includePermissions`
|
|
1386
|
+
* when omitted. `includePermissions: true` + `includeFieldPermissions: false`
|
|
1387
|
+
* keeps `docPermissions`-hook keys but omits `_view`/`_edit` entirely.
|
|
1388
|
+
* Enabling field maps also computes doc permissions as grant input (even
|
|
1389
|
+
* under skim or when `includePermissions` is false), so the maps match the
|
|
1390
|
+
* `includePermissions: true` output.
|
|
1391
|
+
*/
|
|
1392
|
+
includeFieldPermissions?: boolean;
|
|
974
1393
|
includeCount?: boolean;
|
|
975
1394
|
populateAccess?: PopulateAccess;
|
|
976
1395
|
lean?: boolean;
|
|
@@ -987,6 +1406,15 @@ interface PublicReadOptions {
|
|
|
987
1406
|
populateAccess?: PopulateAccess;
|
|
988
1407
|
lean?: boolean;
|
|
989
1408
|
includePermissions?: boolean;
|
|
1409
|
+
/**
|
|
1410
|
+
* Include field-level `_view`/`_edit` maps. Defaults to `includePermissions`
|
|
1411
|
+
* when omitted. `includePermissions: true` + `includeFieldPermissions: false`
|
|
1412
|
+
* keeps `docPermissions`-hook keys but omits `_view`/`_edit` entirely.
|
|
1413
|
+
* Enabling field maps also computes doc permissions as grant input (even
|
|
1414
|
+
* under skim or when `includePermissions` is false), so the maps match the
|
|
1415
|
+
* `includePermissions: true` output.
|
|
1416
|
+
*/
|
|
1417
|
+
includeFieldPermissions?: boolean;
|
|
990
1418
|
/** Retry an authorized read miss with list access (default true); requires list operation access. Forbidden/BadRequest never retry. */
|
|
991
1419
|
tryList?: boolean;
|
|
992
1420
|
}
|
|
@@ -1003,13 +1431,34 @@ interface UpdateOneArgs<T = unknown> extends Omit<PublicUpdateArgs, 'select' | '
|
|
|
1003
1431
|
/** Trusted replacement: nullish generates row policy; false returns Forbidden without filter generation or persistence. */
|
|
1004
1432
|
filter?: Filter<T>;
|
|
1005
1433
|
populate?: Populate[] | string;
|
|
1434
|
+
/**
|
|
1435
|
+
* VIRT-04 internal plan transport (VIRT-00A D6).
|
|
1436
|
+
* Effective mutation output selection carried into internal finalization
|
|
1437
|
+
* before virtual evaluation (explicit `select`, `returningAll: false`
|
|
1438
|
+
* implicit `Object.keys(data)+_id`, or `undefined` for all). Public
|
|
1439
|
+
* `_update` still enforces presentation pick after decorate/tasks.
|
|
1440
|
+
*/
|
|
1441
|
+
effectiveSelect?: Projection;
|
|
1442
|
+
/**
|
|
1443
|
+
* VIRT-04 initiating operation (VIRT-00A D1). Upsert branches keep
|
|
1444
|
+
* `operation: 'upsert'` while accesses follow the taken branch.
|
|
1445
|
+
* Defaults to `'update'` when absent.
|
|
1446
|
+
*/
|
|
1447
|
+
operation?: string;
|
|
1006
1448
|
};
|
|
1007
1449
|
}
|
|
1008
|
-
interface UpdateByIdArgs<T = unknown> extends Omit<
|
|
1450
|
+
interface UpdateByIdArgs<T = unknown> extends Omit<PublicUpdateArgs, 'select' | 'tasks'> {
|
|
1009
1451
|
overrides?: {
|
|
1010
1452
|
populate?: Populate[] | string;
|
|
1011
1453
|
/** Nullish resolves the identifier; false denies without ID/filter generation or persistence. Objects still receive row policy. */
|
|
1012
1454
|
idFilter?: Filter<T>;
|
|
1455
|
+
/**
|
|
1456
|
+
* VIRT-04 internal plan transport (VIRT-00A D6): effective output
|
|
1457
|
+
* selection for virtual planning before decorate/tasks.
|
|
1458
|
+
*/
|
|
1459
|
+
effectiveSelect?: Projection;
|
|
1460
|
+
/** VIRT-04 initiating operation (VIRT-00A D1); defaults to `'update'`. */
|
|
1461
|
+
operation?: string;
|
|
1013
1462
|
};
|
|
1014
1463
|
}
|
|
1015
1464
|
interface UpsertArgs<T = unknown> extends UpdateOneArgs<T> {
|
|
@@ -1018,6 +1467,15 @@ interface PublicUpdateOptions {
|
|
|
1018
1467
|
skim?: boolean;
|
|
1019
1468
|
returningAll?: boolean;
|
|
1020
1469
|
includePermissions?: boolean;
|
|
1470
|
+
/**
|
|
1471
|
+
* Include field-level `_view`/`_edit` maps. Defaults to `includePermissions`
|
|
1472
|
+
* when omitted. `includePermissions: true` + `includeFieldPermissions: false`
|
|
1473
|
+
* keeps `docPermissions`-hook keys but omits `_view`/`_edit` entirely.
|
|
1474
|
+
* Enabling field maps also computes doc permissions as grant input (even
|
|
1475
|
+
* under skim or when `includePermissions` is false), so the maps match the
|
|
1476
|
+
* `includePermissions: true` output.
|
|
1477
|
+
*/
|
|
1478
|
+
includeFieldPermissions?: boolean;
|
|
1021
1479
|
populateAccess?: PopulateAccess;
|
|
1022
1480
|
}
|
|
1023
1481
|
interface PublicUpsertOptions extends PublicUpdateOptions {
|
|
@@ -1067,7 +1525,13 @@ type WithDefaultId<T> = T extends object ? T & {
|
|
|
1067
1525
|
} : {
|
|
1068
1526
|
_id?: unknown;
|
|
1069
1527
|
};
|
|
1070
|
-
|
|
1528
|
+
/**
|
|
1529
|
+
* Public output with optional computed virtual shape (VIRT-01, VIRT-00A D3).
|
|
1530
|
+
* Virtual names are NEVER added to persisted `Filter`, sort/distinct inputs,
|
|
1531
|
+
* or client write types — only selected output shape gains optional virtuals.
|
|
1532
|
+
*/
|
|
1533
|
+
type WithVirtuals<TModel, TVirtuals extends object = Record<never, never>> = TModel & Partial<TVirtuals>;
|
|
1534
|
+
type PublicOutput<T, TVirtuals extends object = Record<never, never>> = WithVirtuals<T, TVirtuals> extends object ? WithVirtuals<T, TVirtuals> & Record<string, unknown> : Record<string, unknown>;
|
|
1071
1535
|
type ModelDocument<TModel = unknown> = mongoose.Document & TModel;
|
|
1072
1536
|
type TrimLeft<S extends string> = S extends ` ${infer Rest}` ? TrimLeft<Rest> : S;
|
|
1073
1537
|
type TrimRight<S extends string> = S extends `${infer Rest} ` ? TrimRight<Rest> : S;
|
|
@@ -1085,7 +1549,12 @@ type DeepPickSingle<T, TPath extends string> = TPath extends `${infer Head}.${in
|
|
|
1085
1549
|
[K in Head]: WithDefaultId<T>[K] extends readonly (infer U)[] ? Array<Simplify<DeepPickSingle<U, Tail>>> : WithDefaultId<T>[K] extends object ? Simplify<DeepPickSingle<WithDefaultId<T>[K], Tail>> : WithDefaultId<T>[K];
|
|
1086
1550
|
} : {} : {} : TPath extends keyof WithDefaultId<T> ? Pick<WithDefaultId<T>, TPath> : {};
|
|
1087
1551
|
type DeepPick<T, TPaths extends string> = Simplify<UnionToIntersection<DeepPickSingle<T, TPaths>>>;
|
|
1088
|
-
|
|
1552
|
+
/**
|
|
1553
|
+
* Literal-select output with optional virtuals (VIRT-01, VIRT-00A D3).
|
|
1554
|
+
* Virtual outputs stay optional (`Partial`) because authorization, absent
|
|
1555
|
+
* dependencies, `undefined`, and fail-closed errors can omit them.
|
|
1556
|
+
*/
|
|
1557
|
+
type SelectedPublicOutput<T, TProjection, TVirtuals extends object = Record<never, never>> = string extends PositiveProjectionPaths<TProjection> ? PublicOutput<T, TVirtuals> : [PositiveProjectionPaths<TProjection>] extends [never] ? PublicOutput<T, TVirtuals> : DeepPick<WithVirtuals<T, TVirtuals>, Extract<PositiveProjectionPaths<TProjection>, string>>;
|
|
1089
1558
|
type PopulateInput = Populate | string;
|
|
1090
1559
|
type PopulateInputs<TPopulate> = TPopulate extends readonly (infer U)[] ? U : TPopulate;
|
|
1091
1560
|
type PopulatePath<TPopulateEntry> = TPopulateEntry extends string ? TPopulateEntry : TPopulateEntry extends {
|
|
@@ -1102,7 +1571,7 @@ type PopulateEntryShape<T, TPath extends string, TValue> = TPath extends `${infe
|
|
|
1102
1571
|
}, TPath> : {};
|
|
1103
1572
|
type PopulateOutputShape<T, TPopulateEntry> = PopulatePath<TPopulateEntry> extends infer TPath extends string ? PopulateEntryShape<T, TPath, PopulateLeafValue<PathValue<WithDefaultId<T>, TPath>, TPopulateEntry>> : {};
|
|
1104
1573
|
type PopulateOutput<T, TPopulate> = string extends PopulatePath<PopulateInputs<TPopulate>> ? {} : [PopulatePath<PopulateInputs<TPopulate>>] extends [never] ? {} : Simplify<UnionToIntersection<PopulateOutputShape<T, Extract<PopulateInputs<TPopulate>, PopulateInput>>>>;
|
|
1105
|
-
type SelectedPopulatedPublicOutput<T, TProjection, TPopulate
|
|
1574
|
+
type SelectedPopulatedPublicOutput<T, TProjection, TPopulate, TVirtuals extends object = Record<never, never>> = Simplify<SelectedPublicOutput<T, TProjection, TVirtuals> & PopulateOutput<T, TPopulate>>;
|
|
1106
1575
|
type Unwrap<T> = NonNullable<Descend<T>>;
|
|
1107
1576
|
type DeepFieldPath<T, Depth extends number = 4> = [Depth] extends [never] ? never : Unwrap<T> extends NonTraversable ? never : {
|
|
1108
1577
|
[K in Extract<keyof Unwrap<T>, string>]: K | (NonNullable<Unwrap<T>[K]> extends NonTraversable ? never : NonNullable<Unwrap<T>[K]> extends readonly (infer U)[] ? Unwrap<U> extends NonTraversable ? never : `${K}.${DeepFieldPath<U, PrevDepth[Depth]>}` : NonNullable<Unwrap<T>[K]> extends object ? `${K}.${DeepFieldPath<NonNullable<Unwrap<T>[K]>, PrevDepth[Depth]>}` : never);
|
|
@@ -1233,7 +1702,16 @@ interface SubPopulate {
|
|
|
1233
1702
|
type GuardHook<TRequest extends AccessRouterRequest = AccessRouterRequest> = (this: TRequest, permissions: AccessRouterPermissions) => boolean | Promise<boolean>;
|
|
1234
1703
|
type Validation = boolean | string | string[] | GuardHook;
|
|
1235
1704
|
type SelectAccess = 'list' | 'create' | 'read' | 'update' | string;
|
|
1236
|
-
|
|
1705
|
+
/** Base operations with both basic and advanced generated model routes. */
|
|
1706
|
+
type PairedRouteAccess = 'list' | 'read' | 'create' | 'update' | 'upsert' | 'count' | 'distinct';
|
|
1707
|
+
/** Existing base route guards, including unpaired operations and subdocument access. */
|
|
1708
|
+
type RouteBaseAccess = PairedRouteAccess | 'new' | 'delete' | 'subs';
|
|
1709
|
+
/** Server-owned transport variant for a paired generated route. */
|
|
1710
|
+
type RouteVariant = 'basic' | 'advanced';
|
|
1711
|
+
/** The fourteen route-only keys, or the two keys for a specified paired base operation. */
|
|
1712
|
+
type RouteVariantAccess<TAccess extends PairedRouteAccess = PairedRouteAccess> = `${RouteVariant}${Capitalize<TAccess>}`;
|
|
1713
|
+
/** Known route guards with the existing trusted custom access-string compatibility. */
|
|
1714
|
+
type RouteGuardAccess = RouteBaseAccess | RouteVariantAccess | (string & {});
|
|
1237
1715
|
type DocPermissionsAccess = 'list' | 'create' | 'read' | 'update' | string;
|
|
1238
1716
|
type BaseFilterAccess = 'list' | 'read' | 'update' | 'delete' | string;
|
|
1239
1717
|
type DecorateAccess = 'list' | 'create' | 'read' | 'update' | string;
|
|
@@ -1302,6 +1780,7 @@ interface RootModelNewQueryEntry extends RootQueryEntryBase<'model', 'new'> {
|
|
|
1302
1780
|
options?: {
|
|
1303
1781
|
skim?: boolean;
|
|
1304
1782
|
includePermissions?: boolean;
|
|
1783
|
+
includeFieldPermissions?: boolean;
|
|
1305
1784
|
};
|
|
1306
1785
|
}
|
|
1307
1786
|
interface RootModelListQueryEntry extends RootQueryEntryBase<'model', 'list'> {
|
|
@@ -1431,7 +1910,7 @@ interface Task {
|
|
|
1431
1910
|
options?: Record<string, unknown>;
|
|
1432
1911
|
}
|
|
1433
1912
|
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>;
|
|
1913
|
+
query: Record<'skip' | 'limit' | 'page' | 'page_size' | 'try_list' | 'skim' | 'include_permissions' | 'include_field_permissions' | 'include_count' | 'include_extra_headers' | 'returning_all', string>;
|
|
1435
1914
|
}
|
|
1436
1915
|
interface ModelRequest extends AccessRouterBaseRequest {
|
|
1437
1916
|
macl: Core;
|
|
@@ -1522,6 +2001,15 @@ interface FindArgs<T = unknown> {
|
|
|
1522
2001
|
interface FindOptions {
|
|
1523
2002
|
skim?: boolean;
|
|
1524
2003
|
includePermissions?: boolean;
|
|
2004
|
+
/**
|
|
2005
|
+
* Include field-level `_view`/`_edit` maps. Defaults to `includePermissions`
|
|
2006
|
+
* when omitted. `includePermissions: true` + `includeFieldPermissions: false`
|
|
2007
|
+
* keeps `docPermissions`-hook keys but omits `_view`/`_edit` entirely.
|
|
2008
|
+
* Enabling field maps also computes doc permissions as grant input (even
|
|
2009
|
+
* under skim or when `includePermissions` is false), so the maps match the
|
|
2010
|
+
* `includePermissions: true` output.
|
|
2011
|
+
*/
|
|
2012
|
+
includeFieldPermissions?: boolean;
|
|
1525
2013
|
includeCount?: boolean;
|
|
1526
2014
|
populateAccess?: PopulateAccess;
|
|
1527
2015
|
lean?: boolean;
|
|
@@ -1544,6 +2032,15 @@ interface FindOneOptions {
|
|
|
1544
2032
|
skim?: boolean;
|
|
1545
2033
|
lean?: boolean;
|
|
1546
2034
|
includePermissions?: boolean;
|
|
2035
|
+
/**
|
|
2036
|
+
* Include field-level `_view`/`_edit` maps. Defaults to `includePermissions`
|
|
2037
|
+
* when omitted. `includePermissions: true` + `includeFieldPermissions: false`
|
|
2038
|
+
* keeps `docPermissions`-hook keys but omits `_view`/`_edit` entirely.
|
|
2039
|
+
* Enabling field maps also computes doc permissions as grant input (even
|
|
2040
|
+
* under skim or when `includePermissions` is false), so the maps match the
|
|
2041
|
+
* `includePermissions: true` output.
|
|
2042
|
+
*/
|
|
2043
|
+
includeFieldPermissions?: boolean;
|
|
1547
2044
|
}
|
|
1548
2045
|
interface FindByIdArgs<T = unknown> {
|
|
1549
2046
|
select?: Projection;
|
|
@@ -1597,6 +2094,161 @@ type ModelHook<TValue = unknown> = HookChain<TValue, ModelHookContext, ModelRequ
|
|
|
1597
2094
|
type ModelListHook<TValue = unknown> = HookChain<TValue[], ModelHookContext, ModelRequest>;
|
|
1598
2095
|
type DataHook<TValue = unknown> = HookChain<TValue, DataHookContext, DataRequest>;
|
|
1599
2096
|
type DataListHook<TValue = unknown> = HookChain<TValue[], DataHookContext, DataRequest>;
|
|
2097
|
+
/**
|
|
2098
|
+
* Supported per-access virtual getter keys (VIRT-00A D1/D5).
|
|
2099
|
+
* `default` is a fallback access; `delete`/`distinct`/`count` are NOT
|
|
2100
|
+
* virtual accesses and must be rejected at configuration time.
|
|
2101
|
+
*/
|
|
2102
|
+
type ModelVirtualAccess = 'list' | 'create' | 'read' | 'update';
|
|
2103
|
+
type ModelVirtualRecordAccess = 'default' | ModelVirtualAccess;
|
|
2104
|
+
/**
|
|
2105
|
+
* Scope-aware virtual getter context (VIRT-00A D2, frozen).
|
|
2106
|
+
* Extends {@link ModelHookContext} with the receiving model, definition/
|
|
2107
|
+
* permission `scopePath` (e.g. `[]` at root, `['contacts','sub']` for
|
|
2108
|
+
* `contacts.sub.displayName`), and the three effective accesses. The
|
|
2109
|
+
* initiating operation stays in `context.operation` and is never
|
|
2110
|
+
* overwritten with fallback/effective visibility.
|
|
2111
|
+
*
|
|
2112
|
+
* Embedded outputs reuse the owning parent's internally computed grants with
|
|
2113
|
+
* the scope's `permissionSchema.<field>.sub` rules; dedicated subdocument
|
|
2114
|
+
* routes resolve grants from the raw parent lookup. Related (populate/
|
|
2115
|
+
* include) targets finalize with the target model's own definitions,
|
|
2116
|
+
* permissions, selection, and effective access before parent getters run.
|
|
2117
|
+
*/
|
|
2118
|
+
interface ModelVirtualContext extends ModelHookContext {
|
|
2119
|
+
receivingModelName: string;
|
|
2120
|
+
scopePath: string[];
|
|
2121
|
+
virtualAccess: ModelVirtualAccess;
|
|
2122
|
+
outputAccess: ModelVirtualAccess;
|
|
2123
|
+
docPermissionsAccess: ModelVirtualAccess;
|
|
2124
|
+
}
|
|
2125
|
+
type IsUnknownVirtual<T> = unknown extends T ? ([keyof T] extends [never] ? true : false) : false;
|
|
2126
|
+
/**
|
|
2127
|
+
* Typed isolated persisted view for a virtual getter scope (VIRT-00A D3/D4).
|
|
2128
|
+
* Known models use a partial read-only persisted view (never
|
|
2129
|
+
* `Record<string, unknown>`); unknown/untyped models keep an explicit
|
|
2130
|
+
* loose fallback.
|
|
2131
|
+
*/
|
|
2132
|
+
type ModelVirtualDoc<TModel = unknown> = IsUnknownVirtual<TModel> extends true ? Readonly<Partial<Record<string, unknown>>> : [Extract<keyof TModel, string>] extends [never] ? Readonly<Partial<Record<string, unknown>>> : Readonly<Partial<TModel>>;
|
|
2133
|
+
/**
|
|
2134
|
+
* Virtual getter signature (VIRT-00A D3/D4, frozen).
|
|
2135
|
+
* Receives an isolated plain-object snapshot of persisted dependencies +
|
|
2136
|
+
* already-finalized related/embedded outputs for its scope. Sibling virtual
|
|
2137
|
+
* values are not visible. Mutating the input cannot change response data or
|
|
2138
|
+
* lifecycle snapshots. Only the returned field value is committed;
|
|
2139
|
+
* `undefined` omits the field; throws omit fail-closed. `this` is the
|
|
2140
|
+
* model request.
|
|
2141
|
+
*/
|
|
2142
|
+
type ModelVirtualGetter<TModel = unknown, TValue = unknown> = (this: ModelRequest, doc: ModelVirtualDoc<TModel>, permissions: AccessRouterPermissions, context: ModelVirtualContext) => MaybePromise<TValue | undefined>;
|
|
2143
|
+
/**
|
|
2144
|
+
* Leaf virtual descriptor (VIRT-01): `{ get, dependsOn }`.
|
|
2145
|
+
* `dependsOn` lists top-level persisted field names relative to the
|
|
2146
|
+
* definition's scope (e.g. `['displayName']` inside `contacts.sub`, never
|
|
2147
|
+
* `['contacts.sub.displayName']`). Dotted paths and virtual-to-virtual
|
|
2148
|
+
* dependencies are rejected at configuration time.
|
|
2149
|
+
*
|
|
2150
|
+
* The getter runs after `toObject()`/lean normalization and after internal
|
|
2151
|
+
* document-permissions computation, with global permissions available and
|
|
2152
|
+
* before `decorate`/`decorateAll`/tasks. Only the returned field value is
|
|
2153
|
+
* committed via document-aware helpers: `undefined` (or a throw) omits the
|
|
2154
|
+
* field fail-closed and computed values never persist. Getters see an
|
|
2155
|
+
* isolated plain-object view for both lean and hydrated results; mutating the
|
|
2156
|
+
* input cannot change response data or lifecycle snapshots.
|
|
2157
|
+
*
|
|
2158
|
+
* @example
|
|
2159
|
+
* ```ts
|
|
2160
|
+
* import type { ModelVirtualDescriptor } from '@web-ts-toolkit/access-router';
|
|
2161
|
+
* interface User { name: string; address: string; }
|
|
2162
|
+
* const descriptor: ModelVirtualDescriptor<User, string> = {
|
|
2163
|
+
* dependsOn: ['address'],
|
|
2164
|
+
* get: async (doc) => (doc.address === undefined ? undefined : `addr:${doc.address}`),
|
|
2165
|
+
* };
|
|
2166
|
+
* ```
|
|
2167
|
+
*/
|
|
2168
|
+
interface ModelVirtualDescriptor<TModel = unknown, TValue = unknown> {
|
|
2169
|
+
get: ModelVirtualGetter<TModel, TValue>;
|
|
2170
|
+
dependsOn?: Array<AccessRouterFieldKey<TModel>>;
|
|
2171
|
+
}
|
|
2172
|
+
/**
|
|
2173
|
+
* Per-access virtual record (VIRT-01): `default` + `list`/`create`/`read`/
|
|
2174
|
+
* `update` (NOT `delete`/`distinct`/`count`). Each present access holds a
|
|
2175
|
+
* leaf descriptor or bare getter shorthand. An optional shared `dependsOn`
|
|
2176
|
+
* supports the documented `{ dependsOn, read: getter }` shape; a leaf's own
|
|
2177
|
+
* `dependsOn` wins over the shared list during resolution.
|
|
2178
|
+
*/
|
|
2179
|
+
type ModelVirtualAccessRecord<TModel = unknown, TValue = unknown> = {
|
|
2180
|
+
default?: ModelVirtualDescriptor<TModel, TValue> | ModelVirtualGetter<TModel, TValue>;
|
|
2181
|
+
list?: ModelVirtualDescriptor<TModel, TValue> | ModelVirtualGetter<TModel, TValue>;
|
|
2182
|
+
create?: ModelVirtualDescriptor<TModel, TValue> | ModelVirtualGetter<TModel, TValue>;
|
|
2183
|
+
read?: ModelVirtualDescriptor<TModel, TValue> | ModelVirtualGetter<TModel, TValue>;
|
|
2184
|
+
update?: ModelVirtualDescriptor<TModel, TValue> | ModelVirtualGetter<TModel, TValue>;
|
|
2185
|
+
dependsOn?: Array<AccessRouterFieldKey<TModel>>;
|
|
2186
|
+
};
|
|
2187
|
+
/** Single virtual entry: bare getter, leaf descriptor, or per-access record. */
|
|
2188
|
+
type ModelVirtualLeaf<TModel = unknown, TValue = unknown> = ModelVirtualGetter<TModel, TValue> | ModelVirtualDescriptor<TModel, TValue> | ModelVirtualAccessRecord<TModel, TValue>;
|
|
2189
|
+
type UnwrapVirtualArray<T> = T extends readonly (infer U)[] ? U : T;
|
|
2190
|
+
/**
|
|
2191
|
+
* Persisted sub-scope model for an embedded container field (VIRT-01).
|
|
2192
|
+
* Unwraps arrays/nullable wrappers to the element/object type; unknown
|
|
2193
|
+
* fields fall back to a loose record.
|
|
2194
|
+
*/
|
|
2195
|
+
type VirtualSubModel<TModel, TField extends string> = TField extends keyof TModel ? UnwrapVirtualArray<NonNullable<TModel[TField]>> : Record<string, unknown>;
|
|
2196
|
+
type LooseVirtualLeaf = ModelVirtualGetter<Record<string, unknown>, unknown> | ModelVirtualDescriptor<Record<string, unknown>, unknown> | ModelVirtualAccessRecord<Record<string, unknown>, unknown>;
|
|
2197
|
+
type LooseVirtualSubEntry = LooseVirtualLeaf | {
|
|
2198
|
+
sub?: Record<string, LooseVirtualLeaf | {
|
|
2199
|
+
sub?: Record<string, LooseVirtualLeaf>;
|
|
2200
|
+
}>;
|
|
2201
|
+
};
|
|
2202
|
+
/**
|
|
2203
|
+
* Virtual definitions for a model scope (VIRT-01).
|
|
2204
|
+
* Root leaves come from `TVirtuals` (strict: unknown persisted getter
|
|
2205
|
+
* fields are type errors). Embedded containers mirror
|
|
2206
|
+
* `permissionSchema.<field>.sub`: any persisted field may host a `sub`
|
|
2207
|
+
* record whose leaves use the container's persisted sub-model view.
|
|
2208
|
+
* Untyped models keep an explicit loose record fallback.
|
|
2209
|
+
*
|
|
2210
|
+
* Selected virtual outputs are optional (`Partial`): authorization, absent
|
|
2211
|
+
* dependencies, `undefined`, and fail-closed errors can omit them. Virtual
|
|
2212
|
+
* names are never added to persisted `Filter`, sort/distinct inputs, or
|
|
2213
|
+
* client write types.
|
|
2214
|
+
*
|
|
2215
|
+
* @example End-to-end typed virtual (persisted `User` has no `fullAddress`):
|
|
2216
|
+
* ```ts
|
|
2217
|
+
* import acl from '@web-ts-toolkit/access-router';
|
|
2218
|
+
* import type { SelectedPublicOutput } from '@web-ts-toolkit/access-router/advanced';
|
|
2219
|
+
* interface User { name: string; address: string; }
|
|
2220
|
+
* interface UserVirtuals { fullAddress: string; }
|
|
2221
|
+
* const router = acl.createRouter<User, UserVirtuals>('User', {
|
|
2222
|
+
* permissionSchema: {
|
|
2223
|
+
* name: { read: true },
|
|
2224
|
+
* address: { read: 'canViewAddress' },
|
|
2225
|
+
* fullAddress: { read: 'canViewAddress' },
|
|
2226
|
+
* },
|
|
2227
|
+
* virtuals: {
|
|
2228
|
+
* fullAddress: {
|
|
2229
|
+
* dependsOn: ['address'],
|
|
2230
|
+
* read: async function (doc) {
|
|
2231
|
+
* if (doc.address === undefined) return undefined;
|
|
2232
|
+
* return `addr:${doc.address}`;
|
|
2233
|
+
* },
|
|
2234
|
+
* },
|
|
2235
|
+
* },
|
|
2236
|
+
* });
|
|
2237
|
+
* type Out = SelectedPublicOutput<User, ['name', 'fullAddress'], UserVirtuals>;
|
|
2238
|
+
* const out: Out = { name: 'Ada', fullAddress: 'addr:x' };
|
|
2239
|
+
* const omitted: Out = { name: 'Ada' }; // virtual is optional when denied/missing
|
|
2240
|
+
* void [router, out, omitted];
|
|
2241
|
+
* ```
|
|
2242
|
+
*/
|
|
2243
|
+
type ModelVirtuals<TModel = unknown, TVirtuals extends object = Record<never, never>> = IsUnknownVirtual<TModel> extends true ? Record<string, LooseVirtualSubEntry> : {
|
|
2244
|
+
[K in Extract<keyof TVirtuals, string>]?: ModelVirtualLeaf<TModel, TVirtuals[K]>;
|
|
2245
|
+
} & {
|
|
2246
|
+
[K in Extract<keyof TModel, string>]?: {
|
|
2247
|
+
sub?: Record<string, ModelVirtualLeaf<VirtualSubModel<TModel, K>, unknown> | {
|
|
2248
|
+
sub?: Record<string, LooseVirtualLeaf>;
|
|
2249
|
+
}>;
|
|
2250
|
+
};
|
|
2251
|
+
};
|
|
1600
2252
|
|
|
1601
2253
|
interface DefaultFindOneArgs<TModel = unknown> extends Omit<FindOneArgs<TModel>, 'overrides'> {
|
|
1602
2254
|
}
|
|
@@ -1609,7 +2261,33 @@ type NestedRequestSchema = {
|
|
|
1609
2261
|
default?: RequestSchema;
|
|
1610
2262
|
data?: RequestSchema;
|
|
1611
2263
|
};
|
|
1612
|
-
|
|
2264
|
+
/**
|
|
2265
|
+
* One field's subdocument route guards: exact variant → field base → field scalar
|
|
2266
|
+
* or closed object. Only an absent field rule falls back to top-level variant/base
|
|
2267
|
+
* guards. A defined object with no applicable key denies; nested `.default` is not
|
|
2268
|
+
* a fallback. Dynamic legacy operation names remain supported.
|
|
2269
|
+
*/
|
|
2270
|
+
interface SubOperationAccess {
|
|
2271
|
+
/** Base list guard for this field; fallback for both subdocument list variants. */
|
|
2272
|
+
list?: Validation;
|
|
2273
|
+
/** Base read guard for this field; fallback for both subdocument read variants. */
|
|
2274
|
+
read?: Validation;
|
|
2275
|
+
create?: Validation;
|
|
2276
|
+
update?: Validation;
|
|
2277
|
+
delete?: Validation;
|
|
2278
|
+
/** GET /:id/<field>; undefined inherits this field's `list` rule. */
|
|
2279
|
+
basicList?: Validation;
|
|
2280
|
+
/** POST /:id/<field>/__query; undefined inherits this field's `list` rule. */
|
|
2281
|
+
advancedList?: Validation;
|
|
2282
|
+
/** GET /:id/<field>/:subId (and HEAD); undefined inherits this field's `read` rule. */
|
|
2283
|
+
basicRead?: Validation;
|
|
2284
|
+
/** POST /:id/<field>/:subId/__query; undefined inherits this field's `read` rule. */
|
|
2285
|
+
advancedRead?: Validation;
|
|
2286
|
+
[access: string]: Validation | undefined;
|
|
2287
|
+
}
|
|
2288
|
+
/** Per-field subdocument route objects or scalar guards; field names remain dynamic. */
|
|
2289
|
+
type SubRouteGuardOptions = Record<string, Validation | SubOperationAccess>;
|
|
2290
|
+
type SubFieldPermissionOptions = Record<string, Validation | Record<string, Validation>>;
|
|
1613
2291
|
interface RequestSchemas {
|
|
1614
2292
|
create?: RequestSchema;
|
|
1615
2293
|
update?: RequestSchema;
|
|
@@ -1678,25 +2356,93 @@ interface GlobalOptions {
|
|
|
1678
2356
|
}
|
|
1679
2357
|
interface RootRouterOptions {
|
|
1680
2358
|
basePath: string;
|
|
2359
|
+
/** Guard for the batch endpoint. Entries use target base operations, never basic/advanced variant names. */
|
|
1681
2360
|
operationAccess?: Validation;
|
|
1682
2361
|
maxBatchEntries?: number;
|
|
1683
2362
|
maxOrderGroups?: number;
|
|
1684
2363
|
/** Whole root operations per order group; separate from requestComplexity.maxBulkConcurrency persistence admission. */
|
|
1685
2364
|
maxConcurrentOperations?: number;
|
|
1686
2365
|
}
|
|
1687
|
-
|
|
2366
|
+
/** Base-operation field grants used by PermissionSchema. Basic/advanced route keys do not grant fields. */
|
|
2367
|
+
interface FieldOperationAccess {
|
|
2368
|
+
new?: Validation;
|
|
2369
|
+
list?: Validation;
|
|
2370
|
+
create?: Validation;
|
|
2371
|
+
read?: Validation;
|
|
2372
|
+
update?: Validation;
|
|
2373
|
+
upsert?: Validation;
|
|
2374
|
+
delete?: Validation;
|
|
2375
|
+
distinct?: Validation;
|
|
2376
|
+
count?: Validation;
|
|
2377
|
+
subs?: Validation | SubFieldPermissionOptions;
|
|
2378
|
+
}
|
|
2379
|
+
/**
|
|
2380
|
+
* Live generated-route guards, separate from PermissionSchema field grants.
|
|
2381
|
+
* An exact basic/advanced rule replaces its base guard; only undefined inherits
|
|
2382
|
+
* the base operation's exact → `.default` → scalar-shorthand resolution. Selected
|
|
2383
|
+
* false/empty-array/false-returning guards are terminal, not ANDed with the base.
|
|
2384
|
+
* Model exact lookup includes runtime-default variants; data has no model defaults.
|
|
2385
|
+
* Denial is HTTP 401; routes remain registered in getEndpoints() and OpenAPI.
|
|
2386
|
+
* Field/row/hook, root-entry, related-target and mutation-response policies keep
|
|
2387
|
+
* their existing base operations. Paths below use the default route segments.
|
|
2388
|
+
*/
|
|
2389
|
+
interface OperationAccess extends Omit<FieldOperationAccess, 'subs'> {
|
|
2390
|
+
/** GET /new guard; unpaired, with no basic/advanced override. */
|
|
1688
2391
|
new?: Validation;
|
|
2392
|
+
/** Base list guard and fallback for both list routes; also governs model read-to-list retries. */
|
|
1689
2393
|
list?: Validation;
|
|
2394
|
+
/** Base create guard and fallback for both model create routes. */
|
|
1690
2395
|
create?: Validation;
|
|
2396
|
+
/** Base read guard and fallback for both read variants; advancedRead covers both POST read routes. */
|
|
1691
2397
|
read?: Validation;
|
|
2398
|
+
/** Base update guard and fallback for both model update routes. */
|
|
1692
2399
|
update?: Validation;
|
|
2400
|
+
/** Base upsert guard and fallback for both upserts; create/update route variants do not gate its branches. */
|
|
1693
2401
|
upsert?: Validation;
|
|
2402
|
+
/** DELETE /:id guard; unpaired, with no basic/advanced override. */
|
|
1694
2403
|
delete?: Validation;
|
|
2404
|
+
/** Base distinct guard and fallback for GET and POST /distinct/:field. */
|
|
1695
2405
|
distinct?: Validation;
|
|
2406
|
+
/** Base count guard and fallback for GET and POST /count. */
|
|
1696
2407
|
count?: Validation;
|
|
2408
|
+
/** Fallback for a base operation without an exact rule; explicit base/variant guards take precedence. */
|
|
2409
|
+
default?: Validation;
|
|
2410
|
+
/** GET / (and HEAD) override for model/data list; undefined inherits `list`. */
|
|
2411
|
+
basicList?: Validation;
|
|
2412
|
+
/** POST /__query override for model/data list; undefined inherits `list`. */
|
|
2413
|
+
advancedList?: Validation;
|
|
2414
|
+
/** GET /:id (and HEAD) override for model/data read; undefined inherits `read`. */
|
|
2415
|
+
basicRead?: Validation;
|
|
2416
|
+
/** POST /__query/:id and /__query/__filter override for model/data read; undefined inherits `read`. */
|
|
2417
|
+
advancedRead?: Validation;
|
|
2418
|
+
/** POST / model create override; undefined inherits `create`. */
|
|
2419
|
+
basicCreate?: Validation;
|
|
2420
|
+
/** POST /__mutation model create override; undefined inherits `create`. */
|
|
2421
|
+
advancedCreate?: Validation;
|
|
2422
|
+
/** PATCH /:id model update override; undefined inherits `update`. */
|
|
2423
|
+
basicUpdate?: Validation;
|
|
2424
|
+
/** PATCH /__mutation/:id model update override; undefined inherits `update`. */
|
|
2425
|
+
advancedUpdate?: Validation;
|
|
2426
|
+
/** PUT / model upsert override; undefined inherits `upsert`. */
|
|
2427
|
+
basicUpsert?: Validation;
|
|
2428
|
+
/** PUT /__mutation model upsert override; undefined inherits `upsert`. */
|
|
2429
|
+
advancedUpsert?: Validation;
|
|
2430
|
+
/** GET /count model count override; undefined inherits `count`. */
|
|
2431
|
+
basicCount?: Validation;
|
|
2432
|
+
/** POST /count model filtered-count override; undefined inherits `count`. */
|
|
2433
|
+
advancedCount?: Validation;
|
|
2434
|
+
/** GET /distinct/:field model distinct override; undefined inherits `distinct`. */
|
|
2435
|
+
basicDistinct?: Validation;
|
|
2436
|
+
/** POST /distinct/:field model filtered-distinct override; undefined inherits `distinct`. */
|
|
2437
|
+
advancedDistinct?: Validation;
|
|
2438
|
+
/**
|
|
2439
|
+
* Per-field scalar or closed-object guards, with list/read variants. Legacy scalar
|
|
2440
|
+
* `subs` is accepted but is not an umbrella guard for absent fields; they use top-level rules.
|
|
2441
|
+
*/
|
|
1697
2442
|
subs?: Validation | SubRouteGuardOptions;
|
|
1698
2443
|
}
|
|
1699
|
-
type PermissionRule = Validation |
|
|
2444
|
+
type PermissionRule = Validation | FieldOperationAccess;
|
|
2445
|
+
/** Field grants by base operation. Put basic/advanced route guards in operationAccess, not field-rule objects. */
|
|
1700
2446
|
type PermissionSchema<TField extends string = string> = Partial<Record<TField, PermissionRule>>;
|
|
1701
2447
|
interface DocPermissions {
|
|
1702
2448
|
list?: ModelDocPermissionsHook;
|
|
@@ -1714,8 +2460,28 @@ interface DefaultModelRouterOptions<TModel = unknown> {
|
|
|
1714
2460
|
parentPath?: string;
|
|
1715
2461
|
queryRouteSegment?: string;
|
|
1716
2462
|
mutationRouteSegment?: string;
|
|
2463
|
+
/**
|
|
2464
|
+
* Live model route guards: scalar Validation shorthand or base/variant rules.
|
|
2465
|
+
* `{ list: true, read: true, basicRead: false }` denies only GET /:id (and HEAD)
|
|
2466
|
+
* among top-level routes, retaining both advanced read POSTs subject to ordinary
|
|
2467
|
+
* field/row policy. Subdocument reads follow their field-rule precedence.
|
|
2468
|
+
* Exact runtime-default variants precede base fallback; option objects replace
|
|
2469
|
+
* top-level keys shallowly, rather than recursively merging operationAccess.
|
|
2470
|
+
*/
|
|
1717
2471
|
operationAccess?: Validation | OperationAccess;
|
|
1718
2472
|
modelPermissionPrefix?: string;
|
|
2473
|
+
/**
|
|
2474
|
+
* When `true`, list/read/update requests without an explicit `select` return
|
|
2475
|
+
* field-less rows (only `_id`, plus the permission field when requested)
|
|
2476
|
+
* instead of all allowed fields. Applies to plain and advanced PATCH
|
|
2477
|
+
* (returned updated record honors `select`, including `?select=` on plain
|
|
2478
|
+
* PATCH and body `select` on advanced PATCH). Only an omitted (`undefined`)
|
|
2479
|
+
* select triggers this; explicit selects — including backend `defaults` and
|
|
2480
|
+
* an explicitly empty string/array (which keep their current meaning) — are
|
|
2481
|
+
* unaffected. Counts, ordering, includes and permission enforcement are
|
|
2482
|
+
* unchanged. Defaults to `false`.
|
|
2483
|
+
*/
|
|
2484
|
+
requireExplicitSelect?: boolean;
|
|
1719
2485
|
/**
|
|
1720
2486
|
* Extra fields allowed in `sort` regardless of `list`/`read` field policy.
|
|
1721
2487
|
* Merged as a union with permission-derived allowed fields (`id`/`_id` are
|
|
@@ -1729,6 +2495,25 @@ interface DefaultModelRouterOptions<TModel = unknown> {
|
|
|
1729
2495
|
* Defaults to `false` (strict: disallowed sort returns `BadRequest`).
|
|
1730
2496
|
*/
|
|
1731
2497
|
stripDisallowedSort?: boolean;
|
|
2498
|
+
/**
|
|
2499
|
+
* When `true`, the document permissions field (`documentPermissionField`,
|
|
2500
|
+
* default `_permissions`) is removed from all outputs — including its empty
|
|
2501
|
+
* placeholder — and client `includePermissions`/`includeFieldPermissions`
|
|
2502
|
+
* input (query params, body/root-batch options and service-direct options)
|
|
2503
|
+
* as well as per-operation `defaults` are ignored (both resolve to `false`).
|
|
2504
|
+
* Output matches `includePermissions: false` with the permissions field
|
|
2505
|
+
* removed entirely. Enforcement still runs; only output metadata is
|
|
2506
|
+
* affected. Defaults to `false`.
|
|
2507
|
+
*/
|
|
2508
|
+
stripPermissionsField?: boolean;
|
|
2509
|
+
/**
|
|
2510
|
+
* When `true`, client `includeFieldPermissions` input (query params,
|
|
2511
|
+
* body/root-batch options and service-direct options) as well as
|
|
2512
|
+
* per-operation `defaults` are ignored and field maps (`_view`/`_edit`)
|
|
2513
|
+
* are never computed. `docPermissions`-hook keys still follow
|
|
2514
|
+
* `includePermissions`. Defaults to `false`.
|
|
2515
|
+
*/
|
|
2516
|
+
disableFieldPermissions?: boolean;
|
|
1732
2517
|
}
|
|
1733
2518
|
interface ExtendedDefaultModelRouterOptions<TModel = unknown> extends DefaultModelRouterOptions<TModel> {
|
|
1734
2519
|
'operationAccess.default'?: Validation;
|
|
@@ -1741,19 +2526,92 @@ interface ExtendedDefaultModelRouterOptions<TModel = unknown> extends DefaultMod
|
|
|
1741
2526
|
'operationAccess.create'?: Validation;
|
|
1742
2527
|
'operationAccess.distinct'?: Validation;
|
|
1743
2528
|
'operationAccess.count'?: Validation;
|
|
1744
|
-
'operationAccess.
|
|
1745
|
-
|
|
1746
|
-
|
|
2529
|
+
'operationAccess.basicList'?: Validation;
|
|
2530
|
+
'operationAccess.advancedList'?: Validation;
|
|
2531
|
+
'operationAccess.basicRead'?: Validation;
|
|
2532
|
+
'operationAccess.advancedRead'?: Validation;
|
|
2533
|
+
'operationAccess.basicCreate'?: Validation;
|
|
2534
|
+
'operationAccess.advancedCreate'?: Validation;
|
|
2535
|
+
'operationAccess.basicUpdate'?: Validation;
|
|
2536
|
+
'operationAccess.advancedUpdate'?: Validation;
|
|
2537
|
+
'operationAccess.basicUpsert'?: Validation;
|
|
2538
|
+
'operationAccess.advancedUpsert'?: Validation;
|
|
2539
|
+
'operationAccess.basicCount'?: Validation;
|
|
2540
|
+
'operationAccess.advancedCount'?: Validation;
|
|
2541
|
+
'operationAccess.basicDistinct'?: Validation;
|
|
2542
|
+
'operationAccess.advancedDistinct'?: Validation;
|
|
2543
|
+
'operationAccess.subs'?: Validation | SubRouteGuardOptions;
|
|
2544
|
+
}
|
|
2545
|
+
interface ModelRouterOptions<TModel = unknown, TVirtuals extends object = Record<never, never>> extends DefaultModelRouterOptions<TModel> {
|
|
1747
2546
|
modelName?: string;
|
|
1748
2547
|
basePath?: string;
|
|
1749
2548
|
/**
|
|
1750
2549
|
* Update grants define assignment boundaries: authorized leaves preserve omitted
|
|
1751
2550
|
* siblings; a whole-object/array grant permits replacement (no generic deep merge).
|
|
1752
2551
|
* Send nested JSON; literal dotted client keys are not update operators.
|
|
2552
|
+
* Basic/advanced keys are route guards in operationAccess, not field grants here.
|
|
2553
|
+
*/
|
|
2554
|
+
permissionSchema?: PermissionSchema<AccessRouterFieldKey<TModel> | Extract<keyof TVirtuals, string>>;
|
|
2555
|
+
/**
|
|
2556
|
+
* Package-level computed fields ("virtuals", VIRT-01).
|
|
2557
|
+
* Typed getter/descriptor/output generics per VIRT-00A D3: typed getters
|
|
2558
|
+
* use a partial read-only persisted view for their scope, never
|
|
2559
|
+
* `Record<string, unknown>` for a known model; untyped consumers keep an
|
|
2560
|
+
* explicit loose fallback. Per-access records use `default` +
|
|
2561
|
+
* `list`/`create`/`read`/`update` (NOT `delete`/`distinct`/`count`) and
|
|
2562
|
+
* recursive embedded `sub` scopes mirroring
|
|
2563
|
+
* `permissionSchema.<field>.sub`. Inapplicable registered names stay
|
|
2564
|
+
* virtual (never persisted). Mutable post-construction (not a build-time
|
|
2565
|
+
* key); replacement swaps the frozen snapshot so in-flight planning keeps
|
|
2566
|
+
* a coherent version.
|
|
2567
|
+
*
|
|
2568
|
+
* Getters run after `toObject()`/lean normalization (both lean and
|
|
2569
|
+
* hydrated results compute), after internal document-permissions
|
|
2570
|
+
* computation, and before `decorate`/`decorateAll`/tasks and final
|
|
2571
|
+
* trimming. `dependsOn` fields are fetched internally even when the caller
|
|
2572
|
+
* may not receive them, then stripped unless independently selected and
|
|
2573
|
+
* authorized. Virtuals are output-only: registered names (including
|
|
2574
|
+
* embedded `sub` leaves) are excluded from persisted projections, client
|
|
2575
|
+
* write admission, and database sort/filter/distinct handling.
|
|
2576
|
+
*
|
|
2577
|
+
* @example Shortest typed happy-path (persisted model lacks `fullAddress`):
|
|
2578
|
+
* ```ts
|
|
2579
|
+
* import acl from '@web-ts-toolkit/access-router';
|
|
2580
|
+
* interface User { name: string; address: string; }
|
|
2581
|
+
* interface UserVirtuals { fullAddress: string; }
|
|
2582
|
+
* const router = acl.createRouter<User, UserVirtuals>('User', {
|
|
2583
|
+
* permissionSchema: {
|
|
2584
|
+
* name: { read: true },
|
|
2585
|
+
* address: { read: 'canViewAddress' },
|
|
2586
|
+
* fullAddress: { read: 'canViewAddress' },
|
|
2587
|
+
* },
|
|
2588
|
+
* virtuals: {
|
|
2589
|
+
* fullAddress: {
|
|
2590
|
+
* dependsOn: ['address'],
|
|
2591
|
+
* read: async function (doc) {
|
|
2592
|
+
* if (doc.address === undefined) return undefined;
|
|
2593
|
+
* return `addr:${doc.address}`;
|
|
2594
|
+
* },
|
|
2595
|
+
* },
|
|
2596
|
+
* },
|
|
2597
|
+
* });
|
|
2598
|
+
* // Selected output: { name: string; fullAddress?: string } — virtual is optional.
|
|
2599
|
+
* void router;
|
|
2600
|
+
* ```
|
|
1753
2601
|
*/
|
|
1754
|
-
|
|
2602
|
+
virtuals?: ModelVirtuals<TModel, TVirtuals>;
|
|
1755
2603
|
alwaysSelectFields?: string[];
|
|
1756
2604
|
docPermissions?: DocPermissions | ModelDocPermissionsHook;
|
|
2605
|
+
/**
|
|
2606
|
+
* Allowlist of `docPermissions`-hook keys exposed in API responses.
|
|
2607
|
+
* Enforcement always uses the full hook map; only serialization is reduced,
|
|
2608
|
+
* after all grant computations and decorate hooks have run. `_view`/`_edit`
|
|
2609
|
+
* are always kept. `undefined` (default) exposes everything (current
|
|
2610
|
+
* behavior); any array (including `[]`) exposes `_view`/`_edit` plus the
|
|
2611
|
+
* listed keys. Note `guard()` conditions on stripped doc-level keys fail
|
|
2612
|
+
* closed — allowlist any key a custom route guard evaluates.
|
|
2613
|
+
*/
|
|
2614
|
+
exposedDocPermissionKeys?: string[];
|
|
1757
2615
|
baseFilter?: ModelBaseFilterHook | Record<string, ModelBaseFilterHook>;
|
|
1758
2616
|
overrideFilter?: ModelOverrideFilterHook | Record<string, ModelOverrideFilterHook>;
|
|
1759
2617
|
decorate?: ModelHook<TModel> | Record<string, ModelHook<TModel>>;
|
|
@@ -1793,9 +2651,16 @@ interface DataRouterOptions<TData = unknown> {
|
|
|
1793
2651
|
resolveIdFilter?: DataIdentifierHook<TData>;
|
|
1794
2652
|
parentPath?: string;
|
|
1795
2653
|
queryRouteSegment?: string;
|
|
2654
|
+
/**
|
|
2655
|
+
* Live data-route guards: list/read plus basicList/advancedList/basicRead/advancedRead.
|
|
2656
|
+
* Only undefined variants inherit their base; false denies with HTTP 401 while
|
|
2657
|
+
* routes remain registered. Data never inherits model defaults. The broader
|
|
2658
|
+
* legacy operation-rule object and scalar Validation shorthand remain accepted.
|
|
2659
|
+
*/
|
|
1796
2660
|
operationAccess?: Validation | OperationAccess;
|
|
1797
2661
|
dataName?: string;
|
|
1798
2662
|
basePath?: string;
|
|
2663
|
+
/** Base-operation field grants, independent of operationAccess's basic/advanced route guards. */
|
|
1799
2664
|
permissionSchema?: PermissionSchema<AccessRouterFieldKey<TData>>;
|
|
1800
2665
|
baseFilter?: DataBaseFilterHook | Record<string, DataBaseFilterHook>;
|
|
1801
2666
|
overrideFilter?: DataOverrideFilterHook | Record<string, DataOverrideFilterHook>;
|
|
@@ -1803,7 +2668,7 @@ interface DataRouterOptions<TData = unknown> {
|
|
|
1803
2668
|
decorateAll?: DataListHook<TData> | Record<string, DataListHook<TData>>;
|
|
1804
2669
|
requestSchemas?: DataRequestSchemas;
|
|
1805
2670
|
}
|
|
1806
|
-
interface ExtendedModelRouterOptions<TModel = unknown
|
|
2671
|
+
interface ExtendedModelRouterOptions<TModel = unknown, TVirtuals extends object = Record<never, never>> extends ModelRouterOptions<TModel, TVirtuals>, ExtendedDefaultModelRouterOptions<TModel> {
|
|
1807
2672
|
'alwaysSelectFields.default'?: string[];
|
|
1808
2673
|
'alwaysSelectFields.list'?: string[];
|
|
1809
2674
|
'alwaysSelectFields.create'?: string[];
|
|
@@ -1885,8 +2750,29 @@ interface ExtendedModelRouterOptions<TModel = unknown> extends ModelRouterOption
|
|
|
1885
2750
|
'defaults.publicReadOptions'?: PublicReadOptions;
|
|
1886
2751
|
'defaults.publicUpdateArgs'?: PublicUpdateArgs;
|
|
1887
2752
|
'defaults.publicUpdateOptions'?: PublicUpdateOptions;
|
|
2753
|
+
/**
|
|
2754
|
+
* Virtual dotted keys (VIRT-01): `virtuals`, `virtuals.<name>`,
|
|
2755
|
+
* `virtuals.<name>.<access>`, `virtuals.<field>.sub...` (recursive).
|
|
2756
|
+
* The template fallback preserves typed getter doc views for known
|
|
2757
|
+
* models so unknown persisted getter fields fail (including dotted
|
|
2758
|
+
* setters); embedded `sub` records stay structurally loose here and are
|
|
2759
|
+
* fully validated at runtime against the receiving Mongoose schema +
|
|
2760
|
+
* child scopes.
|
|
2761
|
+
*/
|
|
2762
|
+
[key: `virtuals.${string}`]: ModelVirtuals<TModel, TVirtuals>[Extract<keyof TVirtuals, string>] | ModelVirtualDescriptor<TModel, unknown> | ModelVirtualGetter<TModel, unknown> | {
|
|
2763
|
+
sub?: Record<string, unknown>;
|
|
2764
|
+
} | Record<string, ModelVirtualDescriptor<Record<string, unknown>, unknown> | ModelVirtualGetter<Record<string, unknown>, unknown> | {
|
|
2765
|
+
sub?: unknown;
|
|
2766
|
+
}>;
|
|
1888
2767
|
}
|
|
1889
2768
|
interface ExtendedDataRouterOptions<TData = unknown> extends DataRouterOptions<TData> {
|
|
2769
|
+
'operationAccess.default'?: Validation;
|
|
2770
|
+
'operationAccess.list'?: Validation;
|
|
2771
|
+
'operationAccess.read'?: Validation;
|
|
2772
|
+
'operationAccess.basicList'?: Validation;
|
|
2773
|
+
'operationAccess.advancedList'?: Validation;
|
|
2774
|
+
'operationAccess.basicRead'?: Validation;
|
|
2775
|
+
'operationAccess.advancedRead'?: Validation;
|
|
1890
2776
|
'requestSchemas.advancedList'?: RequestSchema;
|
|
1891
2777
|
'requestSchemas.advancedReadFilter'?: RequestSchema;
|
|
1892
2778
|
'requestSchemas.advancedRead'?: RequestSchema;
|
|
@@ -1993,4 +2879,4 @@ declare function fromIoTs<TValue = unknown>(decoder: IoTsDecoderLike<TValue>): R
|
|
|
1993
2879
|
declare function fromSuperstruct<TStruct, TOutput = unknown>(struct: TStruct, validate: SuperstructValidateLike): RequestSchemaValidator<TOutput>;
|
|
1994
2880
|
declare function fromVine<TValue = unknown>(validator: VineValidatorLike<TValue>): RequestSchemaValidator<TValue>;
|
|
1995
2881
|
|
|
1996
|
-
export { type DistinctArgs as $, type AccessRouterBaseRequest as A, type BaseFilterAccess as B, type Change as C, type CreateArgs as D, type CreateOptions as E, type CreateQueryInput as F, CustomHeaders as G, DATA_MIDDLEWARE as H, type DataBaseFilterHook as I, type DataFilter as J, type DataFindArgs as K, type DataFindOneArgs as L, type DataFindOneOptions as M, type DataFindOptions as N, type DataHook as O, type DataHookContext as P, type DataIdentifierHook as Q, type DataListHook as R, type DataOverrideFilterHook as S, type DataRequest as T, type DataRequestSchemas as U, type DataRouterOptions as V, type DecorateAccess as W, type DecorateAllAccess as X, type DeepFieldPath as Y, type DefaultModelRouterOptions as Z, type Defaults as _, type AccessRouterFieldKey as a, type
|
|
2882
|
+
export { type DistinctArgs as $, type AccessRouterBaseRequest as A, type BaseFilterAccess as B, type Change as C, type CreateArgs as D, type CreateOptions as E, type CreateQueryInput as F, CustomHeaders as G, DATA_MIDDLEWARE as H, type DataBaseFilterHook as I, type DataFilter as J, type DataFindArgs as K, type DataFindOneArgs as L, type DataFindOneOptions as M, type DataFindOptions as N, type DataHook as O, type DataHookContext as P, type DataIdentifierHook as Q, type DataListHook as R, type DataOverrideFilterHook as S, type DataRequest as T, type DataRequestSchemas as U, type DataRouterOptions as V, type DecorateAccess as W, type DecorateAllAccess as X, type DeepFieldPath as Y, type DefaultModelRouterOptions as Z, type Defaults as _, type AccessRouterFieldKey as a, type Populate as a$, type DistinctBody as a0, type DocPermissionsAccess as a1, type ErrorResult as a2, type ExistsOptions as a3, type ExtendedDataRouterOptions as a4, type ExtendedDefaultModelRouterOptions as a5, type ExtendedModelRouterOptions as a6, type FieldOperationAccess as a7, type Filter as a8, FilterOperator as a9, type ModelDocPermissionsHook as aA, type ModelDocument as aB, type ModelDocumentHook as aC, type ModelHook as aD, type ModelHookContext as aE, type ModelIdentifierHook as aF, type ModelListHook as aG, type ModelOverrideFilterHook as aH, type ModelRequest as aI, type ModelRouterOptions as aJ, type ModelValidateHook as aK, type ModelVirtualAccess as aL, type ModelVirtualAccessRecord as aM, type ModelVirtualContext as aN, type ModelVirtualDescriptor as aO, type ModelVirtualDoc as aP, type ModelVirtualGetter as aQ, type ModelVirtualLeaf as aR, type ModelVirtualRecordAccess as aS, type ModelVirtuals as aT, type OperationAccess as aU, PERMISSIONS as aV, PERMISSION_KEYS as aW, type PairedRouteAccess as aX, type ParentRef as aY, type PathValue as aZ, type PermissionSchema as a_, type FindAccess as aa, type FindArgs as ab, type FindByIdArgs as ac, type FindByIdOptions as ad, type FindOneArgs as ae, type FindOneOptions as af, type FindOptions as ag, type GlobalOptions as ah, type GlobalPermissionValue as ai, type GuardHook as aj, type IdentifierHook as ak, type Include as al, type IoTsContextEntryLike as am, type IoTsDecodeErrorLike as an, type IoTsDecoderLike as ao, type JoiSchemaLike as ap, type JoiValidationErrorDetailLike as aq, type KeyValueProjection as ar, type LegacyInclude as as, type ListQueryInput as at, type ListResult as au, MIDDLEWARE as av, type MaybePromise as aw, type ModelBaseFilterHook as ax, type ModelChangeHook as ay, type ModelDeleteHook as az, type AccessRouterLogger as b, type SingleResult as b$, type PopulateAccess as b0, type PrepareAccess as b1, type Projection as b2, type PublicCreateArgs as b3, type PublicCreateOptions as b4, type PublicErrorResult as b5, type PublicListArgs as b6, type PublicListOptions as b7, type PublicListResult as b8, type PublicOutput as b9, type RootModelDistinctQueryEntry as bA, type RootModelListQueryEntry as bB, type RootModelNewQueryEntry as bC, type RootModelOperation as bD, type RootModelReadByFilterQueryEntry as bE, type RootModelReadByIdQueryEntry as bF, type RootModelSubBulkUpdateQueryEntry as bG, type RootModelSubCreateQueryEntry as bH, type RootModelSubDeleteQueryEntry as bI, type RootModelSubListQueryEntry as bJ, type RootModelSubReadQueryEntry as bK, type RootModelSubUpdateQueryEntry as bL, type RootModelUpdateQueryEntry as bM, type RootModelUpsertQueryEntry as bN, type RootOperationResult as bO, type RootQueryEntry as bP, type RootRouterOptions as bQ, type RootSubOperation as bR, type RootTarget as bS, type RouteBaseAccess as bT, type RouteGuardAccess as bU, type RouteVariant as bV, type RouteVariantAccess as bW, type SelectAccess as bX, type SelectedPopulatedPublicOutput as bY, type SelectedPublicOutput as bZ, type ServiceResult as b_, type PublicReadArgs as ba, type PublicReadOptions as bb, type PublicServiceResult as bc, type PublicSingleResult as bd, type PublicUpdateArgs as be, type PublicUpdateOptions as bf, type PublicUpsertArgs as bg, type PublicUpsertOptions as bh, type ReadQueryInput as bi, type Request as bj, type RequestSchemaAdapter as bk, type RequestSchemaFailure as bl, type RequestSchemaIssue as bm, type RequestSchemaLike as bn, type RequestSchemaOptions as bo, type RequestSchemaResult as bp, type RequestSchemaSuccess as bq, type RequestSchemaValidator as br, type RequestSchemas as bs, type RootDataListQueryEntry as bt, type RootDataOperation as bu, type RootDataReadByFilterQueryEntry as bv, type RootDataReadByIdQueryEntry as bw, type RootModelCountQueryEntry as bx, type RootModelCreateQueryEntry as by, type RootModelDeleteQueryEntry as bz, type AccessRouterRequest as c, fromValibot as c$, type Sort as c0, type SortOrder as c1, type StandardSchemaFailure as c2, type StandardSchemaInferOutput as c3, type StandardSchemaIssue as c4, type StandardSchemaPathSegment as c5, type StandardSchemaResult as c6, type StandardSchemaSuccess as c7, type StandardSchemaV1 as c8, StatusCodes as c9, type UpdateOneOptions as cA, type UpdateQueryInput as cB, type UpsertArgs as cC, type UpsertOptions as cD, type UpsertQueryInput as cE, type ValibotIssueLike as cF, type ValibotPathItemLike as cG, type ValibotSafeParseLike as cH, type ValibotSafeParseResult as cI, type ValidateAccess as cJ, type ValidateRule as cK, type Validation as cL, type ValidationError as cM, type VineValidationErrorLike as cN, type VineValidationMessageLike as cO, type VineValidatorLike as cP, type VirtualSubModel as cQ, type WithVirtuals as cR, type YupSchemaLike as cS, type YupValidationErrorLike as cT, defineRequestSchema as cU, fromAjv as cV, fromArkType as cW, fromIoTs as cX, fromJoi as cY, fromStandardSchema as cZ, fromSuperstruct as c_, type SubListBody as ca, type SubOperationAccess as cb, type SubPopulate as cc, type SubQueryEntry as cd, type SubReadBody as ce, type SubRouteGuardOptions as cf, type SubdocumentBulkRecord as cg, type SubdocumentBulkUpdateInput as ch, type SubdocumentCreateInput as ci, type SubdocumentCreateOptions as cj, type SubdocumentId as ck, type SubdocumentListOptions as cl, type SubdocumentName as cm, type SubdocumentParentArgs as cn, type SubdocumentParentOptions as co, type SubdocumentReadOptions as cp, type SubdocumentRecord as cq, type SuperstructFailureLike as cr, type SuperstructValidateLike as cs, type Task as ct, type TaskType as cu, type TransformAccess as cv, type TypedFilter as cw, type UpdateByIdArgs as cx, type UpdateByIdOptions as cy, type UpdateOneArgs as cz, type AccessRouterRequestExtensions as d, fromVine as d0, fromYup as d1, fromZod as d2, parseBody as d3, parseBodyWithSchema as d4, parseNestedBodyWithSchema as d5, parsePathParam as d6, parseQuery as d7, AccessRuntime as d8, DataService as d9, Service as da, PublicService as db, type OpenApiRouterOptions as dc, type OpenApiRegistryOptions as dd, type OpenApiRouteDescriptor as de, type OpenApiDocumentOptions as df, type OpenApiParameter as dg, type OpenApiSchema as dh, type OpenApiResponses as di, type AccessRouterPermissionMap as dj, type AccessRouterPermissions as dk, type OpenApiMethod as dl, type Permissions as dm, defaultRuntime as dn, type AdvancedCreateBody as e, type AdvancedListBody as f, type AdvancedReadBody as g, type AdvancedReadFilterBody as h, type AdvancedUpdateBody as i, type AdvancedUpsertBody as j, type AfterPersistAccess as k, type AjvErrorObjectLike as l, type AjvValidationErrorLike as m, type AjvValidatorLike as n, type ArkTypeErrorsLike as o, type ArkTypeLike as p, type ArkTypeProblemLike as q, Codes as r, type CorrelatedCountInclude as s, type CorrelatedFilter as t, type CorrelatedInclude as u, type CorrelatedIncludeArgs as v, type CorrelatedListInclude as w, type CorrelatedReadByFilterInclude as x, type CorrelatedReadByIdInclude as y, type CountBody as z };
|