@opensaas/stack-core 0.38.0 → 0.39.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 +123 -0
- package/CLAUDE.md +21 -0
- package/dist/access/access-filter.d.ts +46 -1
- package/dist/access/access-filter.d.ts.map +1 -1
- package/dist/access/access-filter.js +113 -4
- package/dist/access/access-filter.js.map +1 -1
- package/dist/access/declared-dependencies.d.ts.map +1 -1
- package/dist/access/declared-dependencies.js +4 -9
- package/dist/access/declared-dependencies.js.map +1 -1
- package/dist/access/engine.d.ts +0 -20
- package/dist/access/engine.d.ts.map +1 -1
- package/dist/access/engine.js +0 -25
- package/dist/access/engine.js.map +1 -1
- package/dist/access/errors.d.ts +49 -0
- package/dist/access/errors.d.ts.map +1 -1
- package/dist/access/errors.js +75 -0
- package/dist/access/errors.js.map +1 -1
- package/dist/access/field-access.d.ts +39 -1
- package/dist/access/field-access.d.ts.map +1 -1
- package/dist/access/field-access.js +108 -42
- package/dist/access/field-access.js.map +1 -1
- package/dist/access/field-access.test.js +229 -2
- package/dist/access/field-access.test.js.map +1 -1
- package/dist/access/field-transforms.d.ts +0 -27
- package/dist/access/field-transforms.d.ts.map +1 -1
- package/dist/access/field-visibility.d.ts +0 -4
- package/dist/access/field-visibility.d.ts.map +1 -1
- package/dist/access/field-visibility.js +13 -39
- package/dist/access/field-visibility.js.map +1 -1
- package/dist/access/index.d.ts +6 -2
- package/dist/access/index.d.ts.map +1 -1
- package/dist/access/index.js +12 -2
- package/dist/access/index.js.map +1 -1
- package/dist/access/query-validation.d.ts +121 -0
- package/dist/access/query-validation.d.ts.map +1 -0
- package/dist/access/query-validation.js +292 -0
- package/dist/access/query-validation.js.map +1 -0
- package/dist/access/relationship-count.d.ts.map +1 -1
- package/dist/access/relationship-count.js +2 -10
- package/dist/access/relationship-count.js.map +1 -1
- package/dist/access/relationship-label-filter.d.ts +23 -23
- package/dist/access/relationship-label-filter.d.ts.map +1 -1
- package/dist/access/relationship-label-filter.js +5 -76
- package/dist/access/relationship-label-filter.js.map +1 -1
- package/dist/access/relationship-label-filter.test.js +19 -56
- package/dist/access/relationship-label-filter.test.js.map +1 -1
- package/dist/access/transaction-registry.d.ts +45 -0
- package/dist/access/transaction-registry.d.ts.map +1 -0
- package/dist/access/transaction-registry.js +37 -0
- package/dist/access/transaction-registry.js.map +1 -0
- package/dist/access/types.d.ts +55 -59
- package/dist/access/types.d.ts.map +1 -1
- package/dist/config/index.d.ts +9 -7
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +10 -18
- package/dist/config/index.js.map +1 -1
- package/dist/config/label.d.ts +6 -13
- package/dist/config/label.d.ts.map +1 -1
- package/dist/config/label.js +6 -13
- package/dist/config/label.js.map +1 -1
- package/dist/config/nav-count.d.ts +11 -19
- package/dist/config/nav-count.d.ts.map +1 -1
- package/dist/config/nav-count.js +11 -19
- package/dist/config/nav-count.js.map +1 -1
- package/dist/config/plugin-engine.d.ts +0 -10
- package/dist/config/plugin-engine.d.ts.map +1 -1
- package/dist/config/plugin-engine.js +1 -37
- package/dist/config/plugin-engine.js.map +1 -1
- package/dist/config/types.d.ts +94 -109
- package/dist/config/types.d.ts.map +1 -1
- package/dist/context/apply-defaults.d.ts +8 -31
- package/dist/context/apply-defaults.d.ts.map +1 -1
- package/dist/context/apply-defaults.js +12 -41
- package/dist/context/apply-defaults.js.map +1 -1
- package/dist/context/hook-pipeline.d.ts +4 -29
- package/dist/context/hook-pipeline.d.ts.map +1 -1
- package/dist/context/hook-pipeline.js +8 -41
- package/dist/context/hook-pipeline.js.map +1 -1
- package/dist/context/index.d.ts +4 -15
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +190 -193
- package/dist/context/index.js.map +1 -1
- package/dist/context/nested-operations.d.ts +7 -26
- package/dist/context/nested-operations.d.ts.map +1 -1
- package/dist/context/nested-operations.js +73 -171
- package/dist/context/nested-operations.js.map +1 -1
- package/dist/context/transaction-boundary.d.ts +36 -47
- package/dist/context/transaction-boundary.d.ts.map +1 -1
- package/dist/context/transaction-boundary.js +92 -79
- package/dist/context/transaction-boundary.js.map +1 -1
- package/dist/context/write-pipeline.d.ts +25 -64
- package/dist/context/write-pipeline.d.ts.map +1 -1
- package/dist/context/write-pipeline.js +81 -111
- package/dist/context/write-pipeline.js.map +1 -1
- package/dist/fields/format-prisma-default.d.ts.map +1 -1
- package/dist/fields/format-prisma-default.js +4 -7
- package/dist/fields/format-prisma-default.js.map +1 -1
- package/dist/fields/index.d.ts +17 -2
- package/dist/fields/index.d.ts.map +1 -1
- package/dist/fields/index.js +136 -147
- package/dist/fields/index.js.map +1 -1
- package/dist/filter/collect.d.ts +26 -3
- package/dist/filter/collect.d.ts.map +1 -1
- package/dist/filter/collect.js +30 -12
- package/dist/filter/collect.js.map +1 -1
- package/dist/filter/filter.test.js +98 -15
- package/dist/filter/filter.test.js.map +1 -1
- package/dist/filter/index.d.ts +1 -0
- package/dist/filter/index.d.ts.map +1 -1
- package/dist/filter/index.js.map +1 -1
- package/dist/filter/map.d.ts.map +1 -1
- package/dist/filter/map.js +0 -5
- package/dist/filter/map.js.map +1 -1
- package/dist/filter/parse.d.ts.map +1 -1
- package/dist/filter/parse.js +2 -9
- package/dist/filter/parse.js.map +1 -1
- package/dist/filter/serialize.d.ts.map +1 -1
- package/dist/filter/serialize.js +2 -4
- package/dist/filter/serialize.js.map +1 -1
- package/dist/hooks/index.d.ts +4 -42
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +5 -59
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -4
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +1 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +4 -0
- package/dist/internal.js.map +1 -1
- package/dist/lib/case-utils.d.ts +0 -32
- package/dist/lib/case-utils.d.ts.map +1 -1
- package/dist/lib/case-utils.js +0 -32
- package/dist/lib/case-utils.js.map +1 -1
- package/dist/mcp/handler.d.ts +0 -4
- package/dist/mcp/handler.d.ts.map +1 -1
- package/dist/mcp/handler.js +12 -69
- package/dist/mcp/handler.js.map +1 -1
- package/dist/query/index.d.ts +26 -227
- package/dist/query/index.d.ts.map +1 -1
- package/dist/query/index.js +14 -117
- package/dist/query/index.js.map +1 -1
- package/dist/utils/password.d.ts +0 -68
- package/dist/utils/password.d.ts.map +1 -1
- package/dist/utils/password.js +7 -76
- package/dist/utils/password.js.map +1 -1
- package/dist/validation/field-config.d.ts.map +1 -1
- package/dist/validation/field-config.js +0 -6
- package/dist/validation/field-config.js.map +1 -1
- package/dist/validation/schema.d.ts +0 -7
- package/dist/validation/schema.d.ts.map +1 -1
- package/dist/validation/schema.js +0 -10
- package/dist/validation/schema.js.map +1 -1
- package/package.json +1 -1
- package/src/access/access-filter.ts +152 -5
- package/src/access/declared-dependencies.ts +4 -9
- package/src/access/engine.ts +0 -25
- package/src/access/errors.ts +81 -0
- package/src/access/field-access.test.ts +312 -2
- package/src/access/field-access.ts +118 -42
- package/src/access/field-transforms.ts +8 -39
- package/src/access/field-visibility.ts +16 -47
- package/src/access/index.ts +20 -2
- package/src/access/query-validation.ts +359 -0
- package/src/access/relationship-count.ts +2 -10
- package/src/access/relationship-label-filter.test.ts +19 -68
- package/src/access/relationship-label-filter.ts +27 -104
- package/src/access/transaction-registry.ts +58 -0
- package/src/access/types.ts +83 -86
- package/src/config/index.ts +10 -22
- package/src/config/label.ts +6 -13
- package/src/config/nav-count.ts +11 -19
- package/src/config/plugin-engine.ts +1 -37
- package/src/config/types.ts +95 -110
- package/src/context/apply-defaults.ts +12 -44
- package/src/context/hook-pipeline.ts +12 -67
- package/src/context/index.ts +263 -247
- package/src/context/nested-operations.ts +94 -226
- package/src/context/transaction-boundary.ts +113 -109
- package/src/context/write-pipeline.ts +99 -155
- package/src/fields/format-prisma-default.ts +4 -7
- package/src/fields/index.ts +155 -147
- package/src/filter/collect.ts +48 -12
- package/src/filter/filter.test.ts +153 -15
- package/src/filter/index.ts +1 -0
- package/src/filter/map.ts +0 -5
- package/src/filter/parse.ts +2 -9
- package/src/filter/serialize.ts +2 -4
- package/src/hooks/index.ts +5 -59
- package/src/index.ts +23 -4
- package/src/internal.ts +5 -0
- package/src/lib/case-utils.ts +0 -32
- package/src/mcp/handler.ts +13 -71
- package/src/query/index.ts +29 -235
- package/src/utils/password.ts +7 -76
- package/src/validation/field-config.ts +0 -6
- package/src/validation/schema.ts +0 -10
- package/tests/context.test.ts +700 -1
- package/tests/field-types.test.ts +222 -0
- package/tests/mcp-handler.test.ts +34 -0
- package/tests/transaction-boundary-outermost.test.ts +530 -0
- package/tsconfig.tsbuildinfo +1 -1
package/.turbo/turbo-build.log
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,128 @@
|
|
|
1
1
|
# @opensaas/stack-core
|
|
2
2
|
|
|
3
|
+
## 0.39.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#926](https://github.com/OpenSaasAU/stack/pull/926) [`5e546b0`](https://github.com/OpenSaasAU/stack/commit/5e546b0fe3542ba41fc77e0a4628acc96eec13ea) Thanks [@borisno2](https://github.com/borisno2)! - Add a first-class `bigInt()` field type for 64-bit integers (e.g. a millisecond epoch) that overflow `integer()`'s 32-bit `Int` — Prisma `BigInt`, TypeScript `bigint`, with an admin UI component, filtering, and MCP support.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { bigInt } from '@opensaas/stack-core/fields'
|
|
11
|
+
|
|
12
|
+
fields: {
|
|
13
|
+
occurredAtMs: bigInt({ validation: { isRequired: true } }),
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
await context.db.event.create({
|
|
17
|
+
data: { occurredAtMs: 9007199254740993n }, // bigint, number, or numeric string
|
|
18
|
+
})
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Create/update accept `bigint`, an integer `number`, or a numeric `string`, and always coerce to `bigint`. A `number` above `Number.MAX_SAFE_INTEGER` is rejected rather than silently losing precision. `bigint` isn't JSON-serialisable, so an MCP CRUD tool renders the value as a decimal string instead of throwing, and the admin UI's server→client boundary (list table, item form, relationship table) now round-trips a `bigint` value correctly rather than throwing during render. The migration introspector maps Prisma `BigInt` columns to `bigInt()` instead of the previous lossy `text()` fallback.
|
|
22
|
+
|
|
23
|
+
- [#919](https://github.com/OpenSaasAU/stack/pull/919) [`cbb03fc`](https://github.com/OpenSaasAU/stack/commit/cbb03fc26047869d23513fbb156c6194d9be389b) Thanks [@borisno2](https://github.com/borisno2)! - Fix a fail-open bug where a field-level access rule returning a Prisma filter (instead of a boolean) silently granted the field full access. `checkFieldAccess` now throws `InvalidFieldAccessResultError` for any non-boolean result instead of defaulting to allow.
|
|
24
|
+
|
|
25
|
+
This narrows `FieldAccessControl`'s return type from `boolean | PrismaFilter | Promise<...>` to `boolean | Promise<boolean>` — field-level access was already documented (ADR-0001) to be boolean-only; the type had drifted from that. If a field rule returned a filter, it will now fail to compile (or throw at runtime for untyped/JS configs) instead of silently granting access. Evaluate the condition yourself and return a boolean instead, e.g.:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
// Before (silently granted full access on read/write)
|
|
29
|
+
someField: text({
|
|
30
|
+
access: {
|
|
31
|
+
update: ({ session }) => ({ ownerId: { equals: session?.userId } }),
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// After
|
|
36
|
+
someField: text({
|
|
37
|
+
access: {
|
|
38
|
+
update: ({ session, item }) => !!session && session.userId === item?.ownerId,
|
|
39
|
+
},
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
See `docs/adr/0030-field-level-access-fails-closed-on-a-non-boolean-result.md` for the full reasoning.
|
|
44
|
+
|
|
45
|
+
- [#925](https://github.com/OpenSaasAU/stack/pull/925) [`6f9a64d`](https://github.com/OpenSaasAU/stack/commit/6f9a64d2f25212e91181adc2b67add326a540f6a) Thanks [@borisno2](https://github.com/borisno2)! - Fix a field-level `read` gate withholding a field's VALUE but leaving its PREDICATE unconstrained: a read-denied field could still be named in a `where`/`orderBy`, letting its value (or relative order) be recovered by probing — `count()` is the cleanest instrument, since it answers a predicate while returning no rows at all. `findMany`/`count` now reject a `where`/`orderBy` key naming a field the session cannot read (including nested inside `AND`/`OR`/`NOT`), throwing instead of returning a silently narrowed or empty result. A `read` rule that depends on the fetched row (`item`) cannot be evaluated before the query runs and now resolves to a denial rather than being skipped — see `docs/adr/0031-a-predicate-cannot-name-a-field-the-session-cannot-read.md`. `sudo` is unaffected.
|
|
46
|
+
|
|
47
|
+
This was independently reachable through the admin UI's own list view: `collectFilterSpecs`, `buildListFilterWhere`, and `collectFilterSuggestions` (`@opensaas/stack-core`) now take a required `{ session, context }` argument and return a `Promise`, excluding a read-denied field from the collected Filter specs so the UI never suggests, autocompletes, or submits a filter the engine is going to reject — a `field:value` token for such a field now degrades to free text instead. The list view's sort validation (`@opensaas/stack-ui`) excludes the same fields from what a `?sort=` URL param may activate.
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
// Before
|
|
51
|
+
const specs = collectFilterSpecs(listConfig, listKey, config)
|
|
52
|
+
const where = buildListFilterWhere(query, listConfig, listKey, config)
|
|
53
|
+
const suggestions = collectFilterSuggestions(listConfig, listKey, config)
|
|
54
|
+
|
|
55
|
+
// After — pass the session/context the field's `read` access is checked against
|
|
56
|
+
const specs = await collectFilterSpecs(listConfig, listKey, config, { session, context })
|
|
57
|
+
const where = await buildListFilterWhere(query, listConfig, listKey, config, { session, context })
|
|
58
|
+
const suggestions = await collectFilterSuggestions(listConfig, listKey, config, {
|
|
59
|
+
session,
|
|
60
|
+
context,
|
|
61
|
+
})
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- [#934](https://github.com/OpenSaasAU/stack/pull/934) [`9a399d6`](https://github.com/OpenSaasAU/stack/commit/9a399d68e4d3f384d4cef5ccd5fc8ec6802a40a5) Thanks [@list({](https://github.com/list({)! - `afterTransaction` now fires when the OUTERMOST transaction a write participates in settles, and can report `status: 'rolled-back'` where it always reported `'committed'` before (ADR-0028, fixes [#899](https://github.com/OpenSaasAU/stack/issues/899)).
|
|
65
|
+
|
|
66
|
+
A write that joins a transaction it did not open — inside `context.transaction()`, or a hook's own `context.db` write — used to fire its `afterTransaction` bracket optimistically as soon as its own write returned, even though the enclosing transaction was still open and could still roll back. It now defers that bracket until the transaction owner (`context.transaction()`, or the Write Pipeline when it opened the transaction) observes the real commit/rollback, then reports the outcome as a conjunction: `committed` if and only if the write itself succeeded **and** the enclosing transaction committed (the write's own error always wins over the transaction's outcome). `beforeTransaction` is unaffected — it still runs eagerly, before its write.
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
|
|
70
|
+
hooks: {
|
|
71
|
+
afterTransaction: async ({ status, item, error }) => {
|
|
72
|
+
if (status === 'rolled-back') {
|
|
73
|
+
// Now correctly fires even when this write itself succeeded but the
|
|
74
|
+
// OUTER context.transaction() callback later threw.
|
|
75
|
+
await billing.releaseSeat(error)
|
|
76
|
+
} else {
|
|
77
|
+
await billing.confirmSeat(item.seatId)
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Three behavior changes to be aware of when upgrading:
|
|
85
|
+
|
|
86
|
+
- A `context.transaction()` call can now **reject** with `AfterTransactionError` even after its underlying transaction already committed, if a deferred `afterTransaction` hook throws. A transaction/serialization error (e.g. `P2034`) still takes precedence and propagates unwrapped, so an existing `P2034` retry loop is unaffected.
|
|
87
|
+
- The deferred `item` a joined write's `afterTransaction` receives on commit is the row **as that write persisted it**, captured at write time — not re-read at flush — so it can be stale if a later write in the same transaction touches the same record.
|
|
88
|
+
- Transaction-boundary hooks (`beforeTransaction`/`afterTransaction`) on a joined write now always receive a context bound to the base client, never the transaction client — matching what top-level writes already did.
|
|
89
|
+
|
|
90
|
+
A write with no transaction owner at all (an app-managed `prisma.$transaction`, or a client that cannot open one, e.g. a bare test mock) is unaffected and still fires `afterTransaction` optimistically at write time.
|
|
91
|
+
|
|
92
|
+
See `docs/adr/0028-a-transaction-boundary-hook-reports-the-outermost-transaction.md` and the "In-transaction vs transaction-boundary hooks" section of the hooks concept doc.
|
|
93
|
+
|
|
94
|
+
- [#924](https://github.com/OpenSaasAU/stack/pull/924) [`05c9ad4`](https://github.com/OpenSaasAU/stack/commit/05c9ad40f8c4e76718d870e0c1c02511a3475943) Thanks [@borisno2](https://github.com/borisno2)! - Fix `FieldAccess['read']` typing `item` as absent when Field Visibility always passes the fetched row. A field `read` rule that reads a property off `item` now compiles without a cast, `any`, or non-null assertion:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
// Before (required a cast/assertion — `item` was typed `undefined`)
|
|
98
|
+
internalNotes: text({
|
|
99
|
+
access: {
|
|
100
|
+
read: ({ item, session }) => item!.ownerId === session?.userId,
|
|
101
|
+
} as FieldAccessControl,
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
// After (compiles as written — `item` is typed as the row)
|
|
105
|
+
internalNotes: text({
|
|
106
|
+
access: { read: ({ item, session }) => item.ownerId === session?.userId },
|
|
107
|
+
})
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`FieldAccess['read']` now accepts only the single `operation: 'read'` call shape (rather than the full `read | create | update` union `FieldAccess['create']`/`FieldAccess['update']` still accept), so a rule written for the `read` slot never needs to narrow on `operation` to use `item`. The `create` branch — where there genuinely is no row yet — is unchanged.
|
|
111
|
+
|
|
112
|
+
### Patch Changes
|
|
113
|
+
|
|
114
|
+
- [#947](https://github.com/OpenSaasAU/stack/pull/947) [`5f00c3a`](https://github.com/OpenSaasAU/stack/commit/5f00c3a456295a1125281a4227309a8f8c6d853d) Thanks [@borisno2](https://github.com/borisno2)! - Clean up comments across `access/`, `config/`, `fields/`, `filter/`, `hooks/`, `lib/`, `mcp/`, `query/`, `utils/` and `validation/` per the CLAUDE.md Comments rule. No behavior changes.
|
|
115
|
+
|
|
116
|
+
- [#946](https://github.com/OpenSaasAU/stack/pull/946) [`4d8b654`](https://github.com/OpenSaasAU/stack/commit/4d8b654d099ce13d00893ebc4ce904fa69f2c47a) Thanks [@borisno2](https://github.com/borisno2)! - Restore two comments in `src/context/` that were trimmed too far in a prior comment cleanup ([#945](https://github.com/OpenSaasAU/stack/issues/945)). No behavior changes.
|
|
117
|
+
|
|
118
|
+
- [#920](https://github.com/OpenSaasAU/stack/pull/920) [`e0baadd`](https://github.com/OpenSaasAU/stack/commit/e0baaddade059cfea639d232f6953fc8c339f6f4) Thanks [@borisno2](https://github.com/borisno2)! - `findMany`/`count` now reject an undeclared `where`/`orderBy` key (including nested inside `AND`/`OR`/`NOT` or a relation filter), closing the same back-relation surface [#564](https://github.com/OpenSaasAU/stack/issues/564) closed on writes. `sudo` still bypasses.
|
|
119
|
+
|
|
120
|
+
- [#945](https://github.com/OpenSaasAU/stack/pull/945) [`ab4a5dd`](https://github.com/OpenSaasAU/stack/commit/ab4a5ddd83eebcf85d4a98f210cd378b974725f5) Thanks [@borisno2](https://github.com/borisno2)! - Clean up comments in `src/context/` per the CLAUDE.md Comments rule. No behavior changes.
|
|
121
|
+
|
|
122
|
+
- [#929](https://github.com/OpenSaasAU/stack/pull/929) [`94802ee`](https://github.com/OpenSaasAU/stack/commit/94802eee3b2fdc64fab4b576945820a6df9311c5) Thanks [@borisno2](https://github.com/borisno2)! - Fix: a relation filter in `where` (`some`/`every`/`none`/`is`/`isNot`) no longer bypasses the related list's `query` access — it is now scoped exactly like `include` already is, recursing through every hop of a chain, on both `findMany` and `count`. A filter through a related list that denies query access now throws `RelationFilterAccessDeniedError` instead of silently running unscoped; field-level `read` access on the related list also now applies to keys named inside the filter. `@opensaas/stack-ui`'s admin list view no longer needs its own relationship label-filter access fold, since the engine now covers it.
|
|
123
|
+
|
|
124
|
+
- [#931](https://github.com/OpenSaasAU/stack/pull/931) [`114302b`](https://github.com/OpenSaasAU/stack/commit/114302b95129484fadb6a1a640435ab1a5d2d102) Thanks [@borisno2](https://github.com/borisno2)! - Correct `ListIndex`/`db.indexes` doc comments, which wrongly claimed an entry must span two or more fields — a single-field entry is fully supported and now documented as such.
|
|
125
|
+
|
|
3
126
|
## 0.38.0
|
|
4
127
|
|
|
5
128
|
### Minor Changes
|
package/CLAUDE.md
CHANGED
|
@@ -170,6 +170,27 @@ Key points:
|
|
|
170
170
|
- If the client cannot open an interactive transaction (e.g. a test mock, or you
|
|
171
171
|
are already inside one), `fn` runs directly with identical hook/access
|
|
172
172
|
semantics. See ADR-0012.
|
|
173
|
+
- **A transaction-boundary `afterTransaction` reports the OUTERMOST transaction,
|
|
174
|
+
not its own write's return (ADR-0028).** A write nested in `context.transaction()`
|
|
175
|
+
(or a hook's own `context.db` write) cannot itself observe when the enclosing
|
|
176
|
+
transaction settles, so its `afterTransaction` is deferred until the owner —
|
|
177
|
+
`context.transaction()`, or the Write Pipeline when it opened the transaction —
|
|
178
|
+
observes the real commit/rollback. `beforeTransaction` stays eager in every
|
|
179
|
+
case, so under `context.transaction()` it runs with that transaction already
|
|
180
|
+
open (keep it fast; a `context.db` write from it can block on rows the
|
|
181
|
+
transaction itself is writing). Status is a conjunction — `committed` iff the
|
|
182
|
+
write itself succeeded AND the enclosing transaction committed, the write's
|
|
183
|
+
own error always winning otherwise — and the deferred `item` is the row as
|
|
184
|
+
that write persisted it, not re-read at flush (so a later same-record write in
|
|
185
|
+
the same transaction leaves it stale). Because of this, a **rejected
|
|
186
|
+
`context.transaction()` no longer implies rollback**: a deferred hook that
|
|
187
|
+
throws after a successful commit rejects the call with `AfterTransactionError`
|
|
188
|
+
over already-final data, though a transaction/serialization error (e.g.
|
|
189
|
+
`P2034`) still takes precedence and propagates unwrapped. A write with no
|
|
190
|
+
transaction owner at all (an app-managed `prisma.$transaction`, or a client —
|
|
191
|
+
e.g. a bare test mock — that cannot open one) still fires `afterTransaction`
|
|
192
|
+
optimistically at write time, unchanged. See ADR-0028 and the hooks concept
|
|
193
|
+
doc.
|
|
173
194
|
|
|
174
195
|
### Generators (`src/generator/`)
|
|
175
196
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Session, AccessContext } from './types.js';
|
|
2
|
-
import type { OpenSaasConfig, FieldConfig } from '../config/types.js';
|
|
2
|
+
import type { OpenSaasConfig, FieldConfig, ListConfig } from '../config/types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Build the access-scoped `include` for exactly the relations a read
|
|
5
5
|
* requested, recursing only into branches `requestedInclude` itself names.
|
|
@@ -33,6 +33,51 @@ export declare function buildAccessScopedInclude(requestedInclude: Record<string
|
|
|
33
33
|
session: Session | null;
|
|
34
34
|
context: AccessContext;
|
|
35
35
|
}, config: OpenSaasConfig, listKey: string, depth?: number): Promise<Record<string, unknown>>;
|
|
36
|
+
/**
|
|
37
|
+
* Scope every relation filter (`some`/`every`/`none`/`is`/`isNot`) nested in a
|
|
38
|
+
* caller's `where` by the related list's own `query` access — the `where`
|
|
39
|
+
* counterpart to `buildAccessScopedInclude` above, closing #916 (the
|
|
40
|
+
* "unclosed half of ADR-0022"). `include` and `where` are fundamentally
|
|
41
|
+
* different requests (which relations come back, vs. which parent rows
|
|
42
|
+
* match), so this is a distinct function, but it shares every primitive that
|
|
43
|
+
* matters: `checkAccess`/`getRelatedListConfig` (the same access-evaluation
|
|
44
|
+
* calls `buildAccessScopedInclude` makes), `andWhere` (the same AND-fold), and
|
|
45
|
+
* `resolveQueryField`/`LOGICAL_OPERATORS`/`RELATION_QUANTIFIERS`/
|
|
46
|
+
* `walkWhereReadAccess` (the same shape-recognition and field-read check
|
|
47
|
+
* `query-validation.ts` already uses for #912/#915) — there is no second,
|
|
48
|
+
* parallel implementation of any of those decisions.
|
|
49
|
+
*
|
|
50
|
+
* For each relationship key found (at any depth — the walk recurses through
|
|
51
|
+
* `AND`/`OR`/`NOT` and through every hop of a chain):
|
|
52
|
+
* - Not a declared relationship, or the key #912 already rejected (this walk
|
|
53
|
+
* runs strictly after that check) → passed through unchanged.
|
|
54
|
+
* - The related list's `query` access denies it (`=== false`) → THROWS
|
|
55
|
+
* `RelationFilterAccessDeniedError`. Unlike `buildAccessScopedInclude`'s
|
|
56
|
+
* silent drop, this is a loud failure: a `where` predicate has no neutral
|
|
57
|
+
* "not requested" outcome the way a missing `include` key does, so a
|
|
58
|
+
* silently-empty match would itself be a distinguishable signal (ADR-0022).
|
|
59
|
+
* - Otherwise → the access filter (if any) is AND-combined into the relation
|
|
60
|
+
* quantifier's nested clause (never replacing the caller's own condition,
|
|
61
|
+
* mirroring `andWhere`'s include-side contract), keys inside that nested
|
|
62
|
+
* clause are checked against the RELATED list's field-level `read` access
|
|
63
|
+
* (`walkWhereReadAccess`, closing #915's stated gap for this path), and the
|
|
64
|
+
* walk recurses into the related list's own fields for a further hop.
|
|
65
|
+
*
|
|
66
|
+
* One quantifier is deliberately conservative rather than exactly precise:
|
|
67
|
+
* folding the access filter into `every`'s nested clause with a plain AND
|
|
68
|
+
* makes `every` require every related row to be BOTH access-visible AND
|
|
69
|
+
* matching, not "every access-visible row matches" (the latter needs a
|
|
70
|
+
* `NOT`/`OR` transform this does not attempt). The conservative version can
|
|
71
|
+
* only reject a query that the precise version would allow — it never
|
|
72
|
+
* widens what a caller can learn — so it is the safe direction to ship; a
|
|
73
|
+
* caller can observe that an inaccessible related row exists (an `every`
|
|
74
|
+
* that "should" pass instead fails), but never that row's field values,
|
|
75
|
+
* which is the property this ticket exists to close.
|
|
76
|
+
*/
|
|
77
|
+
export declare function buildAccessScopedWhere(where: unknown, listConfig: ListConfig<any>, listName: string, config: OpenSaasConfig, args: {
|
|
78
|
+
session: Session | null;
|
|
79
|
+
context: AccessContext;
|
|
80
|
+
}): Promise<unknown>;
|
|
36
81
|
/**
|
|
37
82
|
* Remove keys that correspond to `virtual` fields from a Prisma `include`
|
|
38
83
|
* object, recursing into nested relationship includes using the related
|
|
@@ -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,EAAgB,MAAM,YAAY,CAAA;AACtE,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"access-filter.d.ts","sourceRoot":"","sources":["../../src/access/access-filter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAgB,MAAM,YAAY,CAAA;AACtE,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAkFjF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;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,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAwDlC;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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { checkAccess, getRelatedListConfig } from './engine.js';
|
|
2
2
|
import { READ_INCLUDE_MAX_DEPTH } from './depth-limits.js';
|
|
3
|
-
import { AccessScopeDepthExceededError } from './errors.js';
|
|
3
|
+
import { AccessScopeDepthExceededError, RelationFilterAccessDeniedError } from './errors.js';
|
|
4
|
+
import { LOGICAL_OPERATORS, RELATION_QUANTIFIERS, resolveQueryField, walkWhereReadAccess, } from './query-validation.js';
|
|
4
5
|
/** A plain object — excludes `null` and arrays, which `typeof x === 'object'` alone would admit. */
|
|
5
6
|
function isPlainObject(value) {
|
|
6
7
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -33,8 +34,7 @@ function asEntryObject(value) {
|
|
|
33
34
|
*
|
|
34
35
|
* The access filter is authoritative: the caller's filter may only NARROW the
|
|
35
36
|
* result further, never widen past what access permits. We therefore wrap both
|
|
36
|
-
* in a Prisma `AND` so neither can override the other.
|
|
37
|
-
* present, it is returned as-is; if neither is present, the result is undefined.
|
|
37
|
+
* in a Prisma `AND` so neither can override the other.
|
|
38
38
|
*/
|
|
39
39
|
function andWhere(accessWhere, callerWhere) {
|
|
40
40
|
if (accessWhere && callerWhere) {
|
|
@@ -113,6 +113,116 @@ export async function buildAccessScopedInclude(requestedInclude, fieldConfigs, a
|
|
|
113
113
|
}
|
|
114
114
|
return result;
|
|
115
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Scope every relation filter (`some`/`every`/`none`/`is`/`isNot`) nested in a
|
|
118
|
+
* caller's `where` by the related list's own `query` access — the `where`
|
|
119
|
+
* counterpart to `buildAccessScopedInclude` above, closing #916 (the
|
|
120
|
+
* "unclosed half of ADR-0022"). `include` and `where` are fundamentally
|
|
121
|
+
* different requests (which relations come back, vs. which parent rows
|
|
122
|
+
* match), so this is a distinct function, but it shares every primitive that
|
|
123
|
+
* matters: `checkAccess`/`getRelatedListConfig` (the same access-evaluation
|
|
124
|
+
* calls `buildAccessScopedInclude` makes), `andWhere` (the same AND-fold), and
|
|
125
|
+
* `resolveQueryField`/`LOGICAL_OPERATORS`/`RELATION_QUANTIFIERS`/
|
|
126
|
+
* `walkWhereReadAccess` (the same shape-recognition and field-read check
|
|
127
|
+
* `query-validation.ts` already uses for #912/#915) — there is no second,
|
|
128
|
+
* parallel implementation of any of those decisions.
|
|
129
|
+
*
|
|
130
|
+
* For each relationship key found (at any depth — the walk recurses through
|
|
131
|
+
* `AND`/`OR`/`NOT` and through every hop of a chain):
|
|
132
|
+
* - Not a declared relationship, or the key #912 already rejected (this walk
|
|
133
|
+
* runs strictly after that check) → passed through unchanged.
|
|
134
|
+
* - The related list's `query` access denies it (`=== false`) → THROWS
|
|
135
|
+
* `RelationFilterAccessDeniedError`. Unlike `buildAccessScopedInclude`'s
|
|
136
|
+
* silent drop, this is a loud failure: a `where` predicate has no neutral
|
|
137
|
+
* "not requested" outcome the way a missing `include` key does, so a
|
|
138
|
+
* silently-empty match would itself be a distinguishable signal (ADR-0022).
|
|
139
|
+
* - Otherwise → the access filter (if any) is AND-combined into the relation
|
|
140
|
+
* quantifier's nested clause (never replacing the caller's own condition,
|
|
141
|
+
* mirroring `andWhere`'s include-side contract), keys inside that nested
|
|
142
|
+
* clause are checked against the RELATED list's field-level `read` access
|
|
143
|
+
* (`walkWhereReadAccess`, closing #915's stated gap for this path), and the
|
|
144
|
+
* walk recurses into the related list's own fields for a further hop.
|
|
145
|
+
*
|
|
146
|
+
* One quantifier is deliberately conservative rather than exactly precise:
|
|
147
|
+
* folding the access filter into `every`'s nested clause with a plain AND
|
|
148
|
+
* makes `every` require every related row to be BOTH access-visible AND
|
|
149
|
+
* matching, not "every access-visible row matches" (the latter needs a
|
|
150
|
+
* `NOT`/`OR` transform this does not attempt). The conservative version can
|
|
151
|
+
* only reject a query that the precise version would allow — it never
|
|
152
|
+
* widens what a caller can learn — so it is the safe direction to ship; a
|
|
153
|
+
* caller can observe that an inaccessible related row exists (an `every`
|
|
154
|
+
* that "should" pass instead fails), but never that row's field values,
|
|
155
|
+
* which is the property this ticket exists to close.
|
|
156
|
+
*/
|
|
157
|
+
export async function buildAccessScopedWhere(where,
|
|
158
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
|
|
159
|
+
listConfig, listName, config, args) {
|
|
160
|
+
if (where === null || typeof where !== 'object')
|
|
161
|
+
return where;
|
|
162
|
+
if (Array.isArray(where)) {
|
|
163
|
+
return Promise.all(where.map((entry) => buildAccessScopedWhere(entry, listConfig, listName, config, args)));
|
|
164
|
+
}
|
|
165
|
+
const result = {};
|
|
166
|
+
for (const [key, value] of Object.entries(where)) {
|
|
167
|
+
if (LOGICAL_OPERATORS.has(key)) {
|
|
168
|
+
result[key] = await buildAccessScopedWhere(value, listConfig, listName, config, args);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const resolved = resolveQueryField(key, listConfig.fields);
|
|
172
|
+
if (!resolved ||
|
|
173
|
+
!resolved.isRelationship ||
|
|
174
|
+
value === null ||
|
|
175
|
+
typeof value !== 'object' ||
|
|
176
|
+
Array.isArray(value)) {
|
|
177
|
+
result[key] = value;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const related = getRelatedListConfig(resolved.fieldConfig.ref, config);
|
|
181
|
+
if (!related) {
|
|
182
|
+
result[key] = value;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const queryAccess = related.listConfig.access?.operation?.query;
|
|
186
|
+
const accessResult = await checkAccess(queryAccess, {
|
|
187
|
+
session: args.session,
|
|
188
|
+
context: args.context,
|
|
189
|
+
});
|
|
190
|
+
if (accessResult === false) {
|
|
191
|
+
throw new RelationFilterAccessDeniedError(listName, key, related.listName);
|
|
192
|
+
}
|
|
193
|
+
const accessWhere = typeof accessResult === 'object' ? accessResult : undefined;
|
|
194
|
+
const relationEntries = Object.entries(value);
|
|
195
|
+
const hasQuantifier = relationEntries.some(([k]) => RELATION_QUANTIFIERS.has(k));
|
|
196
|
+
if (hasQuantifier) {
|
|
197
|
+
const nestedEntry = {};
|
|
198
|
+
for (const [quantifier, quantifierValue] of relationEntries) {
|
|
199
|
+
if (!RELATION_QUANTIFIERS.has(quantifier)) {
|
|
200
|
+
nestedEntry[quantifier] = quantifierValue;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
await walkWhereReadAccess(quantifierValue, related.listConfig, related.listName, args);
|
|
204
|
+
const scopedNested = await buildAccessScopedWhere(quantifierValue, related.listConfig, related.listName, config, args);
|
|
205
|
+
nestedEntry[quantifier] = accessWhere
|
|
206
|
+
? andWhere(accessWhere, scopedNested)
|
|
207
|
+
: scopedNested;
|
|
208
|
+
}
|
|
209
|
+
result[key] = nestedEntry;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
// Prisma's direct-nesting to-one form: the whole value IS the nested
|
|
213
|
+
// WHERE clause, with no `is` wrapper. Only wrap it in `is` when there is
|
|
214
|
+
// an access filter to fold in — an unwrapped value that needs no fold
|
|
215
|
+
// (fully-allowed related list) is returned exactly as the caller wrote
|
|
216
|
+
// it, so an already-permitted query is not perturbed by this pass.
|
|
217
|
+
await walkWhereReadAccess(value, related.listConfig, related.listName, args);
|
|
218
|
+
const scopedNested = await buildAccessScopedWhere(value, related.listConfig, related.listName, config, args);
|
|
219
|
+
result[key] = accessWhere
|
|
220
|
+
? { is: andWhere(accessWhere, scopedNested) }
|
|
221
|
+
: scopedNested;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
116
226
|
/**
|
|
117
227
|
* Remove keys that correspond to `virtual` fields from a Prisma `include`
|
|
118
228
|
* object, recursing into nested relationship includes using the related
|
|
@@ -135,7 +245,6 @@ export function stripVirtualFieldsFromInclude(include, fieldConfigs, config) {
|
|
|
135
245
|
const result = {};
|
|
136
246
|
for (const [key, value] of Object.entries(include)) {
|
|
137
247
|
const fieldConfig = fieldConfigs[key];
|
|
138
|
-
// Virtual fields have no database column — drop them from the include.
|
|
139
248
|
if (fieldConfig?.virtual)
|
|
140
249
|
continue;
|
|
141
250
|
const isDeclaredRelationship = fieldConfig?.type === 'relationship' && 'ref' in fieldConfig && !!fieldConfig.ref;
|
|
@@ -1 +1 @@
|
|
|
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,MAAM,aAAa,CAAA;AA+
|
|
1
|
+
{"version":3,"file":"access-filter.js","sourceRoot":"","sources":["../../src/access/access-filter.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"declared-dependencies.d.ts","sourceRoot":"","sources":["../../src/access/declared-dependencies.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAErE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAE5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAEH,gGAAgG;AAChG,MAAM,MAAM,gBAAgB,GAAG;IAC7B,2EAA2E;IAC3E,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACjB,6JAA6J;IAC7J,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;CACzC,CAAA;AAED,wBAAgB,qBAAqB,IAAI,gBAAgB,CAExD;AA6BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,wBAAwB,CACtC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACzC,cAAc,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GACnC,MAAM,EAAE,CAUV;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC/C,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACzC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,MAAM,EACf,YAAY,GAAE,SAAS,MAAM,EAAc,EAC3C,SAAS,CAAC,EAAE,mBAAmB,GAC9B;IAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAAC,YAAY,EAAE,gBAAgB,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"declared-dependencies.d.ts","sourceRoot":"","sources":["../../src/access/declared-dependencies.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAErE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAE5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAEH,gGAAgG;AAChG,MAAM,MAAM,gBAAgB,GAAG;IAC7B,2EAA2E;IAC3E,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACjB,6JAA6J;IAC7J,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;CACzC,CAAA;AAED,wBAAgB,qBAAqB,IAAI,gBAAgB,CAExD;AA6BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,wBAAwB,CACtC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACzC,cAAc,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GACnC,MAAM,EAAE,CAUV;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC/C,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACzC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,MAAM,EACf,YAAY,GAAE,SAAS,MAAM,EAAc,EAC3C,SAAS,CAAC,EAAE,mBAAmB,GAC9B;IAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAAC,YAAY,EAAE,gBAAgB,CAAA;CAAE,CA6DlF"}
|
|
@@ -94,12 +94,8 @@ export function foldDeclaredDependencies(rawInclude, fieldConfigs, config, listK
|
|
|
94
94
|
if (!relatedConfig)
|
|
95
95
|
continue;
|
|
96
96
|
// Defensive cycle guard (see module doc comment) — a DECLARATION-ADDED
|
|
97
|
-
// edge into a list already on this path stops here
|
|
98
|
-
//
|
|
99
|
-
// deliberately not applied to an edge the request itself named: that
|
|
100
|
-
// recursion is bounded by the request's own finite literal and cannot
|
|
101
|
-
// loop, so stopping it would silently drop the folds beneath a request
|
|
102
|
-
// that merely revisits a list (e.g. `Post → author → posts`).
|
|
97
|
+
// edge into a list already on this path stops here; the value at `key`
|
|
98
|
+
// is left exactly as-is.
|
|
103
99
|
if (declaredOnly.keys.has(key) && visitedLists.includes(relatedConfig.listName))
|
|
104
100
|
continue;
|
|
105
101
|
// A branch added purely by the fold has no fragment scope of its own —
|
|
@@ -117,9 +113,8 @@ export function foldDeclaredDependencies(rawInclude, fieldConfigs, config, listK
|
|
|
117
113
|
include: nested.include,
|
|
118
114
|
};
|
|
119
115
|
}
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
// individual nested-key tracking of its own.
|
|
116
|
+
// Declaration-added branches need no nested tracking of their own (see
|
|
117
|
+
// module doc comment) — only caller/fragment-named branches do.
|
|
123
118
|
if (!declaredOnly.keys.has(key) && !isDeclaredOnlyTreeEmpty(nested.declaredOnly)) {
|
|
124
119
|
declaredOnly.nested[key] = nested.declaredOnly;
|
|
125
120
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"declared-dependencies.js","sourceRoot":"","sources":["../../src/access/declared-dependencies.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"declared-dependencies.js","sourceRoot":"","sources":["../../src/access/declared-dependencies.ts"],"names":[],"mappings":"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"}
|
package/dist/access/engine.d.ts
CHANGED
|
@@ -14,36 +14,16 @@ import type { OpenSaasConfig, ListConfig } from '../config/types.js';
|
|
|
14
14
|
* (`checkFieldAccess`). See `docs/adr/0001-access-control-is-a-two-phase-read.md`
|
|
15
15
|
* and the access-control glossary in `CONTEXT.md`.
|
|
16
16
|
*/
|
|
17
|
-
/**
|
|
18
|
-
* Check if access control result is a boolean
|
|
19
|
-
*/
|
|
20
17
|
export declare function isBoolean(value: unknown): value is boolean;
|
|
21
|
-
/**
|
|
22
|
-
* Check if access control result is a Prisma filter
|
|
23
|
-
*/
|
|
24
18
|
export declare function isPrismaFilter(value: unknown): value is PrismaFilter;
|
|
25
|
-
/**
|
|
26
|
-
* Parse a relationship ref and get the related list configuration
|
|
27
|
-
* Relationship refs are in the format "ListName.fieldName"
|
|
28
|
-
*
|
|
29
|
-
* @param relationshipRef - The ref string (e.g., "Post.author")
|
|
30
|
-
* @param config - The OpenSaas configuration
|
|
31
|
-
* @returns The related list name and config, or null if not found
|
|
32
|
-
*/
|
|
33
19
|
export declare function getRelatedListConfig(relationshipRef: string, config: OpenSaasConfig): {
|
|
34
20
|
listName: string;
|
|
35
21
|
listConfig: ListConfig<any>;
|
|
36
22
|
} | null;
|
|
37
|
-
/**
|
|
38
|
-
* Execute an access control function
|
|
39
|
-
*/
|
|
40
23
|
export declare function checkAccess<T = Record<string, unknown>>(accessControl: AccessControl<T> | undefined, args: {
|
|
41
24
|
session: Session | null;
|
|
42
25
|
item?: T;
|
|
43
26
|
context: AccessContext;
|
|
44
27
|
}): Promise<boolean | PrismaFilter<T>>;
|
|
45
|
-
/**
|
|
46
|
-
* Merge user filter with access control filter
|
|
47
|
-
*/
|
|
48
28
|
export declare function mergeFilters(userFilter: PrismaFilter | undefined, accessFilter: boolean | PrismaFilter): PrismaFilter | null;
|
|
49
29
|
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/access/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACrF,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAEpE;;;;;;;;;;;;;GAaG;AAEH
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/access/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACrF,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAEpE;;;;;;;;;;;;;GAaG;AAEH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,OAAO,CAE1D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAEpE;AAED,wBAAgB,oBAAoB,CAClC,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,cAAc,GAErB;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,UAAU,CAAC,GAAG,CAAC,CAAA;CAAE,GAAG,IAAI,CAe1D;AAED,wBAAsB,WAAW,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC3D,aAAa,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,SAAS,EAC3C,IAAI,EAAE;IACJ,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;IACvB,IAAI,CAAC,EAAE,CAAC,CAAA;IACR,OAAO,EAAE,aAAa,CAAA;CACvB,GACA,OAAO,CAAC,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,CASpC;AAED,wBAAgB,YAAY,CAC1B,UAAU,EAAE,YAAY,GAAG,SAAS,EACpC,YAAY,EAAE,OAAO,GAAG,YAAY,GACnC,YAAY,GAAG,IAAI,CAgBrB"}
|
package/dist/access/engine.js
CHANGED
|
@@ -12,26 +12,12 @@
|
|
|
12
12
|
* (`checkFieldAccess`). See `docs/adr/0001-access-control-is-a-two-phase-read.md`
|
|
13
13
|
* and the access-control glossary in `CONTEXT.md`.
|
|
14
14
|
*/
|
|
15
|
-
/**
|
|
16
|
-
* Check if access control result is a boolean
|
|
17
|
-
*/
|
|
18
15
|
export function isBoolean(value) {
|
|
19
16
|
return typeof value === 'boolean';
|
|
20
17
|
}
|
|
21
|
-
/**
|
|
22
|
-
* Check if access control result is a Prisma filter
|
|
23
|
-
*/
|
|
24
18
|
export function isPrismaFilter(value) {
|
|
25
19
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
26
20
|
}
|
|
27
|
-
/**
|
|
28
|
-
* Parse a relationship ref and get the related list configuration
|
|
29
|
-
* Relationship refs are in the format "ListName.fieldName"
|
|
30
|
-
*
|
|
31
|
-
* @param relationshipRef - The ref string (e.g., "Post.author")
|
|
32
|
-
* @param config - The OpenSaas configuration
|
|
33
|
-
* @returns The related list name and config, or null if not found
|
|
34
|
-
*/
|
|
35
21
|
export function getRelatedListConfig(relationshipRef, config) {
|
|
36
22
|
// Parse ref format: "ListName.fieldName" or "ListName"
|
|
37
23
|
const parts = relationshipRef.split('.');
|
|
@@ -45,35 +31,24 @@ export function getRelatedListConfig(relationshipRef, config) {
|
|
|
45
31
|
}
|
|
46
32
|
return { listName, listConfig };
|
|
47
33
|
}
|
|
48
|
-
/**
|
|
49
|
-
* Execute an access control function
|
|
50
|
-
*/
|
|
51
34
|
export async function checkAccess(accessControl, args) {
|
|
52
35
|
// No access control means deny by default
|
|
53
36
|
if (!accessControl) {
|
|
54
37
|
return false;
|
|
55
38
|
}
|
|
56
|
-
// Execute the access control function
|
|
57
39
|
const result = await accessControl(args);
|
|
58
40
|
return result;
|
|
59
41
|
}
|
|
60
|
-
/**
|
|
61
|
-
* Merge user filter with access control filter
|
|
62
|
-
*/
|
|
63
42
|
export function mergeFilters(userFilter, accessFilter) {
|
|
64
|
-
// If access is denied, return null
|
|
65
43
|
if (accessFilter === false) {
|
|
66
44
|
return null;
|
|
67
45
|
}
|
|
68
|
-
// If access is fully granted, use user filter
|
|
69
46
|
if (accessFilter === true) {
|
|
70
47
|
return userFilter || {};
|
|
71
48
|
}
|
|
72
|
-
// Merge access filter with user filter
|
|
73
49
|
if (!userFilter) {
|
|
74
50
|
return accessFilter;
|
|
75
51
|
}
|
|
76
|
-
// Combine filters with AND
|
|
77
52
|
return {
|
|
78
53
|
AND: [accessFilter, userFilter],
|
|
79
54
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/access/engine.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;GAaG;AAEH
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/access/engine.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;GAaG;AAEH,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,OAAO,OAAO,KAAK,KAAK,SAAS,CAAA;AACnC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;AAC7E,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,eAAuB,EACvB,MAAsB;IAGtB,uDAAuD;IACvD,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACxC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7C,OAAO,IAAI,CAAA;IACb,CAAC;IAED,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;IACzB,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;IAEzC,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAA;AACjC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,aAA2C,EAC3C,IAIC;IAED,0CAA0C;IAC1C,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO,KAAK,CAAA;IACd,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,CAAA;IAExC,OAAO,MAAM,CAAA;AACf,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,UAAoC,EACpC,YAAoC;IAEpC,IAAI,YAAY,KAAK,KAAK,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;QAC1B,OAAO,UAAU,IAAI,EAAE,CAAA;IACzB,CAAC;IAED,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,YAAY,CAAA;IACrB,CAAC;IAED,OAAO;QACL,GAAG,EAAE,CAAC,YAAY,EAAE,UAAU,CAAC;KAChC,CAAA;AACH,CAAC"}
|