@opensaas/stack-core 0.30.0 → 0.31.1
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 +412 -0
- package/dist/access/access-filter.d.ts.map +1 -1
- package/dist/access/access-filter.js +14 -1
- package/dist/access/access-filter.js.map +1 -1
- package/dist/access/access-filter.test.js +50 -0
- package/dist/access/access-filter.test.js.map +1 -1
- package/dist/access/field-access.js +6 -6
- package/dist/access/field-access.js.map +1 -1
- package/dist/access/multi-column-read-write.test.js +46 -12
- package/dist/access/multi-column-read-write.test.js.map +1 -1
- package/dist/access/relationship-count.d.ts +60 -0
- package/dist/access/relationship-count.d.ts.map +1 -0
- package/dist/access/relationship-count.js +233 -0
- package/dist/access/relationship-count.js.map +1 -0
- package/dist/access/relationship-count.test.d.ts +2 -0
- package/dist/access/relationship-count.test.d.ts.map +1 -0
- package/dist/access/relationship-count.test.js +185 -0
- package/dist/access/relationship-count.test.js.map +1 -0
- package/dist/access/relationship-label-filter.d.ts +45 -0
- package/dist/access/relationship-label-filter.d.ts.map +1 -0
- package/dist/access/relationship-label-filter.js +93 -0
- package/dist/access/relationship-label-filter.js.map +1 -0
- package/dist/access/relationship-label-filter.test.d.ts +2 -0
- package/dist/access/relationship-label-filter.test.d.ts.map +1 -0
- package/dist/access/relationship-label-filter.test.js +126 -0
- package/dist/access/relationship-label-filter.test.js.map +1 -0
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/nav-count.d.ts +34 -0
- package/dist/config/nav-count.d.ts.map +1 -0
- package/dist/config/nav-count.js +71 -0
- package/dist/config/nav-count.js.map +1 -0
- package/dist/config/types.d.ts +305 -4
- package/dist/config/types.d.ts.map +1 -1
- package/dist/context/hook-pipeline.d.ts.map +1 -1
- package/dist/context/hook-pipeline.js +11 -1
- package/dist/context/hook-pipeline.js.map +1 -1
- package/dist/context/index.d.ts +28 -0
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +205 -0
- package/dist/context/index.js.map +1 -1
- package/dist/context/nested-operations.d.ts.map +1 -1
- package/dist/context/nested-operations.js +7 -1
- package/dist/context/nested-operations.js.map +1 -1
- package/dist/extend.d.ts +1 -0
- package/dist/extend.d.ts.map +1 -1
- package/dist/fields/index.d.ts +1 -1
- package/dist/fields/index.d.ts.map +1 -1
- package/dist/fields/index.js +166 -0
- package/dist/fields/index.js.map +1 -1
- package/dist/filter/collect.d.ts +32 -0
- package/dist/filter/collect.d.ts.map +1 -0
- package/dist/filter/collect.js +60 -0
- package/dist/filter/collect.js.map +1 -0
- package/dist/filter/filter.test.d.ts +2 -0
- package/dist/filter/filter.test.d.ts.map +1 -0
- package/dist/filter/filter.test.js +348 -0
- package/dist/filter/filter.test.js.map +1 -0
- package/dist/filter/index.d.ts +7 -0
- package/dist/filter/index.d.ts.map +1 -0
- package/dist/filter/index.js +16 -0
- package/dist/filter/index.js.map +1 -0
- package/dist/filter/map.d.ts +18 -0
- package/dist/filter/map.d.ts.map +1 -0
- package/dist/filter/map.js +60 -0
- package/dist/filter/map.js.map +1 -0
- package/dist/filter/parse.d.ts +33 -0
- package/dist/filter/parse.d.ts.map +1 -0
- package/dist/filter/parse.js +103 -0
- package/dist/filter/parse.js.map +1 -0
- package/dist/filter/serialize.d.ts +25 -0
- package/dist/filter/serialize.d.ts.map +1 -0
- package/dist/filter/serialize.js +68 -0
- package/dist/filter/serialize.js.map +1 -0
- package/dist/filter/serialize.test.d.ts +2 -0
- package/dist/filter/serialize.test.d.ts.map +1 -0
- package/dist/filter/serialize.test.js +83 -0
- package/dist/filter/serialize.test.js.map +1 -0
- package/dist/filter/types.d.ts +122 -0
- package/dist/filter/types.d.ts.map +1 -0
- package/dist/filter/types.js +24 -0
- package/dist/filter/types.js.map +1 -0
- package/dist/hooks/index.d.ts +30 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +79 -59
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -0
- 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/package.json +1 -1
- package/src/access/access-filter.test.ts +79 -0
- package/src/access/access-filter.ts +15 -4
- package/src/access/field-access.ts +6 -6
- package/src/access/multi-column-read-write.test.ts +68 -19
- package/src/access/relationship-count.test.ts +269 -0
- package/src/access/relationship-count.ts +311 -0
- package/src/access/relationship-label-filter.test.ts +177 -0
- package/src/access/relationship-label-filter.ts +140 -0
- package/src/config/index.ts +6 -0
- package/src/config/nav-count.ts +85 -0
- package/src/config/types.ts +319 -1
- package/src/context/hook-pipeline.ts +19 -0
- package/src/context/index.ts +285 -3
- package/src/context/nested-operations.ts +22 -0
- package/src/extend.ts +9 -0
- package/src/fields/index.ts +168 -0
- package/src/filter/collect.ts +72 -0
- package/src/filter/filter.test.ts +400 -0
- package/src/filter/index.ts +25 -0
- package/src/filter/map.ts +68 -0
- package/src/filter/parse.ts +106 -0
- package/src/filter/serialize.test.ts +115 -0
- package/src/filter/serialize.ts +75 -0
- package/src/filter/types.ts +120 -0
- package/src/hooks/index.ts +87 -57
- package/src/index.ts +54 -0
- package/src/internal.ts +5 -0
- package/tests/context.test.ts +690 -0
- package/tests/multi-column-validation-ordering.test.ts +305 -0
- package/tests/nav-count.test.ts +189 -0
- package/tsconfig.tsbuildinfo +1 -1
package/.turbo/turbo-build.log
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,417 @@
|
|
|
1
1
|
# @opensaas/stack-core
|
|
2
2
|
|
|
3
|
+
## 0.31.1
|
|
4
|
+
|
|
5
|
+
## 0.31.0
|
|
6
|
+
|
|
7
|
+
### Minor Changes
|
|
8
|
+
|
|
9
|
+
- [#750](https://github.com/OpenSaasAU/stack/pull/750) [`047487a`](https://github.com/OpenSaasAU/stack/commit/047487adf502f10f7f6774ff52c38c70d465f533) Thanks [@borisno2](https://github.com/borisno2)! - Add a `bulkDelete` server action for list-level bulk deletion
|
|
10
|
+
|
|
11
|
+
`context.serverAction` now accepts `{ listKey, action: 'bulkDelete', ids }`. It
|
|
12
|
+
deletes each id row-by-row through the secured context, honouring Silent failure
|
|
13
|
+
(a denied or missing row returns `null` and is not counted; one row's error does
|
|
14
|
+
not abort the rest), and returns `{ deleted, total }`.
|
|
15
|
+
|
|
16
|
+
The result is deliberately a count shape rather than the single-op `{ success }`
|
|
17
|
+
shape, so a UI `serverAction` wrapper that redirects on a single-item success
|
|
18
|
+
(the item-form pattern) does not hijack a list-level bulk operation.
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
const result = await context.serverAction({
|
|
22
|
+
listKey: 'Post',
|
|
23
|
+
action: 'bulkDelete',
|
|
24
|
+
ids: ['a', 'b', 'c'],
|
|
25
|
+
})
|
|
26
|
+
// result: { deleted: 2, total: 3 } // one row was denied/missing
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- [#755](https://github.com/OpenSaasAU/stack/pull/755) [`9cd06dd`](https://github.com/OpenSaasAU/stack/commit/9cd06dddb45512966affc3a6b3455e97595c0de2) Thanks [@list({](https://github.com/list({)! - Admin chrome polish: opt-in nav counts and avatar label cells ([#735](https://github.com/OpenSaasAU/stack/issues/735))
|
|
30
|
+
|
|
31
|
+
Two per-list opt-ins for the admin UI, both off by default.
|
|
32
|
+
|
|
33
|
+
**Nav counts** — set `ui.navCount: true` on a list to show an access-scoped
|
|
34
|
+
record count next to its nav item. The count is fetched through the secured
|
|
35
|
+
context, so it only ever reflects what the current session may see; no count
|
|
36
|
+
query runs for lists that don't opt in, and a list whose query access is
|
|
37
|
+
statically denied renders no count rather than a misleading zero.
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
lists: {
|
|
41
|
+
Post: list({
|
|
42
|
+
fields: {
|
|
43
|
+
/* ... */
|
|
44
|
+
},
|
|
45
|
+
ui: { navCount: true },
|
|
46
|
+
}),
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Avatar label cells** — set `ui.avatar: true` to render a list's label column
|
|
51
|
+
with a deterministic initials bubble ahead of the emphasized Item label. The
|
|
52
|
+
initials and colour derive from the row; the palette is Theme-token-derived (no
|
|
53
|
+
raw hex). A per-field cell override (`ui.cell`) on the label field still wins.
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
lists: {
|
|
57
|
+
|
|
58
|
+
fields: {
|
|
59
|
+
/* ... */
|
|
60
|
+
},
|
|
61
|
+
ui: { avatar: true },
|
|
62
|
+
}),
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
New exports:
|
|
67
|
+
|
|
68
|
+
- `@opensaas/stack-core`: `resolveNavCounts`, `isListQueryStaticallyDenied`
|
|
69
|
+
- `@opensaas/stack-ui`: `Avatar` primitive, `AvatarLabelCell`, and the
|
|
70
|
+
`getInitials`, `getAvatarTone`, `AVATAR_TONES` helpers. New Slots:
|
|
71
|
+
`avatar`, `cell-avatar-label`, `nav-count`.
|
|
72
|
+
|
|
73
|
+
- [#759](https://github.com/OpenSaasAU/stack/pull/759) [`b190813`](https://github.com/OpenSaasAU/stack/commit/b190813a4531bd01b3206845b2c531099e0a204a) Thanks [@borisno2](https://github.com/borisno2)! - Add custom Bulk actions from list config (admin list view)
|
|
74
|
+
|
|
75
|
+
A list can now declare list-specific Bulk actions under `ui.listView.bulkActions`. Each action's button renders in the list view's selection bar (in declaration order) alongside the built-in Delete. The action's server-side `handler` receives the selected ids and the secured context, so all its work runs through access control and hooks — a denied row is a Silent failure absorbed into the outcome, never leaked.
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
Post: list({
|
|
79
|
+
fields: { title: text(), status: select({ options: [/* ... */] }) },
|
|
80
|
+
ui: {
|
|
81
|
+
listView: {
|
|
82
|
+
bulkActions: [
|
|
83
|
+
{
|
|
84
|
+
key: 'publish',
|
|
85
|
+
label: 'Publish',
|
|
86
|
+
// Optional: `variant`, `destructive` (confirm first),
|
|
87
|
+
// `hasAccess` (server-side visibility gate).
|
|
88
|
+
handler: async ({ ids, context }) => {
|
|
89
|
+
let n = 0
|
|
90
|
+
for (const id of ids) {
|
|
91
|
+
const updated = await context.db.post.update({
|
|
92
|
+
where: { id },
|
|
93
|
+
data: { status: 'published' },
|
|
94
|
+
})
|
|
95
|
+
if (updated) n++
|
|
96
|
+
}
|
|
97
|
+
return { message: `Published ${n} of ${ids.length}` }
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
})
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Only serialisable metadata (`key`/`label`/`variant`/`destructive`) crosses to the client; the `handler`/`hasAccess` functions stay on the server. Clicking the button sends the `key` and selected ids back through the generic server action, which looks the handler up and runs it with a freshly-rebuilt secured context. Selection is enabled for a list that has custom actions even when Delete is denied. CSV export is documented as a recipe using this surface rather than shipping as a built-in.
|
|
107
|
+
|
|
108
|
+
- [#754](https://github.com/OpenSaasAU/stack/pull/754) [`f67cd79`](https://github.com/OpenSaasAU/stack/commit/f67cd798724712a90d7ada8f28202d3d6371693f) Thanks [@borisno2](https://github.com/borisno2)! - Add the Filter builder input UI for the admin list view ([#731](https://github.com/OpenSaasAU/stack/issues/731))
|
|
109
|
+
|
|
110
|
+
The admin list view now ships a `FilterBuilder` that constructs the `?search=`
|
|
111
|
+
filter query the filter engine already consumes (ADR-0017) — a free-text search
|
|
112
|
+
box plus structured field / operator / value rows. Available fields, operators,
|
|
113
|
+
and value suggestions are derived entirely from each field's self-contained
|
|
114
|
+
`getFilterSpec` (via the serializable `collectFilterSuggestions` metadata), so
|
|
115
|
+
there is no field-type `switch` and no functions cross the server/client
|
|
116
|
+
boundary. Applied filters flow through the same secured `context.db`, so
|
|
117
|
+
filtering can only ever narrow what a session may see.
|
|
118
|
+
|
|
119
|
+
`@opensaas/stack-core` gains `serializeFilterQuery(tokens)` — the exact inverse
|
|
120
|
+
of `parseFilterQuery` — so the builder produces the grammar the engine parses
|
|
121
|
+
with the quoting and operator-prefix rules kept next to the parser.
|
|
122
|
+
|
|
123
|
+
The `FilterBuilder` is composable (exported from `@opensaas/stack-ui` and
|
|
124
|
+
`@opensaas/stack-ui/standalone`) with theme-token styling and `data-slot` parts
|
|
125
|
+
for extension:
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
import { FilterBuilder } from '@opensaas/stack-ui/standalone'
|
|
129
|
+
import { collectFilterSuggestions } from '@opensaas/stack-core'
|
|
130
|
+
|
|
131
|
+
// Server component: collect serializable suggestion metadata for the list.
|
|
132
|
+
const suggestions = collectFilterSuggestions(listConfig, 'Post', config)
|
|
133
|
+
|
|
134
|
+
// Client: build and apply a `?search=` query.
|
|
135
|
+
<FilterBuilder
|
|
136
|
+
suggestions={suggestions}
|
|
137
|
+
defaultValue={search}
|
|
138
|
+
onApply={(query) => router.push(`/admin/post?search=${encodeURIComponent(query)}`)}
|
|
139
|
+
/>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The list view wires this in automatically; existing `?search=` URLs keep
|
|
143
|
+
working unchanged.
|
|
144
|
+
|
|
145
|
+
- [#746](https://github.com/OpenSaasAU/stack/pull/746) [`dcb10e2`](https://github.com/OpenSaasAU/stack/commit/dcb10e27c28a8a8f9a5e625f550ac5c750436eb6) Thanks [@borisno2](https://github.com/borisno2)! - Add the admin UI filter engine: a Filter spec field-builder contract and URL-driven server-side list filtering (ADR-0017).
|
|
146
|
+
|
|
147
|
+
Fields now declare their filtering capability through a new optional `getFilterSpec` method — a peer of `getPrismaType`/`getTypeScriptType` on the field-builder contract. It reports the operators a field supports, a pure token→condition mapper, and serializable suggestion metadata. Core field types implement it (text contains + free text, integer/decimal/timestamp/calendarDay comparisons, select/checkbox equality against enumerated values, relationship by label lookup). A field without a spec — `password`, `json`, `virtual`, or any third-party field that hasn't adopted one — is simply not filterable, so the addition degrades gracefully everywhere.
|
|
148
|
+
|
|
149
|
+
The admin list view now parses the URL filter query (the list's `search` param) through the engine and merges the result into the access-controlled query via the secured context, so filtering runs server-side and can only ever narrow — never widen — what a session may see. This replaces the previous hard-coded `type === 'text'` search; free-text behavior is now driven by each text field's Filter spec.
|
|
150
|
+
|
|
151
|
+
Grammar (ADR-0017): implicit-AND tokens, quoted multi-word values, `>`/`>=`/`<`/`<=` comparisons on numeric/date fields, and bare words as free text. Unknown syntax degrades to free text, never errors.
|
|
152
|
+
|
|
153
|
+
Multi-word free-text UX shift (intentional, per ADR-0017): bare words now combine with AND, so `hello world` requires each word to match separately (not the literal substring `hello world`). To match a contiguous phrase, quote it: `"hello world"`. A pasted URL such as `http://x` is treated as a single free-text token and searched verbatim — the `http:` prefix is not parsed as a field.
|
|
154
|
+
|
|
155
|
+
New exports from `@opensaas/stack-core`:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import {
|
|
159
|
+
parseFilterQuery, // (query) => FilterToken[] — pure
|
|
160
|
+
buildFilterWhere, // (tokens, specs) => where — pure
|
|
161
|
+
collectFilterSpecs, // (listConfig, listKey, config) => specs
|
|
162
|
+
buildListFilterWhere, // (query, listConfig, listKey, config) => where
|
|
163
|
+
collectFilterSuggestions, // serializable autocomplete metadata
|
|
164
|
+
} from '@opensaas/stack-core'
|
|
165
|
+
|
|
166
|
+
// e.g. "status:Published views:>10 author:\"Ada Lovelace\" beta"
|
|
167
|
+
const where = buildListFilterWhere(query, listConfig, listKey, config)
|
|
168
|
+
const rows = await context.db.post.findMany({ where }) // ANDed with the access filter
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Third-party field authors can implement `FilterSpec` (exported from `@opensaas/stack-core/extend`) to make their field filterable.
|
|
172
|
+
|
|
173
|
+
- [#760](https://github.com/OpenSaasAU/stack/pull/760) [`f8b6f02`](https://github.com/OpenSaasAU/stack/commit/f8b6f02c18322d0d04a7c3cc82e579d0ba9a2da9) Thanks [@borisno2](https://github.com/borisno2)! - Add inline cell editing to admin Relationship tables
|
|
174
|
+
|
|
175
|
+
Cells in a to-many Relationship table on the item view are now editable in place.
|
|
176
|
+
Click a cell to edit it, commit with Enter or blur, cancel with Escape. Each
|
|
177
|
+
commit is a single-field update on the **related** row through the secured
|
|
178
|
+
context, so the related list's own operation- and field-level update access plus
|
|
179
|
+
its hooks/validation apply — never the parent's. The update is optimistic and
|
|
180
|
+
reverts, with a visible reason, on a Silent failure (access denied / row gone) or
|
|
181
|
+
a validation error (inline field errors surface too). Committed values re-render
|
|
182
|
+
through the Cell registry, so select cells stay coloured badges.
|
|
183
|
+
|
|
184
|
+
A field the session cannot write — or a table whose related-list update access is
|
|
185
|
+
statically denied — renders read-only with no edit affordance; row-level
|
|
186
|
+
(filter-scoped) denials surface at commit as a revert. Non-editable cells keep
|
|
187
|
+
click-to-navigate; main list tables are unchanged (this is Relationship-table
|
|
188
|
+
only).
|
|
189
|
+
|
|
190
|
+
- `@opensaas/stack-core`: the generic server action gains a distinct
|
|
191
|
+
`updateRelated` result shape (`{ updated, error?, fieldErrors? }`), and
|
|
192
|
+
`checkFieldAccess` is exposed on `@opensaas/stack-core/internal` so the UI can
|
|
193
|
+
decide the edit affordance without a parallel field-access evaluator.
|
|
194
|
+
- `@opensaas/stack-ui`: `RelationshipTableClient` accepts `editableColumns`; the
|
|
195
|
+
editable cell reuses the field-component registry for its editor and the Cell
|
|
196
|
+
registry for its display (new Slots: `relationship-table-cell-display`,
|
|
197
|
+
`relationship-table-cell-editor`, `relationship-table-cell-edit-trigger`,
|
|
198
|
+
`relationship-table-cell-error`).
|
|
199
|
+
|
|
200
|
+
- [#757](https://github.com/OpenSaasAU/stack/pull/757) [`c05701e`](https://github.com/OpenSaasAU/stack/commit/c05701e523815b8f411a6d39e57bbb9317dc2a9d) Thanks [@borisno2](https://github.com/borisno2)! - Add a pre-linked create drawer to read-only Relationship tables (issue [#738](https://github.com/OpenSaasAU/stack/issues/738))
|
|
201
|
+
|
|
202
|
+
The item view's read-only Relationship tables now offer a "+ Add" control that
|
|
203
|
+
opens a drawer hosting the related list's create form, with the back-reference to
|
|
204
|
+
the current record preset and hidden. On submit the new row is created through
|
|
205
|
+
the secured context already linked to the parent, then the drawer closes and the
|
|
206
|
+
table refreshes.
|
|
207
|
+
|
|
208
|
+
Create-and-link semantics (ADR-0018): the create runs on the RELATED list, so
|
|
209
|
+
the related list's own `create` access control, hooks, and field-level access
|
|
210
|
+
apply — never the parent's. The back-reference is set on the server from the
|
|
211
|
+
field/parent id (never trusted from the client payload). The "+ Add" is shown
|
|
212
|
+
only when a back-reference exists to preset the link and the related list's
|
|
213
|
+
`create` access is not statically denied; a filter/function-scoped denial
|
|
214
|
+
surfaces at commit time as a generic error (no denied-vs-absent leak).
|
|
215
|
+
|
|
216
|
+
New generic server action (`@opensaas/stack-core`):
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
await context.serverAction({
|
|
220
|
+
listKey: 'Post', // the RELATED list
|
|
221
|
+
action: 'createRelated',
|
|
222
|
+
data: { title: 'Hello', slug: 'hello' },
|
|
223
|
+
field: 'author', // the back-reference field on Post
|
|
224
|
+
parentId: user.id, // the record being edited
|
|
225
|
+
})
|
|
226
|
+
// → { created: true, id } | { created: false, error?, fieldErrors? }
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The drawer (`RelationshipCreateDrawer` from `@opensaas/stack-ui`) mounts on the
|
|
230
|
+
existing `relationship-table-toolbar` seam and reuses the shared item-form engine
|
|
231
|
+
and field-component registry, so the related list's full validation and required
|
|
232
|
+
fields are enforced even when a required field is not one of the table's columns.
|
|
233
|
+
|
|
234
|
+
- [#756](https://github.com/OpenSaasAU/stack/pull/756) [`8199238`](https://github.com/OpenSaasAU/stack/commit/81992382290f356071955f16efd14f7771045a16) Thanks [@list({](https://github.com/list({)! - Add relationship-table row removal to the admin item view (ADR-0018)
|
|
235
|
+
|
|
236
|
+
Each read-only Relationship table row now has a ✕ removal control. By default it
|
|
237
|
+
**disconnects** the related row from the current record (non-destructive — the
|
|
238
|
+
row survives and still appears on its own list), gated on the related list's
|
|
239
|
+
update access. A per-relationship opt-in truly deletes the related row (behind a
|
|
240
|
+
confirmation, gated on the related list's delete access), or hides the control
|
|
241
|
+
entirely. Where the schema makes disconnect impossible (a required foreign key on
|
|
242
|
+
the related side) the control is hidden unless delete is opted in. Removals run
|
|
243
|
+
through the secured context, so an access-denied removal is a Silent failure: the
|
|
244
|
+
row stays with a visible reason.
|
|
245
|
+
|
|
246
|
+
Configure per relationship via `ui.itemView.removeAction`:
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
|
|
250
|
+
fields: {
|
|
251
|
+
// Default: ✕ disconnects the post (it still exists).
|
|
252
|
+
posts: relationship({ ref: 'Post.author', many: true }),
|
|
253
|
+
// Opt in to destructive delete (confirmed).
|
|
254
|
+
notes: relationship({
|
|
255
|
+
ref: 'Note.owner',
|
|
256
|
+
many: true,
|
|
257
|
+
ui: { itemView: { removeAction: 'delete' } }, // 'disconnect' (default) | 'delete' | 'none'
|
|
258
|
+
}),
|
|
259
|
+
},
|
|
260
|
+
})
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
`@opensaas/stack-core` adds a `removeRelated` server action (distinct
|
|
264
|
+
`{ removed }` result shape, like `bulkDelete`, so a redirect-on-success wrapper
|
|
265
|
+
never hijacks an in-place removal) and the `RelationshipItemViewConfig.removeAction`
|
|
266
|
+
option.
|
|
267
|
+
|
|
268
|
+
- [#745](https://github.com/OpenSaasAU/stack/pull/745) [`4d99e91`](https://github.com/OpenSaasAU/stack/commit/4d99e910b61c6196564a7248abf3d32b1d6be883) Thanks [@borisno2](https://github.com/borisno2)! - Add a Cell registry with default cells for core field types
|
|
269
|
+
|
|
270
|
+
List tables now render every value through a **Cell** resolved by a
|
|
271
|
+
cell-component registry that mirrors the form-field registry's priority chain:
|
|
272
|
+
per-field override → custom type registry → field-type registry → plain-text
|
|
273
|
+
fallback. Each core field type ships a default Cell — text (plain), integer
|
|
274
|
+
(tabular figures), select (coloured Badge), timestamp (formatted date), checkbox
|
|
275
|
+
(mark), and to-one relationship (Item label link). Unknown/third-party types
|
|
276
|
+
without a registered Cell fall back to plain text.
|
|
277
|
+
|
|
278
|
+
Select options gain optional, additive per-option UI metadata mapping a value to
|
|
279
|
+
a badge variant. Existing options keep working unchanged; unmapped options render
|
|
280
|
+
the neutral badge.
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
// opensaas.config.ts — colour a status value in list-table cells
|
|
284
|
+
status: select({
|
|
285
|
+
options: [
|
|
286
|
+
{ label: 'Draft', value: 'draft', ui: { variant: 'secondary' } },
|
|
287
|
+
{ label: 'Published', value: 'published', ui: { variant: 'success' } },
|
|
288
|
+
],
|
|
289
|
+
})
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Register a Cell for a custom/third-party field exactly as you register its form
|
|
293
|
+
component, or override a single field's Cell:
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
'use client'
|
|
297
|
+
import { registerCellComponent } from '@opensaas/stack-ui'
|
|
298
|
+
registerCellComponent('myField', MyCell)
|
|
299
|
+
|
|
300
|
+
// or per-field override (highest priority)
|
|
301
|
+
price: integer({ ui: { cell: CurrencyCell } })
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
- [#751](https://github.com/OpenSaasAU/stack/pull/751) [`20459b5`](https://github.com/OpenSaasAU/stack/commit/20459b5a7f8b2578342509442d36017cfa2f08f6) Thanks [@list({](https://github.com/list({)! - Derive the admin item view from the list shape, with read-only Relationship tables and a totals footer ([#734](https://github.com/OpenSaasAU/stack/issues/734))
|
|
305
|
+
|
|
306
|
+
A record's edit page now derives its layout from the list's shape. Scalar and
|
|
307
|
+
to-one fields stay in a details card (whole-form Save/Cancel, unchanged), and
|
|
308
|
+
each to-many relationship renders as a read-only **Relationship table**: one
|
|
309
|
+
to-many relationship gives a two-column split, none gives a single centered
|
|
310
|
+
card, several stack. Table columns default to the related list's own column
|
|
311
|
+
curation minus the back-reference to the parent, cells come from the cell
|
|
312
|
+
registry, and a totals footer always shows the row count plus sums for any
|
|
313
|
+
explicitly-configured numeric columns (each formatted by that column's Cell).
|
|
314
|
+
Rows are fetched through the secured context, so only access-visible data shows.
|
|
315
|
+
Rows are read-only here — a row click navigates to the related record.
|
|
316
|
+
|
|
317
|
+
`@opensaas/stack-core` gains additive item-view config (no breaking changes):
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
lists: {
|
|
321
|
+
|
|
322
|
+
fields: {
|
|
323
|
+
posts: relationship({
|
|
324
|
+
ref: 'Post.author',
|
|
325
|
+
many: true,
|
|
326
|
+
ui: {
|
|
327
|
+
itemView: {
|
|
328
|
+
// Override the Relationship table's columns…
|
|
329
|
+
columns: ['title', 'status', 'viewCount'],
|
|
330
|
+
// …and sum numeric columns in the totals footer.
|
|
331
|
+
sum: ['viewCount'],
|
|
332
|
+
// Or demote it back to the compact picker in the details card:
|
|
333
|
+
// displayMode: 'picker',
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
}),
|
|
337
|
+
},
|
|
338
|
+
// Reorder the Relationship-table sections:
|
|
339
|
+
ui: { itemView: { order: ['posts'] } },
|
|
340
|
+
}),
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
New `@opensaas/stack-ui` exports: `RelationshipTable`, `RelationshipTableClient`,
|
|
345
|
+
and the pure `deriveItemViewLayout` helper (with `ItemViewLayout`,
|
|
346
|
+
`ItemViewArrangement`, `RelationshipTableSection`). The Relationship table ships
|
|
347
|
+
named Slots (`relationship-table`, `relationship-table-toolbar`,
|
|
348
|
+
`relationship-table-row`, `relationship-table-cell`, `relationship-table-footer`)
|
|
349
|
+
as extension seams for the follow-up inline-edit, create-drawer, and row-removal
|
|
350
|
+
work.
|
|
351
|
+
|
|
352
|
+
- [#774](https://github.com/OpenSaasAU/stack/pull/774) [`62a1612`](https://github.com/OpenSaasAU/stack/commit/62a16127c7b6610a35fb239911eff3486de585be) Thanks [@borisno2](https://github.com/borisno2)! - Bound the admin item-view Relationship tables with a `take` and a "showing N of M" footer
|
|
353
|
+
|
|
354
|
+
The read-only Relationship tables on a record's edit page (issue [#734](https://github.com/OpenSaasAU/stack/issues/734)) previously
|
|
355
|
+
fetched every related row unbounded. They now fetch a bounded page of related rows
|
|
356
|
+
and surface the full access-scoped total in the footer.
|
|
357
|
+
|
|
358
|
+
- **Bounded fetch:** each to-many Relationship table fetches at most a default cap
|
|
359
|
+
of related rows (`DEFAULT_ITEM_VIEW_TAKE`, 10), overridable per relationship via
|
|
360
|
+
`ui.itemView.take`. Rows are still fetched through the secured context, so only
|
|
361
|
+
access-visible rows come back.
|
|
362
|
+
- **"Showing N of M" footer:** the totals footer now reads `Showing N of M rows`,
|
|
363
|
+
where N is the rendered (bounded) count and M is the full access-scoped total,
|
|
364
|
+
fetched via a filtered `_count` that folds the related list's own `query` access
|
|
365
|
+
in (mirroring the list view's count columns). A fully-denied related list reads
|
|
366
|
+
`Showing 0 of 0` and never leaks a true total. The row count is always shown,
|
|
367
|
+
including the zero-column footer path.
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
sessions: relationship({
|
|
371
|
+
ref: 'Session.user',
|
|
372
|
+
many: true,
|
|
373
|
+
// Cap this table at 5 rows; the footer still shows the full access-scoped total.
|
|
374
|
+
ui: { itemView: { take: 5 } },
|
|
375
|
+
})
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Core: `mergeIncludeWithAccessControl` now preserves a caller-supplied `take` on a
|
|
379
|
+
to-many relation include (it only narrows the fetch, never widening past the access
|
|
380
|
+
`where`), so the secured `findUnique`/`findMany` include can bound related-row reads.
|
|
381
|
+
|
|
382
|
+
- [#764](https://github.com/OpenSaasAU/stack/pull/764) [`c210319`](https://github.com/OpenSaasAU/stack/commit/c210319c3b25ff74d832d3c2ec5d3253d5d8b832) Thanks [@list({](https://github.com/list({)! - Admin list view: to-many relationship columns render an access-visible count, sort by relation count, and filter by numeric count comparisons (issue [#732](https://github.com/OpenSaasAU/stack/issues/732)). Virtual fields render via their Cell but are excluded from sorting and filtering.
|
|
383
|
+
|
|
384
|
+
A to-many relationship used as a list column now shows the count of the related rows the session may see — fetched in the SAME query via a filtered Prisma `_count`, with the related list's `query` access folded into the count's `where`, so it never counts rows the session cannot read and issues no per-row query. Clicking the column header sorts by relation `_count`, and its Filter spec offers numeric comparisons on the count (`posts:>5`) in the filter builder and in shared URLs.
|
|
385
|
+
|
|
386
|
+
Because Prisma cannot compare a relation count in a `where`, a to-many relationship's Filter spec emits a structured count marker that is resolved to an access-scoped `{ id: { in } }` before the query runs, through the secured context.
|
|
387
|
+
|
|
388
|
+
New `@opensaas/stack-core` exports: `buildRelationshipCountSelect`, `resolveRelationshipCountFilters`, `isToManyRelationshipField`, and `RELATIONSHIP_COUNT_FILTER_KEY` (with the `RelationshipCountFilterMarker` type).
|
|
389
|
+
|
|
390
|
+
```ts
|
|
391
|
+
// A to-many relationship column now shows an access-scoped count and is
|
|
392
|
+
// sortable / filterable by that count — zero config:
|
|
393
|
+
|
|
394
|
+
fields: {
|
|
395
|
+
name: text(),
|
|
396
|
+
posts: relationship({ ref: 'Post.author', many: true }),
|
|
397
|
+
},
|
|
398
|
+
})
|
|
399
|
+
// List view: the `posts` column renders the count; its header sorts by count;
|
|
400
|
+
// `posts:>5` filters by count in the builder and in a shared URL.
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Patch Changes
|
|
404
|
+
|
|
405
|
+
- [#773](https://github.com/OpenSaasAU/stack/pull/773) [`5a60291`](https://github.com/OpenSaasAU/stack/commit/5a602916f30535604b590b875c363f21930a109f) Thanks [@borisno2](https://github.com/borisno2)! - Harden `createRelated` server action: reject malformed calls that supply only one of `field`/`parentId`, and validate that the back-reference names a relationship field before injecting the parent connect.
|
|
406
|
+
|
|
407
|
+
- [#775](https://github.com/OpenSaasAU/stack/pull/775) [`2fcb582`](https://github.com/OpenSaasAU/stack/commit/2fcb5820bc00d9d432265d1ba01404097e296e8e) Thanks [@borisno2](https://github.com/borisno2)! - Return a generic "Action failed" message (and log the real error server-side) when a custom bulk-action handler throws an unexpected non-Prisma error, instead of surfacing its internal message to the client
|
|
408
|
+
|
|
409
|
+
- [#771](https://github.com/OpenSaasAU/stack/pull/771) [`85c7fc3`](https://github.com/OpenSaasAU/stack/commit/85c7fc3b3a0090a986cafa0e46b1798f237264da) Thanks [@borisno2](https://github.com/borisno2)! - Harden relationship count-filter resolution: preserve any sibling conditions co-present in a `_countFilter` marker's AND-member instead of replacing it wholesale, and document why the secured count read intentionally keeps its full projection (`context.db` does not honour `select`).
|
|
410
|
+
|
|
411
|
+
- [#780](https://github.com/OpenSaasAU/stack/pull/780) [`55d55e0`](https://github.com/OpenSaasAU/stack/commit/55d55e0a1ed9521b6e31283524d9194a9420059a) Thanks [@borisno2](https://github.com/borisno2)! - Fix a to-one relationship filter token (e.g. `author:Ada`) leaking related-list data by ANDing the related list's `query` access filter into the nested condition instead of running it unscoped.
|
|
412
|
+
|
|
413
|
+
- [#794](https://github.com/OpenSaasAU/stack/pull/794) [`96e1067`](https://github.com/OpenSaasAU/stack/commit/96e1067661c7ebc8e23896086fec7428e475dd03) Thanks [@borisno2](https://github.com/borisno2)! - Fix multi-column fields (e.g. storage `image()`/`file()` in Keystone-parity mode) writing an unrecognised value silently instead of failing validation. The column split now runs after `validateFieldRules`, not before, at the top-level and nested write paths.
|
|
414
|
+
|
|
3
415
|
## 0.30.0
|
|
4
416
|
|
|
5
417
|
### Minor Changes
|
|
@@ -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,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACtE,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAGrE;;;;;;;;;;;;;;GAcG;AAEH;;;GAGG;AACH,wBAAsB,6BAA6B,CACjD,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,KAAK,GAAE,MAAU,EAIjB,YAAY,GAAE,SAAS,MAAM,EAAO;YAeI,YAAY;cAAY,MAAM,CAAC,MAAM,2BAAe;gBAiE7F;
|
|
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,MAAM,oBAAoB,CAAA;AAGrE;;;;;;;;;;;;;;GAcG;AAEH;;;GAGG;AACH,wBAAsB,6BAA6B,CACjD,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,KAAK,GAAE,MAAU,EAIjB,YAAY,GAAE,SAAS,MAAM,EAAO;YAeI,YAAY;cAAY,MAAM,CAAC,MAAM,2BAAe;gBAiE7F;AAyDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAgB,6BAA6B,CAC3C,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACtC,uBAAuB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC5D,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACzC,MAAM,EAAE,cAAc,GACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAmEzB;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,CAgCrC"}
|
|
@@ -87,17 +87,26 @@ visitedLists = []) {
|
|
|
87
87
|
* Narrow an unknown include value to the structured object form (vs bare `true`
|
|
88
88
|
* or any other primitive). Caller-supplied includes arrive untyped at the
|
|
89
89
|
* runtime boundary, so we validate the shape here rather than casting.
|
|
90
|
+
*
|
|
91
|
+
* A numeric `take` on a to-many relation include (a caller-supplied row bound,
|
|
92
|
+
* issue #752) is carried through: it only ever NARROWS the fetched rows and can
|
|
93
|
+
* never widen past the access `where`, so preserving it is access-neutral. The
|
|
94
|
+
* access-controlled include never sets `take` itself — it originates solely
|
|
95
|
+
* from the caller — so `mergeIncludeWithAccessControl` re-attaches it below.
|
|
90
96
|
*/
|
|
91
97
|
function asEntryObject(value) {
|
|
92
98
|
if (value && typeof value === 'object') {
|
|
93
99
|
const obj = value;
|
|
94
100
|
const where = obj.where;
|
|
95
101
|
const include = obj.include;
|
|
102
|
+
const take = obj.take;
|
|
96
103
|
const entry = {};
|
|
97
104
|
if (where && typeof where === 'object')
|
|
98
105
|
entry.where = where;
|
|
99
106
|
if (include && typeof include === 'object')
|
|
100
107
|
entry.include = include;
|
|
108
|
+
if (typeof take === 'number')
|
|
109
|
+
entry.take = take;
|
|
101
110
|
return entry;
|
|
102
111
|
}
|
|
103
112
|
return null;
|
|
@@ -199,7 +208,11 @@ export function mergeIncludeWithAccessControl(callerInclude, accessControlledInc
|
|
|
199
208
|
entry.where = mergedWhere;
|
|
200
209
|
if (mergedNested && Object.keys(mergedNested).length > 0)
|
|
201
210
|
entry.include = mergedNested;
|
|
202
|
-
//
|
|
211
|
+
// Preserve a caller-supplied row bound on the relation (issue #752). It only
|
|
212
|
+
// narrows, never widens, so it rides on top of the access `where`/include.
|
|
213
|
+
if (callerEntry?.take !== undefined)
|
|
214
|
+
entry.take = callerEntry.take;
|
|
215
|
+
// A bare-`true` relation with no access filter, nested include, or row bound stays `true`.
|
|
203
216
|
merged[relationName] = Object.keys(entry).length > 0 ? entry : true;
|
|
204
217
|
}
|
|
205
218
|
return merged;
|
|
@@ -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":"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"}
|
|
@@ -113,4 +113,54 @@ describe('mergeIncludeWithAccessControl — bare-true leaf on a cyclic graph', (
|
|
|
113
113
|
expect(includeDepth(merged)).toBeLessThanOrEqual(3);
|
|
114
114
|
});
|
|
115
115
|
});
|
|
116
|
+
/**
|
|
117
|
+
* A caller-supplied `take` on a to-many relation include must survive the
|
|
118
|
+
* access-controlled merge (issue #752). It only narrows the fetched rows and can
|
|
119
|
+
* never widen past the access `where`, so the merge re-attaches it on top of the
|
|
120
|
+
* per-relation access filter — powering the item view's bounded relationship
|
|
121
|
+
* tables without dropping row-level access.
|
|
122
|
+
*/
|
|
123
|
+
describe('mergeIncludeWithAccessControl — caller take on a to-many relation (issue #752)', () => {
|
|
124
|
+
// A one-list config whose `posts` to-many is query-scoped by an access filter.
|
|
125
|
+
function scopedConfig() {
|
|
126
|
+
return {
|
|
127
|
+
db: { provider: 'sqlite', url: 'file:./dev.db' },
|
|
128
|
+
lists: {
|
|
129
|
+
User: {
|
|
130
|
+
fields: { name: { type: 'text' }, posts: rel('Post.author', true) },
|
|
131
|
+
access: { operation: { query: () => true } },
|
|
132
|
+
},
|
|
133
|
+
Post: {
|
|
134
|
+
fields: { title: { type: 'text' }, author: rel('User.posts') },
|
|
135
|
+
// Scoped read access → the merge must fold this into the relation include.
|
|
136
|
+
access: { operation: { query: () => ({ published: { equals: true } }) } },
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- minimal config for unit test
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
it('preserves the take and AND-combines it with the access where', async () => {
|
|
143
|
+
const config = scopedConfig();
|
|
144
|
+
const accessControlledInclude = await buildIncludeWithAccessControl(config.lists.User.fields, { session: null, context: makeContext() }, config, 0, ['User']);
|
|
145
|
+
const merged = mergeIncludeWithAccessControl({ posts: { take: 10 } }, accessControlledInclude, config.lists.User.fields, config);
|
|
146
|
+
// The bound rides on top of the access filter — neither is dropped. The
|
|
147
|
+
// access-controlled include also auto-nests Post's `author` back-relation.
|
|
148
|
+
expect(merged).toEqual({
|
|
149
|
+
posts: {
|
|
150
|
+
where: { published: { equals: true } },
|
|
151
|
+
include: { author: true },
|
|
152
|
+
take: 10,
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
it('drops a take for a relation whose query access is denied', async () => {
|
|
157
|
+
const config = scopedConfig();
|
|
158
|
+
// Deny Post reads entirely.
|
|
159
|
+
config.lists.Post.access = { operation: { query: () => false } };
|
|
160
|
+
const accessControlledInclude = await buildIncludeWithAccessControl(config.lists.User.fields, { session: null, context: makeContext() }, config, 0, ['User']);
|
|
161
|
+
const merged = mergeIncludeWithAccessControl({ posts: { take: 10 } }, accessControlledInclude, config.lists.User.fields, config);
|
|
162
|
+
// A denied relation is dropped wholesale — the take cannot resurrect it.
|
|
163
|
+
expect(merged).toEqual({});
|
|
164
|
+
});
|
|
165
|
+
});
|
|
116
166
|
//# sourceMappingURL=access-filter.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-filter.test.js","sourceRoot":"","sources":["../../src/access/access-filter.test.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"access-filter.test.js","sourceRoot":"","sources":["../../src/access/access-filter.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -91,12 +91,12 @@ export async function filterWritableFields(data, fieldConfigs, operation, args)
|
|
|
91
91
|
// Map each raw per-part column name contributed by a multi-column field
|
|
92
92
|
// (e.g. storage image()/file() in Keystone-parity mode) back to its OWNING
|
|
93
93
|
// declared field. These columns are injected into the write payload by the
|
|
94
|
-
// field's `splitColumns
|
|
95
|
-
// as their own entries in `fieldConfigs`, so without this map they
|
|
96
|
-
// the #564 undeclared-key reject below.
|
|
94
|
+
// field's `splitColumns`, AFTER validation (#789), and are intentionally NOT
|
|
95
|
+
// declared as their own entries in `fieldConfigs`, so without this map they
|
|
96
|
+
// would trip the #564 undeclared-key reject below.
|
|
97
97
|
//
|
|
98
98
|
// SECURITY (#568): a raw column must NOT be blanket-passed through. The hooks
|
|
99
|
-
// layer (`
|
|
99
|
+
// layer (`splitMultiColumnFields`) only gates the owning field when the
|
|
100
100
|
// LOGICAL key (e.g. `media`) is present, because it iterates declared fields,
|
|
101
101
|
// not data keys. A non-sudo caller who supplies the raw columns DIRECTLY
|
|
102
102
|
// (`data: { media_url, media_size }`) never produces that logical key, so that
|
|
@@ -141,8 +141,8 @@ export async function filterWritableFields(data, fieldConfigs, operation, args)
|
|
|
141
141
|
// they must NOT be blanket-passed through either: gate each one by its
|
|
142
142
|
// OWNING field's write access (see the SECURITY note where the map is built).
|
|
143
143
|
// This is the real gate for callers who supply the raw columns directly,
|
|
144
|
-
// because the logical-key gate in `
|
|
145
|
-
//
|
|
144
|
+
// because the logical-key gate in `splitMultiColumnFields` never fires for
|
|
145
|
+
// them. Denied (non-sudo) throws — same fail-loud behaviour as a denied
|
|
146
146
|
// declared field (#568); allowed (or sudo, via `checkFieldAccess`) passes
|
|
147
147
|
// through, preserving the legitimate multi-column write path.
|
|
148
148
|
const splitColumnOwner = splitColumnOwners.get(fieldName);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-access.js","sourceRoot":"","sources":["../../src/access/field-access.ts"],"names":[],"mappings":"AAEA,iFAAiF;AACjF,8EAA8E;AAC9E,2BAA2B;AAC3B,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAEnD;;;;;;;;;GASG;AAEH;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,WAAoC,EACpC,SAAuC,EACvC,IAKC;IAED,iCAAiC;IACjC,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACzB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,IAAI,CAAA,CAAC,8BAA8B;IAC5C,CAAC;IAED,MAAM,aAAa,GAAG,WAAW,CAAC,SAAS,CAAC,CAAA;IAC5C,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO,IAAI,CAAA,CAAC,yCAAyC;IACvD,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC;QACjC,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,SAAS;KAC6B,CAAC,CAAA;IAEzC,kCAAkC;IAClC,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;QACrB,OAAO,KAAK,CAAA;IACd,CAAC;IAED,kCAAkC;IAClC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,mDAAmD;IACnD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa,CAAC,IAA6B,EAAE,MAA+B;IACnF,KAAK,MAAM,CAAC,GAAG,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACtD,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;YACxD,kDAAkD;YAClD,IAAI,QAAQ,IAAI,SAAS,EAAE,CAAC;gBAC1B,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC,MAAM,EAAE,CAAC;oBACnC,OAAO,KAAK,CAAA;gBACd,CAAC;YACH,CAAC;iBAAM,IAAI,KAAK,IAAI,SAAS,EAAE,CAAC;gBAC9B,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC,GAAG,EAAE,CAAC;oBAChC,OAAO,KAAK,CAAA;gBACd,CAAC;YACH,CAAC;YACD,qCAAqC;QACvC,CAAC;aAAM,CAAC;YACN,wBAAwB;YACxB,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,SAAS,EAAE,CAAC;gBAC5B,OAAO,KAAK,CAAA;YACd,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,IAAO,EACP,YAOC,EACD,SAA8B,EAC9B,IAKC;IAED,MAAM,QAAQ,GAA4B,EAAE,CAAA;IAE5C,oDAAoD;IACpD,6EAA6E;IAC7E,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAU,CAAA;IAC1C,wEAAwE;IACxE,2EAA2E;IAC3E,2EAA2E;IAC3E
|
|
1
|
+
{"version":3,"file":"field-access.js","sourceRoot":"","sources":["../../src/access/field-access.ts"],"names":[],"mappings":"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"}
|