@web-ts-toolkit/access-router 0.43.0 → 0.45.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 +97 -2
- package/advanced.d.mts +655 -19
- package/advanced.d.ts +655 -19
- package/advanced.js +478 -29
- package/advanced.mjs +478 -29
- package/index.d.mts +18 -10
- package/index.d.ts +18 -10
- package/index.js +1699 -262
- package/index.mjs +1576 -137
- package/llms.txt +19 -0
- package/package.json +18 -6
- package/{parsers-PMWujHUV.d.mts → parsers-DppKW-Fd.d.mts} +146 -26
- package/{parsers-PMWujHUV.d.ts → parsers-DppKW-Fd.d.ts} +146 -26
- package/processors.d.mts +151 -1
- package/processors.d.ts +151 -1
- package/processors.js +175 -8
- package/processors.mjs +170 -8
package/README.md
CHANGED
|
@@ -104,6 +104,17 @@ snapshots. Mutating the original options object, a fetched options snapshot, or
|
|
|
104
104
|
not change live runtime policy or served in-memory records. Replace configured data through `router.data(next)`
|
|
105
105
|
or `setDataOption(name, 'data', next)`.
|
|
106
106
|
|
|
107
|
+
ACL filter hooks may return `false` to deny a query. That denial is terminal through identifier, override, and
|
|
108
|
+
base-filter composition. `overrideFilter` is a trusted hook that may replace an ordinary client or identifier
|
|
109
|
+
filter before the base filter is applied, but it is not called for an existing `false` filter and cannot revive
|
|
110
|
+
that denial. Returning `false` from `overrideFilter` also denies the query. Empty or absent filters retain their
|
|
111
|
+
normal unrestricted meaning unless a base filter restricts them.
|
|
112
|
+
|
|
113
|
+
Advanced mutation validators drive dispatch: nested `data` validators run first, then the whole-body (`default`)
|
|
114
|
+
validator sees that transformed envelope, and the service persists only the final parsed `data`, `select`,
|
|
115
|
+
`populate`, `tasks`, and allowed `options`. Missing body options fall back to `returning_all`/`include_permissions`
|
|
116
|
+
query params; only `includePermissions`/`populateAccess` (plus `returningAll` for update/upsert) are forwarded.
|
|
117
|
+
|
|
107
118
|
## Main Exports
|
|
108
119
|
|
|
109
120
|
Root entrypoint (`@web-ts-toolkit/access-router`):
|
|
@@ -115,12 +126,14 @@ Root entrypoint (`@web-ts-toolkit/access-router`):
|
|
|
115
126
|
- `guard(...)` and `GuardModelCondition`, `GuardModelConditionID` types
|
|
116
127
|
- `combineRoutes(...)` and `createOpenApiRouter(runtime, options)` (note: prefer `acl.createOpenApiRouter(options)` for the default runtime)
|
|
117
128
|
- validation adapters: `defineRequestSchema(...)`, `fromZod(...)`, `fromYup(...)`, `fromJoi(...)`, `fromAjv(...)`, `fromStandardSchema(...)`, `fromValibot(...)`, `fromArkType(...)`, `fromIoTs(...)`, `fromSuperstruct(...)`, `fromVine(...)`
|
|
129
|
+
- `permissionsPlugin` (src/plugins.ts) — Mongoose schema plugin used as `schema.plugin(permissionsPlugin, { modelName })`
|
|
130
|
+
- logger helpers: `redactFilter`, `redactPayload`, `safeStringify`, `isLevelEnabled` + `OpLogContext` type
|
|
118
131
|
- option helpers: `setGlobalOptions`, `setGlobalOption`, `getGlobalOptions`, `getGlobalOption`, `setModelOptions`, `setModelOption`, `getModelOptions`, `getModelOption`, `getModelNames`, `getModelJsonSchema`, `setDefaultModelOptions`, `setDefaultModelOption`, `getDefaultModelOptions`, `getDefaultModelOption`
|
|
119
132
|
|
|
120
133
|
Subpath entrypoints:
|
|
121
134
|
|
|
122
|
-
- `@web-ts-toolkit/access-router/advanced` — low-level runtime context, symbols (`MIDDLEWARE`, `PERMISSIONS`, ...), enums (`Codes`, `StatusCodes`), internals (`parseBody`, `parseQuery`, request schemas). Does NOT export `acl`, `defaultRuntime`, or router-creation helpers.
|
|
123
|
-
- `@web-ts-toolkit/access-router/processors` — `copyAndDepopulate` and
|
|
135
|
+
- `@web-ts-toolkit/access-router/advanced` — low-level runtime context, symbols (`MIDDLEWARE`, `DATA_MIDDLEWARE`, `PERMISSIONS`, ...), enums (`Codes`, `StatusCodes`), internals (`parseBody`, `parseQuery`, `parseBodyWithSchema`, request schemas). Does NOT export `acl`, `defaultRuntime`, or router-creation helpers.
|
|
136
|
+
- `@web-ts-toolkit/access-router/processors` — `copyAndDepopulate`, `copyPaths`, `movePaths`, `sliceArrays`, `countPaths`, `maskPaths` and processor option types (`ProcessCopy`, `CopyAndDepopulateOptions`, `CopyAndDepopulateOutput`, `ProcessorOptions`, `SliceProcessorOptions`, `ProcessSlice`, `ProcessCount`, `ProcessMask`, `ProcessorOutput`) for transforming populated documents.
|
|
124
137
|
|
|
125
138
|
## Default runtime vs. isolated runtime
|
|
126
139
|
|
|
@@ -152,6 +165,15 @@ to `runtime.createRouter(model, options)` or call `runtime.registerModelInstance
|
|
|
152
165
|
string-name router. The default `acl` runtime retains string-name compatibility with `mongoose.model(name, schema)` and
|
|
153
166
|
adopts that exact global model instance into its registry on first lookup.
|
|
154
167
|
|
|
168
|
+
Cross-runtime composition on a shared request keeps independent runtime-owned state. When one request passes through
|
|
169
|
+
middleware from runtime A and then runtime B, B initializes its own core, resolves its own `globalPermissions`, and
|
|
170
|
+
uses its own base-filter cache; it never reuses A's permissions or cached filters, even when both runtimes use the
|
|
171
|
+
same `requestPermissionField` name. Repeating the same runtime's middleware on one request reuses that runtime's state
|
|
172
|
+
without rerunning its resolver. Values already present in `requestPermissionField` before any runtime middleware runs
|
|
173
|
+
are treated as application-supplied and preserved. Migration: if you relied on B silently inheriting A's resolved
|
|
174
|
+
permissions on a shared request, set those permissions explicitly before the chain or configure B's `globalPermissions`
|
|
175
|
+
to resolve them.
|
|
176
|
+
|
|
155
177
|
## createRouter overloads
|
|
156
178
|
|
|
157
179
|
`createRouter(modelName, options)` — on the default `acl` runtime, accept the Mongoose model name registered with `mongoose.model(name, schema)`. On isolated runtimes, the name must already be registered with that runtime.
|
|
@@ -180,6 +202,79 @@ Advanced read/list requests can attach related model data with `include` entries
|
|
|
180
202
|
`op: 'count'` returns exact authorized counts and ignores include pagination fields such as `args.limit`.
|
|
181
203
|
`op: 'list'` materializes authorized related rows through the target model's normal list path, so target `limit`, `page`, `pageSize`, and `listHardLimit` bounds still apply to included rows.
|
|
182
204
|
|
|
205
|
+
## Correlated Includes
|
|
206
|
+
|
|
207
|
+
Besides the legacy `localField`/`foreignField` joins, `include` entries accept
|
|
208
|
+
a correlated variant (`mode: 'correlated'`) that runs a target query **per
|
|
209
|
+
parent document** using values from that parent. References are explicit
|
|
210
|
+
structural markers — `{ "$parent": "<field>" }`, dotted paths allowed —
|
|
211
|
+
recognized only in filter _value_ positions (bare values, field operators
|
|
212
|
+
such as `$eq`/`$in`, `$in` array elements, `$and`/`$or`/`$nor` clauses,
|
|
213
|
+
`$elemMatch` subtrees) and as the whole `id` of identifier reads. Plain
|
|
214
|
+
strings are never references: `'$special'` keeps its literal query meaning.
|
|
215
|
+
Match a literal `{ "$parent": "x" }` object with
|
|
216
|
+
`{ "$escape": { "$parent": "x" } }` (use the `$eq`-wrapped form when the
|
|
217
|
+
literal sits in a bare field position).
|
|
218
|
+
|
|
219
|
+
Wire shapes (the originating method fixes `op`; there is no override):
|
|
220
|
+
|
|
221
|
+
```json
|
|
222
|
+
{ "mode": "correlated", "model": "Org", "op": "read", "path": "org", "id": { "$parent": "orgId" } }
|
|
223
|
+
{ "mode": "correlated", "model": "Org", "op": "read", "path": "org",
|
|
224
|
+
"filter": { "_id": { "$parent": "orgId" }, "active": true }, "args": { "select": ["name"] } }
|
|
225
|
+
{ "mode": "correlated", "model": "Post", "op": "list", "path": "posts",
|
|
226
|
+
"filter": { "authorId": { "$parent": "_id" }, "title": "$special" },
|
|
227
|
+
"args": { "select": ["title"], "sort": { "createdAt": -1 }, "limit": 5 } }
|
|
228
|
+
{ "mode": "correlated", "model": "Post", "op": "count", "path": "postCount",
|
|
229
|
+
"filter": { "authorId": { "$parent": "_id" } } }
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Semantics:
|
|
233
|
+
|
|
234
|
+
- References resolve against the **immediate parent** document as fetched
|
|
235
|
+
from persistence (including internally loaded reference fields), before
|
|
236
|
+
include attachment, `decorate` hooks, and task mutation. Nested
|
|
237
|
+
`args.include` entries bind to the target doc of the enclosing include.
|
|
238
|
+
Sibling include output never feeds references.
|
|
239
|
+
- A missing (`undefined`) or `null` reference short-circuits that parent's
|
|
240
|
+
execution to the no-match shape with no target query: `read` attaches
|
|
241
|
+
`null`, `list` attaches `[]`, `count` attaches `0`. Target misses produce
|
|
242
|
+
the same shapes. The output path is always set.
|
|
243
|
+
- Identifier reads use the target's configured identifier behavior
|
|
244
|
+
(`resolveIdentifierFilter`/`genIDFilter`, custom `resolveIdFilter`
|
|
245
|
+
honored) with no read-to-list fallback. Counts use explicit count access
|
|
246
|
+
with count semantics, independent of any list limit. List pagination
|
|
247
|
+
(`skip`/`limit`/`page`/`pageSize`) applies **per parent** — contrast with
|
|
248
|
+
legacy batched list includes, whose pagination applies across the parent
|
|
249
|
+
set. Substituted parent values are data: objects match literally (never
|
|
250
|
+
become operators) and arrays are never flattened.
|
|
251
|
+
- Each correlated entry counts toward `maxIncludeCount`; expanded filters
|
|
252
|
+
are revalidated against `maxNodes`/`maxDepth`/`maxInValues`/
|
|
253
|
+
`maxLogicalClauses`. Total inner executions per request are bounded by
|
|
254
|
+
`maxCorrelatedQueries` (default `100`) and nesting by
|
|
255
|
+
`maxCorrelatedDepth` (default `5`); exceeding any bound fails the whole
|
|
256
|
+
request. Fan-out shares the request-scoped scheduler (no per-parent
|
|
257
|
+
reset). Target authorization denials fail the whole request with zero
|
|
258
|
+
target persistence queries; target runtime errors fail the whole request.
|
|
259
|
+
`path` must be a non-empty valid field path (not `_id`, not `$`-prefixed);
|
|
260
|
+
duplicates in one include array are `BadRequest`; collisions with parent
|
|
261
|
+
fields overwrite (legacy `setDocValue` parity).
|
|
262
|
+
- Referenced parent fields are added to the parent DB select like legacy
|
|
263
|
+
`localField`s, then trimmed unless allowed by field policy and selected.
|
|
264
|
+
A policy-forbidden reference still resolves — only its query effects are
|
|
265
|
+
visible, never the value.
|
|
266
|
+
- Direct count parents carry no `include`; correlated _count_ includes
|
|
267
|
+
attach to read/list parents only. Correlated entries are accepted in both
|
|
268
|
+
direct (`model-router.ts`) and root (`root-router.ts`) validators with
|
|
269
|
+
identical verdicts; malformed correlated input is a controlled
|
|
270
|
+
`BadRequest`, never a silent drop. Legacy entries are unchanged, and
|
|
271
|
+
legacy-shaped entries carrying `$parent` markers are rejected.
|
|
272
|
+
|
|
273
|
+
The TypeScript wire types (`ParentRef`, `CorrelatedInclude`, `Include`) are
|
|
274
|
+
reachable without deep imports via `@web-ts-toolkit/access-router/advanced`
|
|
275
|
+
(which re-exports `./interfaces`). See the website `services`,
|
|
276
|
+
`configuration`, `validation`, and `openapi` pages for the full contract.
|
|
277
|
+
|
|
183
278
|
## Import styles
|
|
184
279
|
|
|
185
280
|
The package ships both a default export and named exports:
|