@opensaas/stack-core 0.39.2 → 0.40.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +150 -0
- package/CLAUDE.md +51 -0
- package/dist/access/access-filter.d.ts +83 -10
- package/dist/access/access-filter.d.ts.map +1 -1
- package/dist/access/access-filter.js +139 -18
- package/dist/access/access-filter.js.map +1 -1
- package/dist/access/access-filter.test.js +224 -12
- package/dist/access/access-filter.test.js.map +1 -1
- package/dist/access/engine.d.ts +43 -1
- package/dist/access/engine.d.ts.map +1 -1
- package/dist/access/engine.js +58 -0
- package/dist/access/engine.js.map +1 -1
- package/dist/access/engine.test.d.ts +2 -0
- package/dist/access/engine.test.d.ts.map +1 -0
- package/dist/access/engine.test.js +56 -0
- package/dist/access/engine.test.js.map +1 -0
- package/dist/access/errors.d.ts +28 -0
- package/dist/access/errors.d.ts.map +1 -1
- package/dist/access/errors.js +38 -2
- package/dist/access/errors.js.map +1 -1
- package/dist/access/field-access.d.ts +12 -0
- package/dist/access/field-access.d.ts.map +1 -1
- package/dist/access/field-access.js +22 -2
- package/dist/access/field-access.js.map +1 -1
- package/dist/access/field-access.test.js +66 -0
- package/dist/access/field-access.test.js.map +1 -1
- package/dist/access/field-visibility.d.ts +2 -1
- package/dist/access/field-visibility.d.ts.map +1 -1
- package/dist/access/field-visibility.js +45 -3
- package/dist/access/field-visibility.js.map +1 -1
- package/dist/access/index.d.ts +5 -2
- package/dist/access/index.d.ts.map +1 -1
- package/dist/access/index.js +4 -2
- package/dist/access/index.js.map +1 -1
- package/dist/config/types.d.ts +76 -10
- package/dist/config/types.d.ts.map +1 -1
- package/dist/context/index.d.ts +19 -1
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +61 -17
- package/dist/context/index.js.map +1 -1
- package/dist/context/nested-operations.d.ts.map +1 -1
- package/dist/context/nested-operations.js +54 -13
- package/dist/context/nested-operations.js.map +1 -1
- package/dist/context/write-pipeline.d.ts +3 -2
- package/dist/context/write-pipeline.d.ts.map +1 -1
- package/dist/context/write-pipeline.js +8 -5
- package/dist/context/write-pipeline.js.map +1 -1
- package/dist/fields/index.d.ts +13 -1
- package/dist/fields/index.d.ts.map +1 -1
- package/dist/fields/index.js +50 -5
- package/dist/fields/index.js.map +1 -1
- package/dist/fields/relationship-foreign-key.test.d.ts +2 -0
- package/dist/fields/relationship-foreign-key.test.d.ts.map +1 -0
- package/dist/fields/relationship-foreign-key.test.js +18 -0
- package/dist/fields/relationship-foreign-key.test.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/case-utils.d.ts +15 -0
- package/dist/lib/case-utils.d.ts.map +1 -1
- package/dist/lib/case-utils.js +21 -0
- package/dist/lib/case-utils.js.map +1 -1
- package/dist/lib/case-utils.test.js +23 -1
- package/dist/lib/case-utils.test.js.map +1 -1
- package/dist/lib/prisma-errors.d.ts +38 -0
- package/dist/lib/prisma-errors.d.ts.map +1 -0
- package/dist/lib/prisma-errors.js +53 -0
- package/dist/lib/prisma-errors.js.map +1 -0
- package/dist/mcp/constants.d.ts +23 -0
- package/dist/mcp/constants.d.ts.map +1 -0
- package/dist/mcp/constants.js +23 -0
- package/dist/mcp/constants.js.map +1 -0
- package/dist/mcp/field-schema.d.ts +8 -0
- package/dist/mcp/field-schema.d.ts.map +1 -0
- package/dist/mcp/field-schema.js +69 -0
- package/dist/mcp/field-schema.js.map +1 -0
- package/dist/mcp/handler.d.ts.map +1 -1
- package/dist/mcp/handler.js +76 -90
- package/dist/mcp/handler.js.map +1 -1
- package/dist/mcp/projection.d.ts +62 -0
- package/dist/mcp/projection.d.ts.map +1 -0
- package/dist/mcp/projection.js +390 -0
- package/dist/mcp/projection.js.map +1 -0
- package/dist/utils/password.d.ts +3 -1
- package/dist/utils/password.d.ts.map +1 -1
- package/dist/utils/password.js +4 -2
- package/dist/utils/password.js.map +1 -1
- package/package.json +1 -1
- package/src/access/access-filter.test.ts +278 -12
- package/src/access/access-filter.ts +207 -19
- package/src/access/engine.test.ts +67 -0
- package/src/access/engine.ts +81 -1
- package/src/access/errors.ts +42 -2
- package/src/access/field-access.test.ts +78 -0
- package/src/access/field-access.ts +34 -2
- package/src/access/field-visibility.ts +75 -10
- package/src/access/index.ts +14 -0
- package/src/config/types.ts +76 -10
- package/src/context/index.ts +95 -15
- package/src/context/nested-operations.ts +61 -16
- package/src/context/write-pipeline.ts +8 -4
- package/src/fields/index.ts +55 -5
- package/src/fields/relationship-foreign-key.test.ts +29 -0
- package/src/index.ts +17 -1
- package/src/lib/case-utils.test.ts +32 -0
- package/src/lib/case-utils.ts +26 -0
- package/src/lib/prisma-errors.ts +86 -0
- package/src/mcp/constants.ts +24 -0
- package/src/mcp/field-schema.ts +84 -0
- package/src/mcp/handler.ts +109 -107
- package/src/mcp/projection.ts +497 -0
- package/src/utils/password.ts +5 -3
- package/tests/access-relationships.test.ts +3 -3
- package/tests/access.test.ts +83 -0
- package/tests/context.test.ts +259 -12
- package/tests/field-types.test.ts +113 -0
- package/tests/mcp-fields-projection-access.test.ts +250 -0
- package/tests/mcp-handler.test.ts +695 -4
- package/tests/nested-access-and-hooks.test.ts +65 -0
- package/tests/nested-write-synthetic-relation.test.ts +437 -0
- package/tests/password-types.test.ts +28 -0
- package/tests/password.test.ts +16 -2
- package/tests/prisma-errors.test.ts +141 -0
- package/tests/singleton.test.ts +9 -1
- package/tests/to-one-include-access-filter.test.ts +172 -0
- package/tests/with-session.test.ts +243 -0
- package/tests/write-pipeline.test.ts +29 -0
- package/tsconfig.tsbuildinfo +1 -1
package/.turbo/turbo-build.log
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,155 @@
|
|
|
1
1
|
# @opensaas/stack-core
|
|
2
2
|
|
|
3
|
+
## 0.40.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#1020](https://github.com/OpenSaasAU/stack/pull/1020) [`8e6707a`](https://github.com/OpenSaasAU/stack/commit/8e6707adcca9d7e062bc1747ec79a29082c09ef9) Thanks [@borisno2](https://github.com/borisno2)! - Add `ui.listView.defaultColumn` to field config — a declared, presentation-only flag (default `true`) controlling whether a field belongs in a list/related-list table's default column set. Naming a field explicitly in `ui.listView.initialColumns` or a relationship's `ui.itemView.columns` always shows it regardless of this flag.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
fields: {
|
|
11
|
+
internalScore: integer({ ui: { listView: { defaultColumn: false } } }),
|
|
12
|
+
}
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`password()` now sets this flag to `false` by default instead of the admin UI matching on field type — a password field can opt back into default columns with `ui: { listView: { defaultColumn: true } } }`.
|
|
16
|
+
|
|
17
|
+
- [#1011](https://github.com/OpenSaasAU/stack/pull/1011) [`afd1a60`](https://github.com/OpenSaasAU/stack/commit/afd1a60a6ddaa558bf14887e45fa1c007e6669b0) Thanks [@borisno2](https://github.com/borisno2)! - `OperationAccess.create` now throws `InvalidCreateAccessResultError` when the rule returns anything other than `true`/`false` — most notably a Prisma filter, which previously fell through the `create` access check unrecognised and was silently treated as a full allow (both the top-level write pipeline and nested-create paths were affected).
|
|
18
|
+
|
|
19
|
+
Create has no existing row to scope a filter against, so a filter can no longer be honoured here:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// Before: type-checked, read as row-scoped, actually allowed everyone
|
|
23
|
+
create: ({ session }) => ({ ownerId: { equals: session.userId } })
|
|
24
|
+
|
|
25
|
+
// Now throws InvalidCreateAccessResultError. Scope ownership in a hook instead:
|
|
26
|
+
hooks: {
|
|
27
|
+
resolveInput: async ({ resolvedData, context, operation }) => {
|
|
28
|
+
if (operation === 'create') {
|
|
29
|
+
return { ...resolvedData, ownerId: context.session?.userId }
|
|
30
|
+
}
|
|
31
|
+
return resolvedData
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
access: {
|
|
35
|
+
operation: {
|
|
36
|
+
create: ({ session }) => !!session, // boolean only
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`create: () => false` still denies via Silent failure as before; only a non-boolean result now throws.
|
|
42
|
+
|
|
43
|
+
- [#984](https://github.com/OpenSaasAU/stack/pull/984) [`51ae299`](https://github.com/OpenSaasAU/stack/commit/51ae299b7624f97e890f85b3075c62d8e114cec2) Thanks [@borisno2](https://github.com/borisno2)! - Extend `isIndexed` to `integer`, `timestamp`, and `select`, matching `text`, `decimal`, `bigInt`, `calendarDay`, and `relationship`.
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
fields: {
|
|
47
|
+
rank: integer({ isIndexed: true }),
|
|
48
|
+
publishedAt: timestamp({ isIndexed: true }),
|
|
49
|
+
status: select({
|
|
50
|
+
options: [{ label: 'Draft', value: 'draft' }],
|
|
51
|
+
isIndexed: 'unique',
|
|
52
|
+
}),
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`isIndexed: true` generates a block-level `@@index([field])`; `isIndexed: 'unique'` generates an inline `@unique`. `select` supports both under the default string column and a native-enum column (`db: { type: 'enum' }`). No field type's default indexing behavior changes — an existing config generates the same schema as before.
|
|
57
|
+
|
|
58
|
+
- [#1007](https://github.com/OpenSaasAU/stack/pull/1007) [`4ce64b4`](https://github.com/OpenSaasAU/stack/commit/4ce64b4f9868eca0f34cc0676e46440b3d8f16ce) Thanks [@borisno2](https://github.com/borisno2)! - The derived MCP `query` tool now accepts an optional `fields` projection — the wire form of the runtime's existing fragment field selection — so an assistant can select scalars and nested relation fields (with `where`/`orderBy`/`take`/`skip`, and a to-many's row count) in a single call instead of following a foreign key with a second one. Omitting `fields` is unchanged, a bare read exactly as before.
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"name": "list_post_query",
|
|
63
|
+
"arguments": {
|
|
64
|
+
"fields": {
|
|
65
|
+
"title": true,
|
|
66
|
+
"author": { "fields": { "name": true } },
|
|
67
|
+
"comments": {
|
|
68
|
+
"fields": { "text": true },
|
|
69
|
+
"where": { "approved": { "equals": true } },
|
|
70
|
+
"take": 5,
|
|
71
|
+
"count": true
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The generated tool schema enumerates two levels of each list's own fields and relations, per session, and refuses (as an `isError` tool result, never a protocol error) anything it doesn't advertise — an unknown field, or a relation named a third level deep. See the ADR (`docs/adr/0033-mcp-tools-advertise-a-bounded-projection.md`) for the full design.
|
|
79
|
+
|
|
80
|
+
**Behaviour change:** `tools/list` is now evaluated per session. A list whose operation-level `query` access denies the session outright no longer appears in the tool listing at all — none of its four CRUD tools, and no relation entry elsewhere pointing at it. Previously every list's tools were listed regardless of session.
|
|
81
|
+
|
|
82
|
+
- [#1002](https://github.com/OpenSaasAU/stack/pull/1002) [`48d2762`](https://github.com/OpenSaasAU/stack/commit/48d27626dfb636c481301116e46c826ef3156124) Thanks [@borisno2](https://github.com/borisno2)! - Fix admin UI URL round-trip for a list keyed with anything other than strict PascalCase (issue [#991](https://github.com/OpenSaasAU/stack/issues/991)). `getListKeyFromUrl` reconstructs a list key by string transformation, which is lossy for a non-PascalCase key — a real example is a better-auth plugin's derived list (e.g. `oauthApplication`, from the `mcp` plugin's OAuth tables). Such a list appeared in navigation but its own link resolved to a key that did not exist in `config.lists`, rendering "List not found".
|
|
83
|
+
|
|
84
|
+
`@opensaas/stack-core` adds `resolveListKeyFromUrl(urlSegment, listKeys)` alongside the existing `getListKeyFromUrl`, which is unchanged and still exported. The new resolver matches a URL segment against the config's actual list keys via `getUrlKey` — the same helper that builds the URL — instead of reconstructing one, so route lookup and URL generation cannot drift apart. It returns `undefined` for a segment matching no list (so callers keep rendering their existing "not found" state), and throws if two distinct list keys would produce the same URL segment.
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import { resolveListKeyFromUrl } from '@opensaas/stack-core'
|
|
88
|
+
|
|
89
|
+
resolveListKeyFromUrl('oauth-application', Object.keys(config.lists)) // 'oauthApplication'
|
|
90
|
+
resolveListKeyFromUrl('does-not-exist', Object.keys(config.lists)) // undefined
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`@opensaas/stack-ui`'s `AdminUI` now uses `resolveListKeyFromUrl` for its route resolution, fixing the broken link for any such list.
|
|
94
|
+
|
|
95
|
+
`@opensaas/stack-auth`'s `convertBetterAuthSchema` now PascalCases a better-auth plugin's camelCase `modelName` when deriving a list key (`oauthApplication` → `OauthApplication`, `rateLimit` → `RateLimit`), matching the repo's PascalCase list-key convention and fixing the same round-trip bug at the source for these lists.
|
|
96
|
+
|
|
97
|
+
**Schema-affecting for `@opensaas/stack-auth` users with a better-auth plugin that declares extra tables** (e.g. `mcp`'s OAuth tables, or `rateLimit.storage: 'database'` with no `modelName` remap configured): the generated Prisma **model name** changes to match the new PascalCase list key. The physical **table name** does not change — the previous camelCase name is preserved via `db.map` (`@@map`) — so `prisma db push` / `prisma migrate dev` sees a model rename, not a table rename, and `context.db.oauthApplication` (the camelCase db accessor) keeps working unchanged. Regenerate (`pnpm generate`) and re-run your migration/push step after upgrading.
|
|
98
|
+
|
|
99
|
+
- [#1003](https://github.com/OpenSaasAU/stack/pull/1003) [`9de43c8`](https://github.com/OpenSaasAU/stack/commit/9de43c80c8ef996dc6f08f68f7c1d8451aa0f10e) Thanks [@borisno2](https://github.com/borisno2)! - Add `context.withSession(session)` — a sibling to `sudo()` for the other axis. It derives a `StackContext` that reuses the receiver's already-resolved config, client (including a transaction client — a call inside `context.transaction()` stays in that transaction), and storage, but carries a substituted session, so access control and hooks run against the new session as normal.
|
|
100
|
+
|
|
101
|
+
This closes a gap for callers that are legitimately authorised but arrive without the session a list `validate` hook expects — an unattended dispatcher, a service principal, or a job runner:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
// Runs with the job owner's session so hooks see the right identity, while
|
|
105
|
+
// still going through the normal access control checks for that session.
|
|
106
|
+
const asOwner = context.withSession(job.ownerSession)
|
|
107
|
+
await asOwner.db.task.update({ where: { id: job.taskId }, data: { status: 'done' } })
|
|
108
|
+
|
|
109
|
+
// Drop to anonymous
|
|
110
|
+
const anonymous = context.withSession(null)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`withSession` grants no authority of its own — the derived context can do exactly what any context built with that session directly could do. It's orthogonal to `sudo()`: `context.withSession(s).sudo()` and `context.sudo().withSession(s)` are equivalent, since `withSession` preserves the receiver's sudo state instead of resetting it.
|
|
114
|
+
|
|
115
|
+
The generated `Context<TSession>` type (`.opensaas/types.ts`) now includes `withSession: (session: TSession | null) => Context<TSession>` alongside `sudo`, so the method is typed in application code — run `opensaas generate` (or `pnpm generate`) to pick it up.
|
|
116
|
+
|
|
117
|
+
### Patch Changes
|
|
118
|
+
|
|
119
|
+
- [#1017](https://github.com/OpenSaasAU/stack/pull/1017) [`b30fa61`](https://github.com/OpenSaasAU/stack/commit/b30fa6135a6acca8c9be99fbdf5ffa7faab1959f) Thanks [@{](https://github.com/{)! - Let an application declare model-level indexes (`db.indexes`) on the derived auth lists (`User`/`Session`/`Account`/`Verification`/`RateLimit`).
|
|
120
|
+
|
|
121
|
+
Each per-model block in `authPlugin()` now accepts `indexes`, in the same shape as a list's own `db.indexes`:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
authPlugin({
|
|
125
|
+
// Adopt a live constraint's real name instead of Prisma's derived one.
|
|
126
|
+
indexes: [{ fields: ['email'], unique: true, name: 'user_email_key' }] },
|
|
127
|
+
session: { indexes: [{ fields: ['token'], unique: true, name: 'session_token_key' }] },
|
|
128
|
+
// Extend a derived column into a composite index.
|
|
129
|
+
verification: {
|
|
130
|
+
indexes: [{ fields: ['identifier', { field: 'createdAt', sort: 'desc' }] }],
|
|
131
|
+
},
|
|
132
|
+
})
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
An entry covering a column the stack already derives an index for (e.g. `User.email`) suppresses that derived index for that column and emits only the app's entry, rather than erroring — the application's declaration wins (ADR-0035). Suppression is per-column: every other derived index on the model is unaffected.
|
|
136
|
+
|
|
137
|
+
This also fixes a related generator gap: a list's `db.indexes` can now reference `createdAt`/`updatedAt` even when the list has no explicit field for them and relies on `db.timestamps` for the auto-injected columns (previously only a list with an explicitly declared `createdAt`/`updatedAt` field could be indexed on it).
|
|
138
|
+
|
|
139
|
+
- [#983](https://github.com/OpenSaasAU/stack/pull/983) [`16da817`](https://github.com/OpenSaasAU/stack/commit/16da8176114826d18d6747d27abedf75de6c3262) Thanks [@borisno2](https://github.com/borisno2)! - Fix `HashedPassword.toJSON()` returning the raw bcrypt hash, so `JSON.stringify` of a row (e.g. a server→client prop, `Response.json()`, an MCP tool response) no longer leaks the stored hash for a `password()` field.
|
|
140
|
+
|
|
141
|
+
`toJSON()` now returns `{ isSet: boolean }`, matching the redaction the admin UI already applies via `valueForClientSerialization`. `toString()`, `valueOf()`, `[Symbol.toPrimitive]`, and `==` comparison against the hash are unchanged. If you parse `JSON.stringify`'d rows and read the password field as a string, update that code to read `.isSet` instead — this is a visible output/type change on `HashedPassword.toJSON()`, though the field's read access remains the application's to configure (unchanged).
|
|
142
|
+
|
|
143
|
+
- [#999](https://github.com/OpenSaasAU/stack/pull/999) [`f85c7d1`](https://github.com/OpenSaasAU/stack/commit/f85c7d1b92e76d5e8ae090f93c0ff94e0d6c36c1) Thanks [@borisno2](https://github.com/borisno2)! - MCP derived CRUD tool and custom tool failures (access denial, thrown engine/database errors, input schema validation) now return a successful JSON-RPC response with `result.isError: true` instead of a JSON-RPC `error` object, so the calling model can see and recover from them. Genuine protocol failures (unknown method, malformed request, unknown tool name) are unchanged. Note: the wire shape of tool failures changes — a consumer asserting on the old `error` shape will need to update.
|
|
144
|
+
|
|
145
|
+
- [#1006](https://github.com/OpenSaasAU/stack/pull/1006) [`0f2e12a`](https://github.com/OpenSaasAU/stack/commit/0f2e12a69710e759d8749b8536fd5b31836226e9) Thanks [@borisno2](https://github.com/borisno2)! - `relationship({ ref: 'ListName' })` list-only refs now accept `db.foreignKey: { map: '...' }` to rename the foreign key column. The boolean form (`true`/`false`) is still rejected there since ownership is implicit on a list-only ref.
|
|
146
|
+
|
|
147
|
+
- [#1004](https://github.com/OpenSaasAU/stack/pull/1004) [`05c747a`](https://github.com/OpenSaasAU/stack/commit/05c747a18284ac769860f751a660b72591570571) Thanks [@borisno2](https://github.com/borisno2)! - Fix a nested create/update/delete through a list-only ref's synthetic reverse relation (`from_<List>_<field>`) silently bypassing the target list's hooks and validation. It now runs the same pipeline a declared relationship field's nested write gets. Under `sudo()`, an undeclared key that isn't a synthetic reverse relation is now refused rather than passed through unchecked.
|
|
148
|
+
|
|
149
|
+
- [#1000](https://github.com/OpenSaasAU/stack/pull/1000) [`0b5b51e`](https://github.com/OpenSaasAU/stack/commit/0b5b51e52787ea9e945206a109a7a56dc38e78e5) Thanks [@borisno2](https://github.com/borisno2)! - Fix `P2002` unique-constraint errors losing per-field detail under Prisma 7 driver adapters (`@prisma/adapter-pg`, PGlite), where `meta.target` is left empty. The error handler now recovers the violated columns and constraint name from the adapter's error shape, and a new `uniqueConstraintOf(error)` helper exposes this to callers of `context.db.*` directly. Unique-violation messages under driver adapters change from the generic fallback back to field-specific text.
|
|
150
|
+
|
|
151
|
+
- [#1001](https://github.com/OpenSaasAU/stack/pull/1001) [`52dfdd2`](https://github.com/OpenSaasAU/stack/commit/52dfdd2c051aa2f4b4cbd96a459213c34c3bf85c) Thanks [@borisno2](https://github.com/borisno2)! - Fix `include` on a to-one relationship throwing `PrismaClientValidationError` when the related list's `query` access resolves to a filter (Prisma only accepts a nested `where` on a to-many include). The relation is now fetched and access-scoped via a batched existence check instead, returning `null` for an excluded related row rather than throwing — a caller relying on the previous exception, or whose types assumed a non-null relation, should re-check nullability.
|
|
152
|
+
|
|
3
153
|
## 0.39.2
|
|
4
154
|
|
|
5
155
|
### Patch Changes
|
package/CLAUDE.md
CHANGED
|
@@ -192,6 +192,45 @@ Key points:
|
|
|
192
192
|
optimistically at write time, unchanged. See ADR-0028 and the hooks concept
|
|
193
193
|
doc.
|
|
194
194
|
|
|
195
|
+
#### Substituting a session (`context.withSession`, #980)
|
|
196
|
+
|
|
197
|
+
`context.withSession(session)` sits beside `sudo()` on the other axis:
|
|
198
|
+
`sudo()` keeps the session and drops access control; `withSession()` keeps
|
|
199
|
+
access control (and hooks) and swaps the session. It reuses the receiver's
|
|
200
|
+
already-resolved config, client (including a transaction client — a call
|
|
201
|
+
inside `context.transaction()` stays in that transaction), and storage.
|
|
202
|
+
|
|
203
|
+
Like `sudo()` (and unlike `transaction()`), plugin runtime services are
|
|
204
|
+
**rebuilt, not reused** — `plugin.runtime(context, sudo)` factories are
|
|
205
|
+
re-run so any plugin service that closes over the session (e.g. an auth
|
|
206
|
+
plugin's "who is this session" lookup) binds to the _new_ one rather than
|
|
207
|
+
staying stale. A hot loop calling `withSession()` per iteration re-runs
|
|
208
|
+
every plugin's `runtime()` each time; batch via a shared derived context
|
|
209
|
+
where the loop body doesn't need a different session per iteration.
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
// An unattended dispatcher running a committed intent, or any job runner
|
|
213
|
+
// that is legitimately authorised but arrives without the session a list's
|
|
214
|
+
// validate hook wants to see in `args.context.session`.
|
|
215
|
+
async function dispatchJob(context: StackContext, job: { ownerSession: Session }) {
|
|
216
|
+
const asOwner = context.withSession(job.ownerSession)
|
|
217
|
+
await asOwner.db.task.update({ where: { id: job.taskId }, data: { status: 'done' } })
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**`withSession` grants no authority of its own.** It is not an
|
|
222
|
+
authorisation — the derived context can do exactly what any context built
|
|
223
|
+
with that session directly could do; access rules still evaluate against
|
|
224
|
+
the new session. The application decides who may call it.
|
|
225
|
+
|
|
226
|
+
**Orthogonal to `sudo()`.** `context.withSession(s).sudo()` and
|
|
227
|
+
`context.sudo().withSession(s)` are equivalent — both elevated and both
|
|
228
|
+
carrying `s`. `withSession` preserves the receiver's sudo state rather than
|
|
229
|
+
resetting it: called on an already-sudo context it stays sudo, called on a
|
|
230
|
+
plain context it does not grant sudo.
|
|
231
|
+
|
|
232
|
+
`withSession(null)` is a legitimate way to drop to an anonymous context.
|
|
233
|
+
|
|
195
234
|
### Generators (`src/generator/`)
|
|
196
235
|
|
|
197
236
|
- `prisma.ts` - Generates `prisma/schema.prisma` from config
|
|
@@ -320,6 +359,18 @@ const context = createContext<typeof prisma>(config, prisma, session)
|
|
|
320
359
|
- Uses context for all operations (access control enforced); writes are validated by the normal `context.db` pipeline (field Zod schemas, hooks, access control)
|
|
321
360
|
- Custom tools may declare a Zod `inputSchema` — validated on `tools/call`, converted to JSON Schema for `tools/list` — or a plain JSON Schema object
|
|
322
361
|
- Auth adapters (like `@opensaas/stack-auth/mcp`) provide session integration; custom session fields pass through to access control
|
|
362
|
+
- The derived `query` tool accepts an optional `fields` projection (`packages/core/src/mcp/projection.ts`) — the wire form of a fragment field selection, translated into a `context.db` `include` so the ordinary read pipeline (access scoping, `needs` folding, the depth cap) applies with no parallel read path. The generated schema enumerates two fixed levels of each list's vocabulary, per session; `tools/list` itself is per-session as a result (ADR-0033)
|
|
363
|
+
|
|
364
|
+
### The MCP `query` Tool's `fields` Projection (ADR-0033)
|
|
365
|
+
|
|
366
|
+
An optional `fields` argument on the derived `query` tool narrows (or widens, up to two levels) what a read returns, in the wire form of a fragment field selection: `{ scalarField: true, relation: { fields: {...} } }`. A to-many relation additionally accepts `where`/`orderBy`/`take`/`skip` and a `count`. Omitting `fields` is unchanged — a bare read, per ADR-0024.
|
|
367
|
+
|
|
368
|
+
`projection.ts` does the work in two passes, both walking the same per-session vocabulary (`relatedListIfVisible`):
|
|
369
|
+
|
|
370
|
+
- `generateFieldsProjectionSchema` builds the JSON Schema advertised on `tools/list` — a relation whose target list denies this session's operation-level `query` access, or has `mcp.enabled: false`, is omitted from the vocabulary entirely, not merely left unusable.
|
|
371
|
+
- `resolveFieldsProjection` validates a caller's `fields` against that same vocabulary and translates it into a plain `context.db` `include` (deliberately NOT the fragment `query:` argument — going through the ordinary caller-`include` path is what gets `buildAccessScopedInclude`'s nested-`where` AND-fold, the to-one existence check, and the depth cap for free; the trade-off is that every computed field at a traversed level still computes server-side, same as any other `include`-based read, even one the projection didn't select — only the wire response is narrowed). Throws `McpProjectionRefusedError` (caught in the handler and turned into an `isError` tool result, never a JSON-RPC error) naming what was asked for and what's available on any mismatch.
|
|
372
|
+
|
|
373
|
+
A to-many's `count` is resolved separately via Prisma's `_count.select`, access-scoped by ANDing the related list's `query` access with any caller `where` on that same relation (mirroring, but not reusing, `relationship-count.ts`'s `_count` scoping — that module's own helper isn't exported and was built for a different feature, issue #732's admin list view).
|
|
323
374
|
|
|
324
375
|
### With Third-Party Field Packages
|
|
325
376
|
|
|
@@ -1,5 +1,28 @@
|
|
|
1
|
-
import type { Session, AccessContext } from './types.js';
|
|
1
|
+
import type { Session, AccessContext, PrismaFilter } from './types.js';
|
|
2
2
|
import type { OpenSaasConfig, FieldConfig, ListConfig } from '../config/types.js';
|
|
3
|
+
/** One to-one relation's recorded access filter, or an outright denial — see the module doc's "To-one relations" section. */
|
|
4
|
+
export type ToOneAccessFilterEntry = {
|
|
5
|
+
kind: 'scoped';
|
|
6
|
+
relatedListName: string;
|
|
7
|
+
accessWhere: PrismaFilter;
|
|
8
|
+
} | {
|
|
9
|
+
kind: 'denied';
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Which to-one relations, at which nesting level of an `include`, need a
|
|
13
|
+
* post-query existence check rather than a Prisma-side `where` — because
|
|
14
|
+
* their related list's `query` access resolved to a filter (`kind: 'scoped'`)
|
|
15
|
+
* or a denial (`kind: 'denied'`) and Prisma cannot express either as a nested
|
|
16
|
+
* `where` on a to-one include. `resolveToOneAccessVisibility` consumes this
|
|
17
|
+
* tree; `filterReadableFields` (`field-visibility.ts`) applies its result.
|
|
18
|
+
*/
|
|
19
|
+
export type ToOneAccessFilterTree = {
|
|
20
|
+
/** To-one relation keys at THIS level needing a post-query check. */
|
|
21
|
+
filters: Record<string, ToOneAccessFilterEntry>;
|
|
22
|
+
/** Per-key trees for relations present in the include for other reasons, whose own nested include may contain further to-one filters. */
|
|
23
|
+
nested: Record<string, ToOneAccessFilterTree>;
|
|
24
|
+
};
|
|
25
|
+
export declare function emptyToOneAccessFilterTree(): ToOneAccessFilterTree;
|
|
3
26
|
/**
|
|
4
27
|
* Build the access-scoped `include` for exactly the relations a read
|
|
5
28
|
* requested, recursing only into branches `requestedInclude` itself names.
|
|
@@ -11,14 +34,21 @@ import type { OpenSaasConfig, FieldConfig, ListConfig } from '../config/types.js
|
|
|
11
34
|
* - A declared relationship whose related list's `query` access denies it
|
|
12
35
|
* (`=== false`) → dropped entirely, no matter what the request asked for
|
|
13
36
|
* nested beneath it (#566): the caller chooses *which* relations, access
|
|
14
|
-
* control chooses *whether* and *with what filter*.
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
37
|
+
* control chooses *whether* and *with what filter*. For a to-one relation
|
|
38
|
+
* this denial is also recorded in `toOneAccessFilters` (`kind: 'denied'`),
|
|
39
|
+
* so `filterReadableFields` can still surface an explicit `null` for it
|
|
40
|
+
* (issue #974) rather than an absent key.
|
|
41
|
+
* - Otherwise, for a to-**many** relation → the access `where` is
|
|
42
|
+
* AND-combined with any caller-supplied nested `where` (never replaced —
|
|
43
|
+
* the other half of #566), and a caller-supplied `take` rides through
|
|
44
|
+
* unchanged (#752). For a to-**one** relation → the access filter (if any)
|
|
45
|
+
* is recorded in `toOneAccessFilters` instead of attached as `where`,
|
|
46
|
+
* because Prisma only accepts a nested `where` on a to-many include
|
|
47
|
+
* (issue #974) — the entry itself never carries a `where` for a to-one key.
|
|
48
|
+
* - Either way — the "One hop" rule (ADR-0026) — nested relations are scoped
|
|
49
|
+
* ONLY if `requestedInclude` itself named a nested `include` here. A bare
|
|
50
|
+
* relation (or one with no nested `include`) fetches its own columns and
|
|
51
|
+
* stops: no recursive call, no access evaluation on anything beneath it.
|
|
22
52
|
*
|
|
23
53
|
* **Depth is a cost limit, not a cycle guard (ADR-0026).** A `requestedInclude`
|
|
24
54
|
* is always a finite literal — the caller's own object, or
|
|
@@ -32,7 +62,50 @@ import type { OpenSaasConfig, FieldConfig, ListConfig } from '../config/types.js
|
|
|
32
62
|
export declare function buildAccessScopedInclude(requestedInclude: Record<string, unknown>, fieldConfigs: Record<string, FieldConfig>, args: {
|
|
33
63
|
session: Session | null;
|
|
34
64
|
context: AccessContext;
|
|
35
|
-
}, config: OpenSaasConfig, listKey: string, depth?: number): Promise<
|
|
65
|
+
}, config: OpenSaasConfig, listKey: string, depth?: number): Promise<{
|
|
66
|
+
include: Record<string, unknown>;
|
|
67
|
+
toOneAccessFilters: ToOneAccessFilterTree;
|
|
68
|
+
}>;
|
|
69
|
+
/** One to-one relation's resolved post-query visibility — see `resolveToOneAccessVisibility`. */
|
|
70
|
+
export type ToOneVisibility = {
|
|
71
|
+
kind: 'denied';
|
|
72
|
+
} | {
|
|
73
|
+
kind: 'visible';
|
|
74
|
+
ids: ReadonlySet<string>;
|
|
75
|
+
};
|
|
76
|
+
/** The resolved counterpart to {@link ToOneAccessFilterTree}, produced by `resolveToOneAccessVisibility`. */
|
|
77
|
+
export type ToOneAccessVisibilityTree = {
|
|
78
|
+
filters: Record<string, ToOneVisibility>;
|
|
79
|
+
nested: Record<string, ToOneAccessVisibilityTree>;
|
|
80
|
+
};
|
|
81
|
+
export declare function emptyToOneAccessVisibilityTree(): ToOneAccessVisibilityTree;
|
|
82
|
+
/**
|
|
83
|
+
* Resolve a `ToOneAccessFilterTree` against the RAW rows Prisma already
|
|
84
|
+
* fetched (unscoped for the flagged to-one relations — see
|
|
85
|
+
* `buildAccessScopedInclude`) into the set of related ids the session may
|
|
86
|
+
* actually see, one batched `id IN (...)` existence check per (relation,
|
|
87
|
+
* nesting level) across every row in `items` — never once per row.
|
|
88
|
+
*
|
|
89
|
+
* For each `filters` entry at a level:
|
|
90
|
+
* - `kind: 'denied'` → carried straight through; no query, nothing to check.
|
|
91
|
+
* - `kind: 'scoped'` → every id present at this key across ALL of `items` is
|
|
92
|
+
* collected first (an empty set skips the query entirely — nothing to
|
|
93
|
+
* check), then ONE `findMany` through the RAW `prisma` client (not
|
|
94
|
+
* `context.db`, which would re-evaluate the same access-control function a
|
|
95
|
+
* second time) asks which of those ids also satisfy `accessWhere` — the
|
|
96
|
+
* exact `PrismaFilter` `checkAccess` already produced, handed to Prisma
|
|
97
|
+
* unmodified rather than interpreted by hand.
|
|
98
|
+
*
|
|
99
|
+
* Recurses into `nested` by flattening the related items reached through
|
|
100
|
+
* each key across every row in `items` (a to-many hop contributes every one
|
|
101
|
+
* of its rows; a to-one hop contributes its single row, if any) into the
|
|
102
|
+
* next level's own `items` array, so a to-one relation nested arbitrarily
|
|
103
|
+
* deep is still resolved with one batched query per node, not per parent row.
|
|
104
|
+
*/
|
|
105
|
+
export declare function resolveToOneAccessVisibility(items: readonly unknown[], tree: ToOneAccessFilterTree, args: {
|
|
106
|
+
session: Session | null;
|
|
107
|
+
context: AccessContext;
|
|
108
|
+
}): Promise<ToOneAccessVisibilityTree>;
|
|
36
109
|
/**
|
|
37
110
|
* Scope every relation filter (`some`/`every`/`none`/`is`/`isNot`) nested in a
|
|
38
111
|
* caller's `where` by the related list's own `query` access — the `where`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-filter.d.ts","sourceRoot":"","sources":["../../src/access/access-filter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"access-filter.d.ts","sourceRoot":"","sources":["../../src/access/access-filter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACtE,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAmHjF,6HAA6H;AAC7H,MAAM,MAAM,sBAAsB,GAChC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,YAAY,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAA;AAE7F;;;;;;;GAOG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAA;IAC/C,yIAAyI;IACzI,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAA;CAC9C,CAAA;AAED,wBAAgB,0BAA0B,IAAI,qBAAqB,CAElE;AAWD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAsB,wBAAwB,CAC5C,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACzC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACzC,IAAI,EAAE;IACJ,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;IACvB,OAAO,EAAE,aAAa,CAAA;CACvB,EACD,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,MAAU,GAChB,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,kBAAkB,EAAE,qBAAqB,CAAA;CAAE,CAAC,CAgF1F;AAED,iGAAiG;AACjG,MAAM,MAAM,eAAe,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,GAAG,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CAAE,CAAA;AAEhG,6GAA6G;AAC7G,MAAM,MAAM,yBAAyB,GAAG;IACtC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;IACxC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAA;CAClD,CAAA;AAED,wBAAgB,8BAA8B,IAAI,yBAAyB,CAE1E;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,4BAA4B,CAChD,KAAK,EAAE,SAAS,OAAO,EAAE,EACzB,IAAI,EAAE,qBAAqB,EAC3B,IAAI,EAAE;IACJ,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;IACvB,OAAO,EAAE,aAAa,CAAA;CACvB,GACA,OAAO,CAAC,yBAAyB,CAAC,CA+CpC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,OAAO,EAEd,UAAU,EAAE,UAAU,CAAC,GAAG,CAAC,EAC3B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE;IACJ,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;IACvB,OAAO,EAAE,aAAa,CAAA;CACvB,GACA,OAAO,CAAC,OAAO,CAAC,CA0FlB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC5C,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACzC,MAAM,EAAE,cAAc,GACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CA+BrC"}
|
|
@@ -2,6 +2,7 @@ import { checkAccess, getRelatedListConfig } from './engine.js';
|
|
|
2
2
|
import { READ_INCLUDE_MAX_DEPTH } from './depth-limits.js';
|
|
3
3
|
import { AccessScopeDepthExceededError, RelationFilterAccessDeniedError } from './errors.js';
|
|
4
4
|
import { LOGICAL_OPERATORS, RELATION_QUANTIFIERS, resolveQueryField, walkWhereReadAccess, } from './query-validation.js';
|
|
5
|
+
import { getDbKey } from '../lib/case-utils.js';
|
|
5
6
|
/** A plain object — excludes `null` and arrays, which `typeof x === 'object'` alone would admit. */
|
|
6
7
|
function isPlainObject(value) {
|
|
7
8
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -13,13 +14,15 @@ function isPlainObject(value) {
|
|
|
13
14
|
* own type before trusting it, rather than casting the whole value wholesale.
|
|
14
15
|
*
|
|
15
16
|
* A numeric `take` on a to-many relation include (a caller-supplied row bound,
|
|
16
|
-
* issue #752)
|
|
17
|
-
*
|
|
17
|
+
* issue #752), and a caller-supplied `orderBy`/`skip` (#851), are carried
|
|
18
|
+
* through the same way: none of the three can ever widen the result past the
|
|
19
|
+
* access `where` — they only narrow or reorder rows the access filter already
|
|
20
|
+
* admits — so preserving them is access-neutral.
|
|
18
21
|
*/
|
|
19
22
|
function asEntryObject(value) {
|
|
20
23
|
if (!isPlainObject(value))
|
|
21
24
|
return null;
|
|
22
|
-
const { where, include, take } = value;
|
|
25
|
+
const { where, include, take, orderBy, skip } = value;
|
|
23
26
|
const entry = {};
|
|
24
27
|
if (isPlainObject(where))
|
|
25
28
|
entry.where = where;
|
|
@@ -27,6 +30,11 @@ function asEntryObject(value) {
|
|
|
27
30
|
entry.include = include;
|
|
28
31
|
if (typeof take === 'number')
|
|
29
32
|
entry.take = take;
|
|
33
|
+
if (isPlainObject(orderBy) || Array.isArray(orderBy)) {
|
|
34
|
+
entry.orderBy = orderBy;
|
|
35
|
+
}
|
|
36
|
+
if (typeof skip === 'number')
|
|
37
|
+
entry.skip = skip;
|
|
30
38
|
return entry;
|
|
31
39
|
}
|
|
32
40
|
/**
|
|
@@ -42,6 +50,16 @@ function andWhere(accessWhere, callerWhere) {
|
|
|
42
50
|
}
|
|
43
51
|
return accessWhere ?? callerWhere;
|
|
44
52
|
}
|
|
53
|
+
export function emptyToOneAccessFilterTree() {
|
|
54
|
+
return { filters: {}, nested: {} };
|
|
55
|
+
}
|
|
56
|
+
function isToOneAccessFilterTreeEmpty(tree) {
|
|
57
|
+
return Object.keys(tree.filters).length === 0 && Object.keys(tree.nested).length === 0;
|
|
58
|
+
}
|
|
59
|
+
/** Whether a relationship field is to-one (at most one related row) rather than to-many. */
|
|
60
|
+
function isToOneRelationship(fieldConfig) {
|
|
61
|
+
return !('many' in fieldConfig && fieldConfig.many === true);
|
|
62
|
+
}
|
|
45
63
|
/**
|
|
46
64
|
* Build the access-scoped `include` for exactly the relations a read
|
|
47
65
|
* requested, recursing only into branches `requestedInclude` itself names.
|
|
@@ -53,14 +71,21 @@ function andWhere(accessWhere, callerWhere) {
|
|
|
53
71
|
* - A declared relationship whose related list's `query` access denies it
|
|
54
72
|
* (`=== false`) → dropped entirely, no matter what the request asked for
|
|
55
73
|
* nested beneath it (#566): the caller chooses *which* relations, access
|
|
56
|
-
* control chooses *whether* and *with what filter*.
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
74
|
+
* control chooses *whether* and *with what filter*. For a to-one relation
|
|
75
|
+
* this denial is also recorded in `toOneAccessFilters` (`kind: 'denied'`),
|
|
76
|
+
* so `filterReadableFields` can still surface an explicit `null` for it
|
|
77
|
+
* (issue #974) rather than an absent key.
|
|
78
|
+
* - Otherwise, for a to-**many** relation → the access `where` is
|
|
79
|
+
* AND-combined with any caller-supplied nested `where` (never replaced —
|
|
80
|
+
* the other half of #566), and a caller-supplied `take` rides through
|
|
81
|
+
* unchanged (#752). For a to-**one** relation → the access filter (if any)
|
|
82
|
+
* is recorded in `toOneAccessFilters` instead of attached as `where`,
|
|
83
|
+
* because Prisma only accepts a nested `where` on a to-many include
|
|
84
|
+
* (issue #974) — the entry itself never carries a `where` for a to-one key.
|
|
85
|
+
* - Either way — the "One hop" rule (ADR-0026) — nested relations are scoped
|
|
86
|
+
* ONLY if `requestedInclude` itself named a nested `include` here. A bare
|
|
87
|
+
* relation (or one with no nested `include`) fetches its own columns and
|
|
88
|
+
* stops: no recursive call, no access evaluation on anything beneath it.
|
|
64
89
|
*
|
|
65
90
|
* **Depth is a cost limit, not a cycle guard (ADR-0026).** A `requestedInclude`
|
|
66
91
|
* is always a finite literal — the caller's own object, or
|
|
@@ -77,6 +102,7 @@ export async function buildAccessScopedInclude(requestedInclude, fieldConfigs, a
|
|
|
77
102
|
throw new AccessScopeDepthExceededError(listKey, requestedKeys[0], depth);
|
|
78
103
|
}
|
|
79
104
|
const result = {};
|
|
105
|
+
const toOneAccessFilters = emptyToOneAccessFilterTree();
|
|
80
106
|
for (const [relationName, requestedValue] of Object.entries(requestedInclude)) {
|
|
81
107
|
const fieldConfig = fieldConfigs[relationName];
|
|
82
108
|
const isDeclaredRelationship = fieldConfig?.type === 'relationship' && 'ref' in fieldConfig && !!fieldConfig.ref;
|
|
@@ -87,31 +113,126 @@ export async function buildAccessScopedInclude(requestedInclude, fieldConfigs, a
|
|
|
87
113
|
const relatedConfig = getRelatedListConfig(fieldConfig.ref, config);
|
|
88
114
|
if (!relatedConfig)
|
|
89
115
|
continue;
|
|
116
|
+
const isToOne = isToOneRelationship(fieldConfig);
|
|
90
117
|
const queryAccess = relatedConfig.listConfig.access?.operation?.query;
|
|
91
118
|
const accessResult = await checkAccess(queryAccess, {
|
|
92
119
|
session: args.session,
|
|
93
120
|
context: args.context,
|
|
94
121
|
});
|
|
95
122
|
if (accessResult === false) {
|
|
123
|
+
if (isToOne) {
|
|
124
|
+
toOneAccessFilters.filters[relationName] = { kind: 'denied' };
|
|
125
|
+
}
|
|
96
126
|
continue;
|
|
97
127
|
}
|
|
98
128
|
const accessWhere = typeof accessResult === 'object' ? accessResult : undefined;
|
|
99
129
|
const requestedEntry = asEntryObject(requestedValue);
|
|
100
|
-
const mergedWhere = andWhere(accessWhere, requestedEntry?.where);
|
|
101
130
|
let nestedInclude;
|
|
131
|
+
let nestedToOneFilters;
|
|
102
132
|
if (requestedEntry?.include) {
|
|
103
|
-
|
|
133
|
+
const nested = await buildAccessScopedInclude(requestedEntry.include, relatedConfig.listConfig.fields, args, config, relatedConfig.listName, depth + 1);
|
|
134
|
+
nestedInclude = nested.include;
|
|
135
|
+
nestedToOneFilters = nested.toOneAccessFilters;
|
|
104
136
|
}
|
|
105
137
|
const entry = {};
|
|
106
|
-
if (
|
|
107
|
-
|
|
138
|
+
if (isToOne) {
|
|
139
|
+
if (accessWhere) {
|
|
140
|
+
toOneAccessFilters.filters[relationName] = {
|
|
141
|
+
kind: 'scoped',
|
|
142
|
+
relatedListName: relatedConfig.listName,
|
|
143
|
+
accessWhere,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
const mergedWhere = andWhere(accessWhere, requestedEntry?.where);
|
|
149
|
+
if (mergedWhere)
|
|
150
|
+
entry.where = mergedWhere;
|
|
151
|
+
if (requestedEntry?.take !== undefined)
|
|
152
|
+
entry.take = requestedEntry.take;
|
|
153
|
+
if (requestedEntry?.orderBy !== undefined)
|
|
154
|
+
entry.orderBy = requestedEntry.orderBy;
|
|
155
|
+
if (requestedEntry?.skip !== undefined)
|
|
156
|
+
entry.skip = requestedEntry.skip;
|
|
157
|
+
}
|
|
108
158
|
if (nestedInclude && Object.keys(nestedInclude).length > 0)
|
|
109
159
|
entry.include = nestedInclude;
|
|
110
|
-
if (
|
|
111
|
-
|
|
160
|
+
if (nestedToOneFilters && !isToOneAccessFilterTreeEmpty(nestedToOneFilters)) {
|
|
161
|
+
toOneAccessFilters.nested[relationName] = nestedToOneFilters;
|
|
162
|
+
}
|
|
112
163
|
result[relationName] = Object.keys(entry).length > 0 ? entry : true;
|
|
113
164
|
}
|
|
114
|
-
return result;
|
|
165
|
+
return { include: result, toOneAccessFilters };
|
|
166
|
+
}
|
|
167
|
+
export function emptyToOneAccessVisibilityTree() {
|
|
168
|
+
return { filters: {}, nested: {} };
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Resolve a `ToOneAccessFilterTree` against the RAW rows Prisma already
|
|
172
|
+
* fetched (unscoped for the flagged to-one relations — see
|
|
173
|
+
* `buildAccessScopedInclude`) into the set of related ids the session may
|
|
174
|
+
* actually see, one batched `id IN (...)` existence check per (relation,
|
|
175
|
+
* nesting level) across every row in `items` — never once per row.
|
|
176
|
+
*
|
|
177
|
+
* For each `filters` entry at a level:
|
|
178
|
+
* - `kind: 'denied'` → carried straight through; no query, nothing to check.
|
|
179
|
+
* - `kind: 'scoped'` → every id present at this key across ALL of `items` is
|
|
180
|
+
* collected first (an empty set skips the query entirely — nothing to
|
|
181
|
+
* check), then ONE `findMany` through the RAW `prisma` client (not
|
|
182
|
+
* `context.db`, which would re-evaluate the same access-control function a
|
|
183
|
+
* second time) asks which of those ids also satisfy `accessWhere` — the
|
|
184
|
+
* exact `PrismaFilter` `checkAccess` already produced, handed to Prisma
|
|
185
|
+
* unmodified rather than interpreted by hand.
|
|
186
|
+
*
|
|
187
|
+
* Recurses into `nested` by flattening the related items reached through
|
|
188
|
+
* each key across every row in `items` (a to-many hop contributes every one
|
|
189
|
+
* of its rows; a to-one hop contributes its single row, if any) into the
|
|
190
|
+
* next level's own `items` array, so a to-one relation nested arbitrarily
|
|
191
|
+
* deep is still resolved with one batched query per node, not per parent row.
|
|
192
|
+
*/
|
|
193
|
+
export async function resolveToOneAccessVisibility(items, tree, args) {
|
|
194
|
+
const resolved = emptyToOneAccessVisibilityTree();
|
|
195
|
+
for (const [key, entry] of Object.entries(tree.filters)) {
|
|
196
|
+
if (entry.kind === 'denied') {
|
|
197
|
+
resolved.filters[key] = { kind: 'denied' };
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const ids = new Set();
|
|
201
|
+
for (const item of items) {
|
|
202
|
+
if (!item || typeof item !== 'object')
|
|
203
|
+
continue;
|
|
204
|
+
const value = item[key];
|
|
205
|
+
if (value && typeof value === 'object' && 'id' in value) {
|
|
206
|
+
ids.add(String(value.id));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (ids.size === 0) {
|
|
210
|
+
resolved.filters[key] = { kind: 'visible', ids: new Set() };
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- dynamic model access by list name, mirroring the rest of the read pipeline
|
|
214
|
+
const model = args.context.prisma[getDbKey(entry.relatedListName)];
|
|
215
|
+
const visibleRows = await model.findMany({
|
|
216
|
+
where: { AND: [entry.accessWhere, { id: { in: [...ids] } }] },
|
|
217
|
+
select: { id: true },
|
|
218
|
+
});
|
|
219
|
+
const visibleIds = new Set(Array.isArray(visibleRows) ? visibleRows.map((row) => String(row.id)) : []);
|
|
220
|
+
resolved.filters[key] = { kind: 'visible', ids: visibleIds };
|
|
221
|
+
}
|
|
222
|
+
for (const [key, nestedTree] of Object.entries(tree.nested)) {
|
|
223
|
+
const nestedItems = [];
|
|
224
|
+
for (const item of items) {
|
|
225
|
+
if (!item || typeof item !== 'object')
|
|
226
|
+
continue;
|
|
227
|
+
const value = item[key];
|
|
228
|
+
if (Array.isArray(value))
|
|
229
|
+
nestedItems.push(...value);
|
|
230
|
+
else if (value && typeof value === 'object')
|
|
231
|
+
nestedItems.push(value);
|
|
232
|
+
}
|
|
233
|
+
resolved.nested[key] = await resolveToOneAccessVisibility(nestedItems, nestedTree, args);
|
|
234
|
+
}
|
|
235
|
+
return resolved;
|
|
115
236
|
}
|
|
116
237
|
/**
|
|
117
238
|
* Scope every relation filter (`some`/`every`/`none`/`is`/`isNot`) nested in a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-filter.js","sourceRoot":"","sources":["../../src/access/access-filter.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"access-filter.js","sourceRoot":"","sources":["../../src/access/access-filter.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAC/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAA;AAC1D,OAAO,EAAE,6BAA6B,EAAE,+BAA+B,EAAE,MAAM,aAAa,CAAA;AAC5F,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAyD/C,oGAAoG;AACpG,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;AAC7E,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,aAAa,CAAC,KAAc;IACnC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACtC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,KAAK,CAAA;IACrD,MAAM,KAAK,GAAuB,EAAE,CAAA;IACpC,IAAI,aAAa,CAAC,KAAK,CAAC;QAAE,KAAK,CAAC,KAAK,GAAG,KAAK,CAAA;IAC7C,IAAI,aAAa,CAAC,OAAO,CAAC;QAAE,KAAK,CAAC,OAAO,GAAG,OAAO,CAAA;IACnD,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,KAAK,CAAC,IAAI,GAAG,IAAI,CAAA;IAC/C,IAAI,aAAa,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACrD,KAAK,CAAC,OAAO,GAAG,OAAwC,CAAA;IAC1D,CAAC;IACD,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,KAAK,CAAC,IAAI,GAAG,IAAI,CAAA;IAC/C,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;;GAMG;AACH,SAAS,QAAQ,CACf,WAAqC,EACrC,WAAqC;IAErC,IAAI,WAAW,IAAI,WAAW,EAAE,CAAC;QAC/B,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,CAAA;IAC5C,CAAC;IACD,OAAO,WAAW,IAAI,WAAW,CAAA;AACnC,CAAC;AAqBD,MAAM,UAAU,0BAA0B;IACxC,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAA;AACpC,CAAC;AAED,SAAS,4BAA4B,CAAC,IAA2B;IAC/D,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,CAAA;AACxF,CAAC;AAED,4FAA4F;AAC5F,SAAS,mBAAmB,CAAC,WAAwB;IACnD,OAAO,CAAC,CAAC,MAAM,IAAI,WAAW,IAAI,WAAW,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;AAC9D,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,gBAAyC,EACzC,YAAyC,EACzC,IAGC,EACD,MAAsB,EACtB,OAAe,EACf,KAAK,GAAW,CAAC;IAEjB,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;IACnD,IAAI,KAAK,IAAI,sBAAsB,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,6BAA6B,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;IAC3E,CAAC;IAED,MAAM,MAAM,GAA4B,EAAE,CAAA;IAC1C,MAAM,kBAAkB,GAAG,0BAA0B,EAAE,CAAA;IAEvD,KAAK,MAAM,CAAC,YAAY,EAAE,cAAc,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAC9E,MAAM,WAAW,GAAG,YAAY,CAAC,YAAY,CAAC,CAAA;QAC9C,MAAM,sBAAsB,GAC1B,WAAW,EAAE,IAAI,KAAK,cAAc,IAAI,KAAK,IAAI,WAAW,IAAI,CAAC,CAAC,WAAW,CAAC,GAAG,CAAA;QAEnF,IAAI,CAAC,sBAAsB,EAAE,CAAC;YAC5B,MAAM,CAAC,YAAY,CAAC,GAAG,cAAc,CAAA;YACrC,SAAQ;QACV,CAAC;QAED,MAAM,aAAa,GAAG,oBAAoB,CAAC,WAAW,CAAC,GAAa,EAAE,MAAM,CAAC,CAAA;QAC7E,IAAI,CAAC,aAAa;YAAE,SAAQ;QAE5B,MAAM,OAAO,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAA;QAEhD,MAAM,WAAW,GAAG,aAAa,CAAC,UAAU,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,CAAA;QACrE,MAAM,YAAY,GAAG,MAAM,WAAW,CAAC,WAAW,EAAE;YAClD,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,OAAO,EAAE,IAAI,CAAC,OAAO;SACtB,CAAC,CAAA;QAEF,IAAI,YAAY,KAAK,KAAK,EAAE,CAAC;YAC3B,IAAI,OAAO,EAAE,CAAC;gBACZ,kBAAkB,CAAC,OAAO,CAAC,YAAY,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAA;YAC/D,CAAC;YACD,SAAQ;QACV,CAAC;QAED,MAAM,WAAW,GAAG,OAAO,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAA;QAC/E,MAAM,cAAc,GAAG,aAAa,CAAC,cAAc,CAAC,CAAA;QAEpD,IAAI,aAAkD,CAAA;QACtD,IAAI,kBAAqD,CAAA;QACzD,IAAI,cAAc,EAAE,OAAO,EAAE,CAAC;YAC5B,MAAM,MAAM,GAAG,MAAM,wBAAwB,CAC3C,cAAc,CAAC,OAAO,EACtB,aAAa,CAAC,UAAU,CAAC,MAAM,EAC/B,IAAI,EACJ,MAAM,EACN,aAAa,CAAC,QAAQ,EACtB,KAAK,GAAG,CAAC,CACV,CAAA;YACD,aAAa,GAAG,MAAM,CAAC,OAAO,CAAA;YAC9B,kBAAkB,GAAG,MAAM,CAAC,kBAAkB,CAAA;QAChD,CAAC;QAED,MAAM,KAAK,GAAuB,EAAE,CAAA;QACpC,IAAI,OAAO,EAAE,CAAC;YACZ,IAAI,WAAW,EAAE,CAAC;gBAChB,kBAAkB,CAAC,OAAO,CAAC,YAAY,CAAC,GAAG;oBACzC,IAAI,EAAE,QAAQ;oBACd,eAAe,EAAE,aAAa,CAAC,QAAQ;oBACvC,WAAW;iBACZ,CAAA;YACH,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,WAAW,GAAG,QAAQ,CAAC,WAAW,EAAE,cAAc,EAAE,KAAK,CAAC,CAAA;YAChE,IAAI,WAAW;gBAAE,KAAK,CAAC,KAAK,GAAG,WAAW,CAAA;YAC1C,IAAI,cAAc,EAAE,IAAI,KAAK,SAAS;gBAAE,KAAK,CAAC,IAAI,GAAG,cAAc,CAAC,IAAI,CAAA;YACxE,IAAI,cAAc,EAAE,OAAO,KAAK,SAAS;gBAAE,KAAK,CAAC,OAAO,GAAG,cAAc,CAAC,OAAO,CAAA;YACjF,IAAI,cAAc,EAAE,IAAI,KAAK,SAAS;gBAAE,KAAK,CAAC,IAAI,GAAG,cAAc,CAAC,IAAI,CAAA;QAC1E,CAAC;QACD,IAAI,aAAa,IAAI,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,MAAM,GAAG,CAAC;YAAE,KAAK,CAAC,OAAO,GAAG,aAAa,CAAA;QACzF,IAAI,kBAAkB,IAAI,CAAC,4BAA4B,CAAC,kBAAkB,CAAC,EAAE,CAAC;YAC5E,kBAAkB,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,kBAAkB,CAAA;QAC9D,CAAC;QAED,MAAM,CAAC,YAAY,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA;IACrE,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAA;AAChD,CAAC;AAWD,MAAM,UAAU,8BAA8B;IAC5C,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAA;AACpC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,KAAyB,EACzB,IAA2B,EAC3B,IAGC;IAED,MAAM,QAAQ,GAAG,8BAA8B,EAAE,CAAA;IAEjD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACxD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC5B,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAA;YAC1C,SAAQ;QACV,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAA;QAC7B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;gBAAE,SAAQ;YAC/C,MAAM,KAAK,GAAI,IAAgC,CAAC,GAAG,CAAC,CAAA;YACpD,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,IAAI,IAAI,KAAK,EAAE,CAAC;gBACxD,GAAG,CAAC,GAAG,CAAC,MAAM,CAAE,KAAiC,CAAC,EAAE,CAAC,CAAC,CAAA;YACxD,CAAC;QACH,CAAC;QAED,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACnB,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,GAAG,EAAE,EAAE,CAAA;YAC3D,SAAQ;QACV,CAAC;QAED,4IAA4I;QAC5I,MAAM,KAAK,GAAI,IAAI,CAAC,OAAO,CAAC,MAAc,CAAC,QAAQ,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAA;QAC3E,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC;YACvC,KAAK,EAAE,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,EAAE,CAAC,EAAE;YAC7D,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE;SACrB,CAAC,CAAA;QACF,MAAM,UAAU,GAAG,IAAI,GAAG,CACxB,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,GAAoB,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAC5F,CAAA;QACD,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,UAAU,EAAE,CAAA;IAC9D,CAAC;IAED,KAAK,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5D,MAAM,WAAW,GAAc,EAAE,CAAA;QACjC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;gBAAE,SAAQ;YAC/C,MAAM,KAAK,GAAI,IAAgC,CAAC,GAAG,CAAC,CAAA;YACpD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;gBAAE,WAAW,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAA;iBAC/C,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;gBAAE,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACtE,CAAC;QACD,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,4BAA4B,CAAC,WAAW,EAAE,UAAU,EAAE,IAAI,CAAC,CAAA;IAC1F,CAAC;IAED,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,KAAc;AACd,qGAAqG;AACrG,UAA2B,EAC3B,QAAgB,EAChB,MAAsB,EACtB,IAGC;IAED,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAE7D,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,OAAO,CAAC,GAAG,CAChB,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,sBAAsB,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CACxF,CAAA;IACH,CAAC;IAED,MAAM,MAAM,GAA4B,EAAE,CAAA;IAE1C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,EAAE,CAAC;QAC5E,IAAI,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/B,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,sBAAsB,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,CAAA;YACrF,SAAQ;QACV,CAAC;QAED,MAAM,QAAQ,GAAG,iBAAiB,CAAC,GAAG,EAAE,UAAU,CAAC,MAAM,CAAC,CAAA;QAC1D,IACE,CAAC,QAAQ;YACT,CAAC,QAAQ,CAAC,cAAc;YACxB,KAAK,KAAK,IAAI;YACd,OAAO,KAAK,KAAK,QAAQ;YACzB,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EACpB,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;YACnB,SAAQ;QACV,CAAC;QAED,MAAM,OAAO,GAAG,oBAAoB,CAAC,QAAQ,CAAC,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;QACtE,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;YACnB,SAAQ;QACV,CAAC;QAED,MAAM,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,CAAA;QAC/D,MAAM,YAAY,GAAG,MAAM,WAAW,CAAC,WAAW,EAAE;YAClD,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,OAAO,EAAE,IAAI,CAAC,OAAO;SACtB,CAAC,CAAA;QAEF,IAAI,YAAY,KAAK,KAAK,EAAE,CAAC;YAC3B,MAAM,IAAI,+BAA+B,CAAC,QAAQ,EAAE,GAAG,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAA;QAC5E,CAAC;QAED,MAAM,WAAW,GAAG,OAAO,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAA;QAC/E,MAAM,eAAe,GAAG,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,CAAA;QACxE,MAAM,aAAa,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;QAEhF,IAAI,aAAa,EAAE,CAAC;YAClB,MAAM,WAAW,GAA4B,EAAE,CAAA;YAC/C,KAAK,MAAM,CAAC,UAAU,EAAE,eAAe,CAAC,IAAI,eAAe,EAAE,CAAC;gBAC5D,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;oBAC1C,WAAW,CAAC,UAAU,CAAC,GAAG,eAAe,CAAA;oBACzC,SAAQ;gBACV,CAAC;gBACD,MAAM,mBAAmB,CAAC,eAAe,EAAE,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;gBACtF,MAAM,YAAY,GAAG,MAAM,sBAAsB,CAC/C,eAAe,EACf,OAAO,CAAC,UAAU,EAClB,OAAO,CAAC,QAAQ,EAChB,MAAM,EACN,IAAI,CACL,CAAA;gBACD,WAAW,CAAC,UAAU,CAAC,GAAG,WAAW;oBACnC,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,YAAwC,CAAC;oBACjE,CAAC,CAAC,YAAY,CAAA;YAClB,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,GAAG,WAAW,CAAA;QAC3B,CAAC;aAAM,CAAC;YACN,qEAAqE;YACrE,yEAAyE;YACzE,sEAAsE;YACtE,uEAAuE;YACvE,mEAAmE;YACnE,MAAM,mBAAmB,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;YAC5E,MAAM,YAAY,GAAG,MAAM,sBAAsB,CAC/C,KAAK,EACL,OAAO,CAAC,UAAU,EAClB,OAAO,CAAC,QAAQ,EAChB,MAAM,EACN,IAAI,CACL,CAAA;YACD,MAAM,CAAC,GAAG,CAAC,GAAG,WAAW;gBACvB,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,CAAC,WAAW,EAAE,YAAwC,CAAC,EAAE;gBACzE,CAAC,CAAC,YAAY,CAAA;QAClB,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,6BAA6B,CAC3C,OAA4C,EAC5C,YAAyC,EACzC,MAAsB;IAEtB,IAAI,CAAC,OAAO;QAAE,OAAO,OAAO,CAAA;IAE5B,MAAM,MAAM,GAA4B,EAAE,CAAA;IAE1C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACnD,MAAM,WAAW,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;QAErC,IAAI,WAAW,EAAE,OAAO;YAAE,SAAQ;QAElC,MAAM,sBAAsB,GAC1B,WAAW,EAAE,IAAI,KAAK,cAAc,IAAI,KAAK,IAAI,WAAW,IAAI,CAAC,CAAC,WAAW,CAAC,GAAG,CAAA;QACnF,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,CAAA;QAElC,IAAI,sBAAsB,IAAI,KAAK,EAAE,OAAO,EAAE,CAAC;YAC7C,MAAM,aAAa,GAAG,oBAAoB,CAAC,WAAW,CAAC,GAAa,EAAE,MAAM,CAAC,CAAA;YAC7E,IAAI,aAAa,EAAE,CAAC;gBAClB,MAAM,aAAa,GAAG,6BAA6B,CACjD,KAAK,CAAC,OAAO,EACb,aAAa,CAAC,UAAU,CAAC,MAAM,EAC/B,MAAM,CACP,CAAA;gBACD,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,GAAI,KAAiC,EAAE,OAAO,EAAE,aAAa,EAAE,CAAA;gBAC/E,SAAQ;YACV,CAAC;QACH,CAAC;QAED,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;IACrB,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC"}
|