@opensaas/stack-core 0.21.0 → 0.23.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 +268 -0
- package/CLAUDE.md +18 -15
- package/dist/access/field-visibility.d.ts.map +1 -1
- package/dist/access/field-visibility.js +29 -6
- package/dist/access/field-visibility.js.map +1 -1
- package/dist/access/multi-column-read-write.test.d.ts +2 -0
- package/dist/access/multi-column-read-write.test.d.ts.map +1 -0
- package/dist/access/multi-column-read-write.test.js +149 -0
- package/dist/access/multi-column-read-write.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/types.d.ts +289 -1
- package/dist/config/types.d.ts.map +1 -1
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +31 -0
- package/dist/context/index.js.map +1 -1
- package/dist/extend.d.ts +1 -1
- package/dist/extend.d.ts.map +1 -1
- package/dist/fields/format-prisma-default.d.ts +35 -0
- package/dist/fields/format-prisma-default.d.ts.map +1 -0
- package/dist/fields/format-prisma-default.js +52 -0
- package/dist/fields/format-prisma-default.js.map +1 -0
- package/dist/fields/format-prisma-default.test.d.ts +2 -0
- package/dist/fields/format-prisma-default.test.d.ts.map +1 -0
- package/dist/fields/format-prisma-default.test.js +54 -0
- package/dist/fields/format-prisma-default.test.js.map +1 -0
- package/dist/fields/index.d.ts +1 -1
- package/dist/fields/index.d.ts.map +1 -1
- package/dist/fields/index.js +54 -16
- package/dist/fields/index.js.map +1 -1
- package/dist/fields/select.test.js +85 -0
- package/dist/fields/select.test.js.map +1 -1
- package/dist/fields/text-keystone-compat.test.d.ts +2 -0
- package/dist/fields/text-keystone-compat.test.d.ts.map +1 -0
- package/dist/fields/text-keystone-compat.test.js +93 -0
- package/dist/fields/text-keystone-compat.test.js.map +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +60 -16
- 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 +7 -0
- package/dist/index.js.map +1 -1
- package/dist/index.test.d.ts +2 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/index.test.js +33 -0
- package/dist/index.test.js.map +1 -0
- package/dist/mcp/handler.js +0 -1
- package/dist/mcp/handler.js.map +1 -1
- package/package.json +1 -1
- package/src/access/field-visibility.ts +28 -6
- package/src/access/multi-column-read-write.test.ts +255 -0
- package/src/config/index.ts +2 -0
- package/src/config/types.ts +291 -0
- package/src/context/index.ts +45 -0
- package/src/extend.ts +6 -1
- package/src/fields/format-prisma-default.test.ts +64 -0
- package/src/fields/format-prisma-default.ts +67 -0
- package/src/fields/index.ts +65 -18
- package/src/fields/select.test.ts +99 -0
- package/src/fields/text-keystone-compat.test.ts +126 -0
- package/src/hooks/index.ts +60 -17
- package/src/index.test.ts +50 -0
- package/src/index.ts +17 -1
- package/src/mcp/handler.ts +0 -2
- package/tests/context.test.ts +80 -1
- package/tsconfig.tsbuildinfo +1 -1
package/.turbo/turbo-build.log
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,273 @@
|
|
|
1
1
|
# @opensaas/stack-core
|
|
2
2
|
|
|
3
|
+
## 0.23.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#535](https://github.com/OpenSaasAU/stack/pull/535) [`da4ba52`](https://github.com/OpenSaasAU/stack/commit/da4ba529161e2c8702e4c62ae1594e300f32cbb1) Thanks [@borisno2](https://github.com/borisno2)! - context.db findUnique/findMany now warn (once per list+op) when passed an ignored `select` — narrow reads via `include` or a fragment `query`.
|
|
8
|
+
|
|
9
|
+
## 0.22.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [#497](https://github.com/OpenSaasAU/stack/pull/497) [`be4181a`](https://github.com/OpenSaasAU/stack/commit/be4181ada3f2d6386052df4d4869ad150d360f89) Thanks [@{](https://github.com/{)! - Derive the auth plugin's Auth lists from the better-auth config
|
|
14
|
+
|
|
15
|
+
`authPlugin` now mirrors the better-auth config a developer writes instead of hardcoding the keys `User`/`Session`/`Account`/`Verification`. Per-model `modelName` becomes the OpenSaaS list key (and a table `@@map`), and the `fields` column map becomes per-field `@map`s. The plugin only ever adds/extends its own derived keys, so an app's separate domain `User` is never overwritten. The runtime `getUser`/`getCurrentUser` helpers now resolve the user list key from the configured user model instead of a hardcoded `'user'`.
|
|
16
|
+
|
|
17
|
+
Default behaviour (no overrides) is unchanged: the lists are still keyed `User`/`Session`/`Account`/`Verification` with the original field shapes and no `@@map`.
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// Adopt existing better-auth tables without a destructive migration
|
|
21
|
+
authPlugin({
|
|
22
|
+
modelName: 'AuthUser', fields: { name: 'full_name' } },
|
|
23
|
+
session: { modelName: 'AuthSession', fields: { userId: 'user_id' } },
|
|
24
|
+
account: { modelName: 'AuthAccount' },
|
|
25
|
+
verification: { modelName: 'AuthVerification' },
|
|
26
|
+
})
|
|
27
|
+
// -> lists keyed AuthUser/AuthSession/AuthAccount/AuthVerification
|
|
28
|
+
// with @@map + column @map matching the live tables
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Lists also gain a model-level `db.map` option, which emits a `@@map("...")` on the generated Prisma model so a list key can differ from its physical table name.
|
|
32
|
+
|
|
33
|
+
- [#498](https://github.com/OpenSaasAU/stack/pull/498) [`dc51f23`](https://github.com/OpenSaasAU/stack/commit/dc51f237323ee53a705c4b9831dd8db85efd9bc1) Thanks [@borisno2](https://github.com/borisno2)! - Add an `output` config block so `opensaas generate` can relocate the generated Prisma schema and `.opensaas` bundle (e.g. to coexist with an existing Keystone `prisma/` during migration)
|
|
34
|
+
|
|
35
|
+
Set `output.prismaSchema` and/or `output.opensaasDir` in `opensaas.config.ts` to move where the generator writes. Defaults are unchanged (`prisma/schema.prisma`, `.opensaas/`) when the block is omitted. The generated files' cross-references follow the configured locations: `context.ts`/`prisma-extensions.ts` import `opensaas.config` from the resolved bundle, the Prisma client `generator { output }` points back at the relocated bundle, and the top-level `prisma.config.ts` references the configured schema directory so `prisma` CLI commands keep working.
|
|
36
|
+
|
|
37
|
+
The pre-existing top-level `opensaasPath` option is preserved: the effective `.opensaas` bundle directory resolves as `output.opensaasDir` > `opensaasPath` > the default `.opensaas`. Setting `opensaasPath` alone still relocates the bundle through the CLI exactly as before; `output.opensaasDir` overrides it when both are set.
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
export default config({
|
|
41
|
+
output: {
|
|
42
|
+
prismaSchema: 'prisma-opensaas/schema.prisma',
|
|
43
|
+
opensaasDir: 'generated/opensaas',
|
|
44
|
+
},
|
|
45
|
+
db: {
|
|
46
|
+
/* ... */
|
|
47
|
+
},
|
|
48
|
+
lists: {
|
|
49
|
+
/* ... */
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- [#511](https://github.com/OpenSaasAU/stack/pull/511) [`696f5c0`](https://github.com/OpenSaasAU/stack/commit/696f5c08c37d4a18107e48cb6b360c9492c7425c) Thanks [@borisno2](https://github.com/borisno2)! - Add non-destructive multi-column mode to `image()` / `file()` for adopting an existing Keystone database without dropping columns (ADR-0006).
|
|
55
|
+
|
|
56
|
+
Keystone stores an image across seven per-part columns (`_url`, `_width`, `_height`, `_filesize`, `_contentType`, `_contentDisposition`, `_pathname`) and a file across three (`_filename`, `_filesize`, `_url`). By default `image()`/`file()` still back a single `Json?` column (greenfield unchanged). Set `db.columns: 'keystone'` to map the field onto the existing per-part columns in place — assembled into an `ImageMetadata`/`FileMetadata` on read and split back on write — so a migrating project reaches a clean schema diff with no data migration and no re-upload of existing assets.
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { image, file } from '@opensaas/stack-storage/fields'
|
|
60
|
+
|
|
61
|
+
fields: {
|
|
62
|
+
// Maps onto image_url, image_width, … image_pathname in place.
|
|
63
|
+
avatar: image({ storage: 'images', db: { columns: 'keystone' } }),
|
|
64
|
+
|
|
65
|
+
// Per-part @map names are configurable for non-default column names.
|
|
66
|
+
cover: image({
|
|
67
|
+
storage: 'images',
|
|
68
|
+
db: { columns: { mode: 'keystone', map: { url: 'cover_link' } } },
|
|
69
|
+
}),
|
|
70
|
+
|
|
71
|
+
resume: file({ storage: 'documents', db: { columns: 'keystone' } }),
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
No-re-upload guarantee (both modes): an already-shaped metadata value — or, in multi-column mode, populated columns — is authoritative and never triggers a storage upload; only a `File`-like input uploads.
|
|
76
|
+
|
|
77
|
+
Adds a multi-column field-emission contract (`getPrismaColumns`) plus `getColumnNames`/`assembleColumns`/`splitColumns` to the field-authoring surface so any field can map onto several physical columns. The generator emits one `@map`-ped Prisma line per column; reads assemble the logical value from the raw columns and strip them from the result; writes split the logical value back across the columns.
|
|
78
|
+
|
|
79
|
+
- [#499](https://github.com/OpenSaasAU/stack/pull/499) [`f9e0505`](https://github.com/OpenSaasAU/stack/commit/f9e05053c75c76781751d5d9e5d1ed5cd9be635f) Thanks [@borisno2](https://github.com/borisno2)! - Add opt-in `db.keystoneCompat` mode for Keystone-compatible empty-string text defaults
|
|
80
|
+
|
|
81
|
+
When migrating from Keystone 6, every non-null text column carries an implicit empty-string default. Set `db: { keystoneCompat: true }` to mirror that: any non-null `text()` column without an explicit `defaultValue` now generates `String @default("")`, so a migrating schema reaches parity without hand-setting `defaultValue: ''` on dozens of columns.
|
|
82
|
+
|
|
83
|
+
The mode is off by default (greenfield schemas stay clean) and never affects nullable text, fields with an explicit `defaultValue`, or any non-text field — an explicit `text({ defaultValue: 'x' })` always wins.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
export default config({
|
|
87
|
+
db: {
|
|
88
|
+
provider: 'postgresql',
|
|
89
|
+
keystoneCompat: true, // non-null text without a default → @default("")
|
|
90
|
+
prismaClientConstructor: (PrismaClient) => {
|
|
91
|
+
// ... adapter setup
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
lists: {
|
|
95
|
+
Account: list({
|
|
96
|
+
fields: {
|
|
97
|
+
// required text → String @default("")
|
|
98
|
+
name: text({ validation: { isRequired: true } }),
|
|
99
|
+
// explicit default still wins → String @default("PLEASE_UPDATE")
|
|
100
|
+
status: text({ validation: { isRequired: true }, defaultValue: 'PLEASE_UPDATE' }),
|
|
101
|
+
// nullable text is untouched → String?
|
|
102
|
+
bio: text(),
|
|
103
|
+
},
|
|
104
|
+
}),
|
|
105
|
+
},
|
|
106
|
+
})
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
See ADR-0004 for the full Keystone-compatible generator defaults.
|
|
110
|
+
|
|
111
|
+
- [#501](https://github.com/OpenSaasAU/stack/pull/501) [`e30f6a1`](https://github.com/OpenSaasAU/stack/commit/e30f6a1ef69dc65ae68b37539fa74c3f97823cfd) Thanks [@borisno2](https://github.com/borisno2)! - Auto-timestamps are now OFF by default; opt in with `db.timestamps`
|
|
112
|
+
|
|
113
|
+
The generator no longer appends `createdAt`/`updatedAt` to every model. This matches
|
|
114
|
+
Keystone 6 (which never adds them automatically) and keeps Keystone → stack migrations
|
|
115
|
+
non-destructive. A list opts in either by declaring the fields itself or by enabling the
|
|
116
|
+
new `db.timestamps` flag. See ADR-0004.
|
|
117
|
+
|
|
118
|
+
Note: this changes a long-standing default. Existing apps that relied on auto-injected
|
|
119
|
+
timestamps should set `db: { timestamps: true }` to keep them.
|
|
120
|
+
|
|
121
|
+
Enable globally:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
export default config({
|
|
125
|
+
db: {
|
|
126
|
+
provider: 'postgresql',
|
|
127
|
+
timestamps: true, // re-enable auto createdAt/updatedAt for all lists
|
|
128
|
+
// ...
|
|
129
|
+
},
|
|
130
|
+
lists: {
|
|
131
|
+
/* ... */
|
|
132
|
+
},
|
|
133
|
+
})
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Override per list (takes precedence over the global setting):
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
lists: {
|
|
140
|
+
// Opt this one list out even though timestamps are on globally
|
|
141
|
+
Production: list({
|
|
142
|
+
fields: { name: text() },
|
|
143
|
+
db: { timestamps: false },
|
|
144
|
+
}),
|
|
145
|
+
// Opt this one list in even though the global default is off
|
|
146
|
+
Audited: list({
|
|
147
|
+
fields: { name: text() },
|
|
148
|
+
db: { timestamps: true },
|
|
149
|
+
}),
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
When timestamps are enabled and a list already declares its own `createdAt`/`updatedAt`
|
|
154
|
+
field, the auto column is skipped for the declared field(s) so Prisma never sees a
|
|
155
|
+
duplicate (`P1012`):
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
lists: {
|
|
159
|
+
Post: list({
|
|
160
|
+
fields: {
|
|
161
|
+
title: text(),
|
|
162
|
+
createdAt: timestamp(), // kept as declared; no duplicate auto column
|
|
163
|
+
},
|
|
164
|
+
}),
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The decision is exposed as a pure, testable predicate `resolveListTimestamps(listConfig, dbConfig)`
|
|
169
|
+
from `@opensaas/stack-cli`, and `DatabaseConfig` is now re-exported from `@opensaas/stack-core`.
|
|
170
|
+
|
|
171
|
+
- [#503](https://github.com/OpenSaasAU/stack/pull/503) [`f471e3c`](https://github.com/OpenSaasAU/stack/commit/f471e3c95eee2254ac9fde04adc8c5693240e293) Thanks [@borisno2](https://github.com/borisno2)! - Add `select()` db options for Keystone schema parity: `db.isNullable` and `db.enumName`.
|
|
172
|
+
|
|
173
|
+
`db.isNullable: true` forces the nullable `?` on the generated column even when a
|
|
174
|
+
`defaultValue` is present. The default behaviour is unchanged — a select with a
|
|
175
|
+
`defaultValue` still generates NOT NULL unless you opt in explicitly:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
// Optional select with a default, kept nullable for data containing NULLs
|
|
179
|
+
status: select({
|
|
180
|
+
options: [
|
|
181
|
+
{ label: 'Draft', value: 'draft' },
|
|
182
|
+
{ label: 'Published', value: 'published' },
|
|
183
|
+
],
|
|
184
|
+
defaultValue: 'draft',
|
|
185
|
+
db: { isNullable: true },
|
|
186
|
+
})
|
|
187
|
+
// Generates: status String? @default("draft")
|
|
188
|
+
|
|
189
|
+
// Enum-backed equivalent
|
|
190
|
+
status: select({
|
|
191
|
+
options: [{ label: 'Open', value: 'open' }],
|
|
192
|
+
defaultValue: 'open',
|
|
193
|
+
db: { type: 'enum', isNullable: true },
|
|
194
|
+
})
|
|
195
|
+
// Generates: status <Enum>? @default(open)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
`db.enumName` overrides the derived `<List><Field>` name of the generated Prisma
|
|
199
|
+
enum for native-enum selects, renaming both the `enum` block and every reference
|
|
200
|
+
to it in the owning model — useful for matching a live DB enum (e.g. Keystone's
|
|
201
|
+
`…Type` suffix):
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
status: select({
|
|
205
|
+
options: [
|
|
206
|
+
{ label: 'Open', value: 'open' },
|
|
207
|
+
{ label: 'Closed', value: 'closed' },
|
|
208
|
+
],
|
|
209
|
+
db: { type: 'enum', enumName: 'AccountNoteStatusType' },
|
|
210
|
+
})
|
|
211
|
+
// Generates: enum AccountNoteStatusType { ... } and the column references it
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
- [#493](https://github.com/OpenSaasAU/stack/pull/493) [`acb6100`](https://github.com/OpenSaasAU/stack/commit/acb6100a078aca29e94a82ebe607d2d4f8683af2) Thanks [@borisno2](https://github.com/borisno2)! - Honour `defaultValue` for `text()`, `integer()`, and `json()` fields in the generated Prisma schema
|
|
215
|
+
|
|
216
|
+
These three field builders previously dropped `defaultValue` and emitted no `@default(...)`. They now serialise the configured default into a Prisma `@default(...)` literal via a new shared, pure `formatPrismaDefault` module, matching Keystone 6 conventions. The nullable `?` modifier is preserved independently of the default, and fields without a `defaultValue` still emit no `@default(...)`.
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
fields: {
|
|
220
|
+
// Int @default(3550)
|
|
221
|
+
quota: integer({ defaultValue: 3550 }),
|
|
222
|
+
// String @default("PLEASE_UPDATE")
|
|
223
|
+
status: text({ defaultValue: 'PLEASE_UPDATE' }),
|
|
224
|
+
// Json? @default("[1,2,3,4,5]") — Keystone's space-free JSON literal
|
|
225
|
+
limits: json({ defaultValue: [1, 2, 3, 4, 5] }),
|
|
226
|
+
// Json? @default("[]")
|
|
227
|
+
tags: json({ defaultValue: [] }),
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
See ADR-0004 for the Keystone-compatibility rationale.
|
|
232
|
+
|
|
233
|
+
- [#502](https://github.com/OpenSaasAU/stack/pull/502) [`593390c`](https://github.com/OpenSaasAU/stack/commit/593390c57d9844ca7ada8f45b340c849f1d8d647) Thanks [@{](https://github.com/{)! - Add `authPlugin` schema placement so Auth lists can adopt an existing non-`public` better-auth layout (clean-diff adoption)
|
|
234
|
+
|
|
235
|
+
The auth lists can now be placed in a non-`public` Postgres schema (e.g. `auth`) so they diff CLEAN against a separate-schema better-auth installation. A plugin-level `schema` option applies `@@schema(...)` to all generated Auth lists, with a per-list override.
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
authPlugin({
|
|
239
|
+
schema: 'auth', // all Auth lists get @@schema("auth")
|
|
240
|
+
modelName: 'AuthUser' },
|
|
241
|
+
session: { modelName: 'AuthSession' },
|
|
242
|
+
account: { modelName: 'AuthAccount' },
|
|
243
|
+
// per-model override: relocate one list to a different schema
|
|
244
|
+
verification: { modelName: 'AuthVerification', schema: 'auth_internal' },
|
|
245
|
+
})
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
The plugin's `beforeGenerate` hook wires the datasource `schemas` array (always including `public`) and defaults any list without an explicit `db.schema` to `public`, producing a valid multi-schema Prisma schema. With no `schema` option the output is unchanged (greenfield default stays in `public`, no `@@schema`).
|
|
249
|
+
|
|
250
|
+
Core support added for this (mirroring the `db.map` → `@@map` work):
|
|
251
|
+
- List-level `db.schema` → the Prisma generator emits `@@schema("...")` on the model.
|
|
252
|
+
- Database-level `db.schemas` → the generator emits the datasource `schemas = [...]` array and enables the `multiSchema` preview feature.
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
// Core/generator building blocks
|
|
256
|
+
db: { provider: 'postgresql', schemas: ['public', 'auth'] }
|
|
257
|
+
AuthUser: list({ fields: { ... }, db: { map: 'AuthUser', schema: 'auth' } })
|
|
258
|
+
// Generates: model AuthUser { ... @@map("AuthUser") @@schema("auth") }
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Patch Changes
|
|
262
|
+
|
|
263
|
+
- [#500](https://github.com/OpenSaasAU/stack/pull/500) [`309c666`](https://github.com/OpenSaasAU/stack/commit/309c666388b71e2bfbe16b7da3ee0f923b3bf716) Thanks [@borisno2](https://github.com/borisno2)! - Re-export the fragment query API (`defineFragment`, `runQuery`, `runQueryOne`, and the `ResultOf`, `RelationSelector`, `QueryArgs` types) from the package root so the documented `import { defineFragment, runQuery, runQueryOne, type ResultOf } from '@opensaas/stack-core'` resolves.
|
|
264
|
+
|
|
265
|
+
- [#511](https://github.com/OpenSaasAU/stack/pull/511) [`696f5c0`](https://github.com/OpenSaasAU/stack/commit/696f5c08c37d4a18107e48cb6b360c9492c7425c) Thanks [@borisno2](https://github.com/borisno2)! - Fix field-level write-access bypass for multi-column `image()`/`file()` fields. The per-part column split now respects the field's own `create`/`update` access (denied fields write none of their columns), matching single-column behaviour.
|
|
266
|
+
|
|
267
|
+
Note the known lossy multi-column round-trip when assembling legacy Keystone columns: `originalFilename` collapses to `filename`, `uploadedAt` is `''`, and a NULL `contentType` reads back as `application/octet-stream`.
|
|
268
|
+
|
|
269
|
+
- [#518](https://github.com/OpenSaasAU/stack/pull/518) [`d152203`](https://github.com/OpenSaasAU/stack/commit/d1522035e21b6ad7ad1b89b05264c54c13dadcf1) Thanks [@borisno2](https://github.com/borisno2)! - Remove leftover debug console.log statements from runtime code (password field resolveInput and MCP tool call handler)
|
|
270
|
+
|
|
3
271
|
## 0.21.0
|
|
4
272
|
|
|
5
273
|
### Minor Changes
|
package/CLAUDE.md
CHANGED
|
@@ -173,21 +173,27 @@ const prismaType = field.getPrismaType(fieldName)
|
|
|
173
173
|
|
|
174
174
|
1. List `resolveInput`
|
|
175
175
|
2. Field `resolveInput` (e.g., hash password)
|
|
176
|
-
3. List `
|
|
177
|
-
4. Field
|
|
178
|
-
5. Field
|
|
179
|
-
6. Field
|
|
180
|
-
7.
|
|
181
|
-
8.
|
|
182
|
-
9.
|
|
183
|
-
10.
|
|
176
|
+
3. List `validate`
|
|
177
|
+
4. Field `validate`
|
|
178
|
+
5. Field validation (isRequired, length, min/max)
|
|
179
|
+
6. Field-level access control (filter writable fields)
|
|
180
|
+
7. Field `beforeOperation`
|
|
181
|
+
8. List `beforeOperation`
|
|
182
|
+
9. **Database operation**
|
|
183
|
+
10. List `afterOperation`
|
|
184
|
+
11. Field `afterOperation`
|
|
184
185
|
|
|
185
186
|
### Hook Execution Order (Read)
|
|
186
187
|
|
|
188
|
+
Reads run no `afterOperation` (list or field):
|
|
189
|
+
|
|
187
190
|
1. **Database operation**
|
|
188
191
|
2. Field-level access control (filter readable fields)
|
|
189
192
|
3. Field `resolveOutput`
|
|
190
|
-
|
|
193
|
+
|
|
194
|
+
### Narrowing Reads (`select` is not honoured)
|
|
195
|
+
|
|
196
|
+
`context.db` reads (`findUnique`, `findMany`) do **not** apply Prisma's `select` semantics. Narrow a read with `include` (for relationships) or a fragment `query` instead. Passing `select` is a visible no-op: the op logs a one-time `console.warn` and still returns the full, access-filtered record. Field-level visibility is always enforced by access control regardless of `select`, so there is no leak — only a correctness/perf footgun the warning surfaces.
|
|
191
197
|
|
|
192
198
|
### Context Type Safety
|
|
193
199
|
|
|
@@ -339,17 +345,14 @@ User: list({
|
|
|
339
345
|
})
|
|
340
346
|
|
|
341
347
|
// Usage
|
|
342
|
-
const user = await context.db.user.findUnique({
|
|
343
|
-
|
|
344
|
-
select: { firstName: true, lastName: true, fullName: true }, // fullName computed on demand
|
|
345
|
-
})
|
|
346
|
-
console.log(user.fullName) // "John Doe"
|
|
348
|
+
const user = await context.db.user.findUnique({ where: { id } })
|
|
349
|
+
console.log(user.fullName) // "John Doe" — computed via resolveOutput on every read
|
|
347
350
|
```
|
|
348
351
|
|
|
349
352
|
**Key characteristics:**
|
|
350
353
|
|
|
351
354
|
- Not stored in database (no Prisma column created)
|
|
352
|
-
-
|
|
355
|
+
- Computed via `resolveOutput` on every read (`select` is not honoured — narrow with `include`/fragment `query`)
|
|
353
356
|
- Must provide `type` (TypeScript type string) and `resolveOutput` hook
|
|
354
357
|
- Can optionally provide `resolveInput` for write side effects
|
|
355
358
|
- Useful for derived values, computed properties, and external API sync
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-visibility.d.ts","sourceRoot":"","sources":["../../src/access/field-visibility.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAqGrE;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,IAAI,EAAE,CAAC,EACP,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACzC,IAAI,EAAE;IACJ,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;IACvB,OAAO,EAAE,aAAa,GAAG;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAC/C,EACD,MAAM,CAAC,EAAE,cAAc,EACvB,KAAK,GAAE,MAAU,EACjB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"field-visibility.d.ts","sourceRoot":"","sources":["../../src/access/field-visibility.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAqGrE;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,IAAI,EAAE,CAAC,EACP,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACzC,IAAI,EAAE;IACJ,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;IACvB,OAAO,EAAE,aAAa,GAAG;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAC/C,EACD,MAAM,CAAC,EAAE,cAAc,EACvB,KAAK,GAAE,MAAU,EACjB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAwJrB"}
|
|
@@ -57,8 +57,31 @@ async function resolveReadableFieldValue(params) {
|
|
|
57
57
|
export async function filterReadableFields(item, fieldConfigs, args, config, depth = 0, listKey) {
|
|
58
58
|
const filtered = {};
|
|
59
59
|
const MAX_DEPTH = 5; // Prevent infinite recursion
|
|
60
|
+
// Multi-column fields (e.g. storage image()/file() in Keystone-parity mode)
|
|
61
|
+
// back several physical columns rather than one. Before the per-field pass,
|
|
62
|
+
// assemble each such field's logical value from its raw columns and remove the
|
|
63
|
+
// raw columns from the working row, so only the assembled value is exposed
|
|
64
|
+
// (the raw per-part columns never leak). The assembled value then flows
|
|
65
|
+
// through the normal read-access + resolveOutput path under the field's own
|
|
66
|
+
// key. See ADR-0006.
|
|
67
|
+
const workingItem = { ...item };
|
|
68
|
+
for (const [fieldName, fieldConfig] of Object.entries(fieldConfigs)) {
|
|
69
|
+
if (!fieldConfig.assembleColumns || !fieldConfig.getColumnNames)
|
|
70
|
+
continue;
|
|
71
|
+
const columnNames = fieldConfig.getColumnNames(fieldName);
|
|
72
|
+
// Only assemble when the raw columns are present in the row (i.e. they were
|
|
73
|
+
// selected); otherwise leave the field absent from the result.
|
|
74
|
+
const hasAnyColumn = columnNames.some((name) => name in workingItem);
|
|
75
|
+
if (!hasAnyColumn)
|
|
76
|
+
continue;
|
|
77
|
+
const assembled = fieldConfig.assembleColumns(fieldName, workingItem);
|
|
78
|
+
for (const name of columnNames) {
|
|
79
|
+
delete workingItem[name];
|
|
80
|
+
}
|
|
81
|
+
workingItem[fieldName] = assembled;
|
|
82
|
+
}
|
|
60
83
|
// Process existing fields from the database result
|
|
61
|
-
for (const [fieldName, value] of Object.entries(
|
|
84
|
+
for (const [fieldName, value] of Object.entries(workingItem)) {
|
|
62
85
|
const fieldConfig = fieldConfigs[fieldName];
|
|
63
86
|
// Always include id, createdAt, updatedAt
|
|
64
87
|
if (['id', 'createdAt', 'updatedAt'].includes(fieldName)) {
|
|
@@ -78,7 +101,7 @@ export async function filterReadableFields(item, fieldConfigs, args, config, dep
|
|
|
78
101
|
// Gate the relationship on read access before recursing.
|
|
79
102
|
const canRead = await checkFieldAccess(fieldConfig?.access, 'read', {
|
|
80
103
|
...args,
|
|
81
|
-
item,
|
|
104
|
+
item: workingItem,
|
|
82
105
|
});
|
|
83
106
|
if (!canRead) {
|
|
84
107
|
continue;
|
|
@@ -108,8 +131,8 @@ export async function filterReadableFields(item, fieldConfigs, args, config, dep
|
|
|
108
131
|
fieldConfig,
|
|
109
132
|
fieldName,
|
|
110
133
|
value,
|
|
111
|
-
accessItem:
|
|
112
|
-
hookItem:
|
|
134
|
+
accessItem: workingItem,
|
|
135
|
+
hookItem: workingItem,
|
|
113
136
|
listKey,
|
|
114
137
|
args,
|
|
115
138
|
});
|
|
@@ -132,7 +155,7 @@ export async function filterReadableFields(item, fieldConfigs, args, config, dep
|
|
|
132
155
|
// without one there is nothing to add to the result.
|
|
133
156
|
if (!(fieldConfig.hooks?.resolveOutput && listKey)) {
|
|
134
157
|
// Still evaluate read access to preserve any access-fn side effects.
|
|
135
|
-
await checkFieldAccess(fieldConfig.access, 'read', { ...args, item });
|
|
158
|
+
await checkFieldAccess(fieldConfig.access, 'read', { ...args, item: workingItem });
|
|
136
159
|
continue;
|
|
137
160
|
}
|
|
138
161
|
// Check read access and compute the value via the shared helper. Virtual
|
|
@@ -141,7 +164,7 @@ export async function filterReadableFields(item, fieldConfigs, args, config, dep
|
|
|
141
164
|
fieldConfig,
|
|
142
165
|
fieldName,
|
|
143
166
|
value: undefined, // Virtual fields don't have a database value
|
|
144
|
-
accessItem:
|
|
167
|
+
accessItem: workingItem,
|
|
145
168
|
hookItem: filtered,
|
|
146
169
|
listKey,
|
|
147
170
|
args,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-visibility.js","sourceRoot":"","sources":["../../src/access/field-visibility.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAmCpD;;;;;;;;;;;;;GAaG;AACH,KAAK,UAAU,yBAAyB,CAAC,MAQxC;IACC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,MAAM,CAAA;IAErF,kEAAkE;IAClE,MAAM,OAAO,GAAG,MAAM,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE;QAClE,GAAG,IAAI;QACP,IAAI,EAAE,UAAU;KACjB,CAAC,CAAA;IAEF,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;IAC5B,CAAC;IAED,sCAAsC;IACtC,IAAI,WAAW,EAAE,KAAK,EAAE,aAAa,IAAI,OAAO,EAAE,CAAC;QACjD,6CAA6C;QAC7C,yEAAyE;QACzE,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,aAAoD,CAAA;QACnF,iFAAiF;QACjF,qDAAqD;QACrD,IAAI,CAAC,OAAO,CAAC,qBAAqB,CAAC,KAAK,EAAE,CAAA;QAC1C,IAAI,CAAC;YACH,4DAA4D;YAC5D,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,OAAO,CACpC,IAAI,CAAC;gBACH,KAAK;gBACL,SAAS,EAAE,OAAO;gBAClB,SAAS;gBACT,OAAO;gBACP,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,IAAI,CAAC,OAAO;aACtB,CAAC,CACH,CAAA;YACD,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAA;QAC5C,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,OAAO,CAAC,qBAAqB,CAAC,KAAK,EAAE,CAAA;QAC5C,CAAC;IACH,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;AAClC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,IAAO,EACP,YAAyC,EACzC,IAGC,EACD,MAAuB,EACvB,QAAgB,CAAC,EACjB,OAAgB;IAEhB,MAAM,QAAQ,GAA4B,EAAE,CAAA;IAC5C,MAAM,SAAS,GAAG,CAAC,CAAA,CAAC,6BAA6B;IAEjD,mDAAmD;IACnD,KAAK,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"field-visibility.js","sourceRoot":"","sources":["../../src/access/field-visibility.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAmCpD;;;;;;;;;;;;;GAaG;AACH,KAAK,UAAU,yBAAyB,CAAC,MAQxC;IACC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,MAAM,CAAA;IAErF,kEAAkE;IAClE,MAAM,OAAO,GAAG,MAAM,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE;QAClE,GAAG,IAAI;QACP,IAAI,EAAE,UAAU;KACjB,CAAC,CAAA;IAEF,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;IAC5B,CAAC;IAED,sCAAsC;IACtC,IAAI,WAAW,EAAE,KAAK,EAAE,aAAa,IAAI,OAAO,EAAE,CAAC;QACjD,6CAA6C;QAC7C,yEAAyE;QACzE,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,aAAoD,CAAA;QACnF,iFAAiF;QACjF,qDAAqD;QACrD,IAAI,CAAC,OAAO,CAAC,qBAAqB,CAAC,KAAK,EAAE,CAAA;QAC1C,IAAI,CAAC;YACH,4DAA4D;YAC5D,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,OAAO,CACpC,IAAI,CAAC;gBACH,KAAK;gBACL,SAAS,EAAE,OAAO;gBAClB,SAAS;gBACT,OAAO;gBACP,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,IAAI,CAAC,OAAO;aACtB,CAAC,CACH,CAAA;YACD,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAA;QAC5C,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,OAAO,CAAC,qBAAqB,CAAC,KAAK,EAAE,CAAA;QAC5C,CAAC;IACH,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;AAClC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,IAAO,EACP,YAAyC,EACzC,IAGC,EACD,MAAuB,EACvB,QAAgB,CAAC,EACjB,OAAgB;IAEhB,MAAM,QAAQ,GAA4B,EAAE,CAAA;IAC5C,MAAM,SAAS,GAAG,CAAC,CAAA,CAAC,6BAA6B;IAEjD,4EAA4E;IAC5E,4EAA4E;IAC5E,+EAA+E;IAC/E,2EAA2E;IAC3E,wEAAwE;IACxE,4EAA4E;IAC5E,qBAAqB;IACrB,MAAM,WAAW,GAA4B,EAAE,GAAG,IAAI,EAAE,CAAA;IACxD,KAAK,MAAM,CAAC,SAAS,EAAE,WAAW,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;QACpE,IAAI,CAAC,WAAW,CAAC,eAAe,IAAI,CAAC,WAAW,CAAC,cAAc;YAAE,SAAQ;QACzE,MAAM,WAAW,GAAG,WAAW,CAAC,cAAc,CAAC,SAAS,CAAC,CAAA;QACzD,4EAA4E;QAC5E,+DAA+D;QAC/D,MAAM,YAAY,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,WAAW,CAAC,CAAA;QACpE,IAAI,CAAC,YAAY;YAAE,SAAQ;QAC3B,MAAM,SAAS,GAAG,WAAW,CAAC,eAAe,CAAC,SAAS,EAAE,WAAW,CAAC,CAAA;QACrE,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;YAC/B,OAAO,WAAW,CAAC,IAAI,CAAC,CAAA;QAC1B,CAAC;QACD,WAAW,CAAC,SAAS,CAAC,GAAG,SAAS,CAAA;IACpC,CAAC;IAED,mDAAmD;IACnD,KAAK,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;QAC7D,MAAM,WAAW,GAAG,YAAY,CAAC,SAAS,CAAC,CAAA;QAE3C,0CAA0C;QAC1C,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YACzD,QAAQ,CAAC,SAAS,CAAC,GAAG,KAAK,CAAA;YAC3B,SAAQ;QACV,CAAC;QAED,8EAA8E;QAC9E,iGAAiG;QACjG,iEAAiE;QACjE,IACE,MAAM;YACN,WAAW,EAAE,IAAI,KAAK,cAAc;YACpC,KAAK,IAAI,WAAW;YACpB,WAAW,CAAC,GAAG;YACf,KAAK,KAAK,IAAI;YACd,KAAK,KAAK,SAAS;YACnB,KAAK,GAAG,SAAS,EACjB,CAAC;YACD,yDAAyD;YACzD,MAAM,OAAO,GAAG,MAAM,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE;gBAClE,GAAG,IAAI;gBACP,IAAI,EAAE,WAAW;aAClB,CAAC,CAAA;YAEF,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,SAAQ;YACV,CAAC;YAED,MAAM,aAAa,GAAG,oBAAoB,CAAC,WAAW,CAAC,GAAa,EAAE,MAAM,CAAC,CAAA;YAE7E,IAAI,aAAa,EAAE,CAAC;gBAClB,2EAA2E;gBAC3E,kEAAkE;gBAClE,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;oBACzB,QAAQ,CAAC,SAAS,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CACrC,KAAK,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CACxB,oBAAoB,CAClB,WAAW,EACX,aAAa,CAAC,UAAU,CAAC,MAAM,EAC/B,IAAI,EACJ,MAAM,EACN,KAAK,GAAG,CAAC,EACT,aAAa,CAAC,QAAQ,CACvB,CACF,CACF,CAAA;gBACH,CAAC;gBACD,iEAAiE;gBACjE,kEAAkE;qBAC7D,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;oBACnC,QAAQ,CAAC,SAAS,CAAC,GAAG,MAAM,oBAAoB,CAC9C,KAAgC,EAChC,aAAa,CAAC,UAAU,CAAC,MAAM,EAC/B,IAAI,EACJ,MAAM,EACN,KAAK,GAAG,CAAC,EACT,aAAa,CAAC,QAAQ,CACvB,CAAA;gBACH,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,oDAAoD;gBACpD,QAAQ,CAAC,SAAS,CAAC,GAAG,KAAK,CAAA;YAC7B,CAAC;YACD,SAAQ;QACV,CAAC;QAED,wEAAwE;QACxE,mEAAmE;QACnE,MAAM,MAAM,GAAG,MAAM,yBAAyB,CAAC;YAC7C,WAAW;YACX,SAAS;YACT,KAAK;YACL,UAAU,EAAE,WAAW;YACvB,QAAQ,EAAE,WAAW;YACrB,OAAO;YACP,IAAI;SACL,CAAC,CAAA;QAEF,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YACpB,QAAQ,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,KAAK,CAAA;QACpC,CAAC;IACH,CAAC;IAED,4DAA4D;IAC5D,2FAA2F;IAC3F,KAAK,MAAM,CAAC,SAAS,EAAE,WAAW,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;QACpE,mDAAmD;QACnD,IAAI,SAAS,IAAI,QAAQ,EAAE,CAAC;YAC1B,SAAQ;QACV,CAAC;QAED,8BAA8B;QAC9B,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;YACzB,SAAQ;QACV,CAAC;QAED,wEAAwE;QACxE,qDAAqD;QACrD,IAAI,CAAC,CAAC,WAAW,CAAC,KAAK,EAAE,aAAa,IAAI,OAAO,CAAC,EAAE,CAAC;YACnD,qEAAqE;YACrE,MAAM,gBAAgB,CAAC,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAA;YAClF,SAAQ;QACV,CAAC;QAED,yEAAyE;QACzE,wEAAwE;QACxE,MAAM,MAAM,GAAG,MAAM,yBAAyB,CAAC;YAC7C,WAAW;YACX,SAAS;YACT,KAAK,EAAE,SAAS,EAAE,6CAA6C;YAC/D,UAAU,EAAE,WAAW;YACvB,QAAQ,EAAE,QAAQ;YAClB,OAAO;YACP,IAAI;SACL,CAAC,CAAA;QAEF,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YACpB,QAAQ,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,KAAK,CAAA;QACpC,CAAC;IACH,CAAC;IAED,OAAO,QAAsB,CAAA;AAC/B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-column-read-write.test.d.ts","sourceRoot":"","sources":["../../src/access/multi-column-read-write.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { filterReadableFields } from './field-visibility.js';
|
|
3
|
+
import { executeFieldResolveInputHooks } from '../hooks/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* Generic core wiring for multi-column fields (the contract storage
|
|
6
|
+
* image()/file() use in Keystone-parity mode — see ADR-0006). These tests are
|
|
7
|
+
* field-agnostic: they assert that ANY field implementing
|
|
8
|
+
* getColumnNames/assembleColumns/splitColumns is assembled on read (raw columns
|
|
9
|
+
* stripped) and split on write.
|
|
10
|
+
*/
|
|
11
|
+
// A minimal multi-column field: two physical columns `m_url` and `m_size`
|
|
12
|
+
// assembled into `{ url, size }` and split back. Optionally carries field-level
|
|
13
|
+
// access so we can lock the write-access gate around the split.
|
|
14
|
+
function multiColumnField(access) {
|
|
15
|
+
const COLUMNS = ['m_url', 'm_size'];
|
|
16
|
+
return {
|
|
17
|
+
type: 'multiColumn',
|
|
18
|
+
access,
|
|
19
|
+
getColumnNames: () => COLUMNS,
|
|
20
|
+
assembleColumns: (_fieldName, row) => {
|
|
21
|
+
const url = row.m_url;
|
|
22
|
+
if (url === null || url === undefined || url === '')
|
|
23
|
+
return null;
|
|
24
|
+
return { url, size: row.m_size ?? 0 };
|
|
25
|
+
},
|
|
26
|
+
splitColumns: (_fieldName, value) => {
|
|
27
|
+
if (value === null || value === undefined) {
|
|
28
|
+
return { m_url: null, m_size: null };
|
|
29
|
+
}
|
|
30
|
+
const v = value;
|
|
31
|
+
return { m_url: v.url ?? null, m_size: v.size ?? null };
|
|
32
|
+
},
|
|
33
|
+
// Field's own resolveInput is identity here (the value is authoritative).
|
|
34
|
+
hooks: {
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- generic test hook
|
|
36
|
+
resolveInput: async ({ resolvedData, fieldKey }) => resolvedData?.[fieldKey],
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function makeContext(overrides = {}) {
|
|
41
|
+
return {
|
|
42
|
+
session: null,
|
|
43
|
+
_isSudo: overrides.isSudo ?? false,
|
|
44
|
+
_resolveOutputCounter: { depth: 0 },
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- minimal context for unit test
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
describe('multi-column read assembly (filterReadableFields)', () => {
|
|
49
|
+
const fields = { media: multiColumnField() };
|
|
50
|
+
it('assembles the per-part columns into the logical field and strips the raw columns', async () => {
|
|
51
|
+
const row = { id: 'a', m_url: 'https://x/y.jpg', m_size: 99, title: 'hi' };
|
|
52
|
+
const result = await filterReadableFields(row,
|
|
53
|
+
// title is a plain field
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- inline field configs
|
|
55
|
+
{ ...fields, title: { type: 'text' } }, { session: null, context: makeContext() }, undefined, 0, 'Post');
|
|
56
|
+
expect(result).toEqual({ id: 'a', title: 'hi', media: { url: 'https://x/y.jpg', size: 99 } });
|
|
57
|
+
// Raw columns must NOT leak.
|
|
58
|
+
expect('m_url' in result).toBe(false);
|
|
59
|
+
expect('m_size' in result).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
it('assembles a partially-populated row (only m_url present)', async () => {
|
|
62
|
+
const row = { id: 'b', m_url: 'https://x/only.jpg' };
|
|
63
|
+
const result = await filterReadableFields(row, fields, { session: null, context: makeContext() }, undefined, 0, 'Post');
|
|
64
|
+
expect(result).toEqual({ id: 'b', media: { url: 'https://x/only.jpg', size: 0 } });
|
|
65
|
+
});
|
|
66
|
+
it('yields a null logical value when the columns are empty', async () => {
|
|
67
|
+
const row = { id: 'c', m_url: null, m_size: null };
|
|
68
|
+
const result = await filterReadableFields(row, fields, { session: null, context: makeContext() }, undefined, 0, 'Post');
|
|
69
|
+
expect(result).toEqual({ id: 'c', media: null });
|
|
70
|
+
});
|
|
71
|
+
it('leaves the field absent when its columns were not selected', async () => {
|
|
72
|
+
const row = { id: 'd', title: 'no media columns' };
|
|
73
|
+
const result = await filterReadableFields(row,
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- inline field configs
|
|
75
|
+
{ ...fields, title: { type: 'text' } }, { session: null, context: makeContext() }, undefined, 0, 'Post');
|
|
76
|
+
expect(result).toEqual({ id: 'd', title: 'no media columns' });
|
|
77
|
+
expect('media' in result).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
describe('multi-column write split (executeFieldResolveInputHooks)', () => {
|
|
81
|
+
const fields = { media: multiColumnField() };
|
|
82
|
+
it('splits the logical value into per-part columns and removes the logical key', async () => {
|
|
83
|
+
const inputData = { media: { url: 'https://x/y.jpg', size: 99 } };
|
|
84
|
+
const result = await executeFieldResolveInputHooks(inputData, { ...inputData }, fields, 'create', makeContext(), 'Post');
|
|
85
|
+
expect(result).toEqual({ m_url: 'https://x/y.jpg', m_size: 99 });
|
|
86
|
+
expect('media' in result).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
it('splitting null clears all per-part columns', async () => {
|
|
89
|
+
const inputData = { media: null };
|
|
90
|
+
const result = await executeFieldResolveInputHooks(inputData, { ...inputData }, fields, 'update', makeContext(), 'Post');
|
|
91
|
+
expect(result).toEqual({ m_url: null, m_size: null });
|
|
92
|
+
});
|
|
93
|
+
it('does not touch the columns when the logical field is absent from the write', async () => {
|
|
94
|
+
const inputData = { title: 'no media in payload' };
|
|
95
|
+
const result = await executeFieldResolveInputHooks(inputData, { ...inputData },
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- inline field configs
|
|
97
|
+
{ ...fields, title: { type: 'text' } }, 'update', makeContext(), 'Post');
|
|
98
|
+
expect(result).toEqual({ title: 'no media in payload' });
|
|
99
|
+
expect('m_url' in result).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
describe('multi-column write split respects field-level write access', () => {
|
|
103
|
+
it('does NOT write any per-part columns when update access is denied', async () => {
|
|
104
|
+
const fields = { media: multiColumnField({ update: () => false }) };
|
|
105
|
+
const inputData = { media: { url: 'https://x/y.jpg', size: 99 } };
|
|
106
|
+
const result = await executeFieldResolveInputHooks(inputData, { ...inputData }, fields, 'update', makeContext(), 'Post');
|
|
107
|
+
// The logical key is dropped (it is not a real column) AND none of its
|
|
108
|
+
// per-part columns are written — identical to how filterWritableFields
|
|
109
|
+
// drops a denied single-column field.
|
|
110
|
+
expect(result).toEqual({});
|
|
111
|
+
expect('media' in result).toBe(false);
|
|
112
|
+
expect('m_url' in result).toBe(false);
|
|
113
|
+
expect('m_size' in result).toBe(false);
|
|
114
|
+
});
|
|
115
|
+
it('does NOT write any per-part columns when create access is denied', async () => {
|
|
116
|
+
const fields = { media: multiColumnField({ create: () => false }) };
|
|
117
|
+
const inputData = { media: { url: 'https://x/y.jpg', size: 99 } };
|
|
118
|
+
const result = await executeFieldResolveInputHooks(inputData, { ...inputData }, fields, 'create', makeContext(), 'Post');
|
|
119
|
+
expect(result).toEqual({});
|
|
120
|
+
expect('m_url' in result).toBe(false);
|
|
121
|
+
});
|
|
122
|
+
it('still splits/writes the columns when write access is granted', async () => {
|
|
123
|
+
const fields = { media: multiColumnField({ update: () => true, create: () => true }) };
|
|
124
|
+
const inputData = { media: { url: 'https://x/y.jpg', size: 99 } };
|
|
125
|
+
const result = await executeFieldResolveInputHooks(inputData, { ...inputData }, fields, 'update', makeContext(), 'Post');
|
|
126
|
+
expect(result).toEqual({ m_url: 'https://x/y.jpg', m_size: 99 });
|
|
127
|
+
expect('media' in result).toBe(false);
|
|
128
|
+
});
|
|
129
|
+
it('denying the OTHER operation does not block the write (update field, create op)', async () => {
|
|
130
|
+
// A field that denies `update` must still be writable on `create`.
|
|
131
|
+
const fields = { media: multiColumnField({ update: () => false }) };
|
|
132
|
+
const inputData = { media: { url: 'https://x/y.jpg', size: 99 } };
|
|
133
|
+
const result = await executeFieldResolveInputHooks(inputData, { ...inputData }, fields, 'create', makeContext(), 'Post');
|
|
134
|
+
expect(result).toEqual({ m_url: 'https://x/y.jpg', m_size: 99 });
|
|
135
|
+
});
|
|
136
|
+
it('sudo bypasses the field-access gate and still splits', async () => {
|
|
137
|
+
const fields = { media: multiColumnField({ update: () => false }) };
|
|
138
|
+
const inputData = { media: { url: 'https://x/y.jpg', size: 99 } };
|
|
139
|
+
const result = await executeFieldResolveInputHooks(inputData, { ...inputData }, fields, 'update', makeContext({ isSudo: true }), 'Post');
|
|
140
|
+
expect(result).toEqual({ m_url: 'https://x/y.jpg', m_size: 99 });
|
|
141
|
+
});
|
|
142
|
+
it('a multi-column field WITHOUT field-level access splits exactly as before', async () => {
|
|
143
|
+
const fields = { media: multiColumnField() };
|
|
144
|
+
const inputData = { media: { url: 'https://x/y.jpg', size: 99 } };
|
|
145
|
+
const result = await executeFieldResolveInputHooks(inputData, { ...inputData }, fields, 'update', makeContext(), 'Post');
|
|
146
|
+
expect(result).toEqual({ m_url: 'https://x/y.jpg', m_size: 99 });
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
//# sourceMappingURL=multi-column-read-write.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-column-read-write.test.js","sourceRoot":"","sources":["../../src/access/multi-column-read-write.test.ts"],"names":[],"mappings":"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"}
|
package/dist/config/index.d.ts
CHANGED
|
@@ -62,5 +62,5 @@ export declare function config(userConfig: OpenSaasConfig): OpenSaasConfig | Pro
|
|
|
62
62
|
* ```
|
|
63
63
|
*/
|
|
64
64
|
export declare function list<TTypeInfo extends import('./types.js').TypeInfo>(config: ListConfigInput<TTypeInfo>): ListConfig<TTypeInfo>;
|
|
65
|
-
export type { OpenSaasConfig, ListConfig, ListConfigInput, ListAccessControl, FieldConfig, BaseFieldConfig, TextField, IntegerField, CheckboxField, TimestampField, PasswordField, SelectField, RelationshipField, PrismaRelationResult, JsonField, VirtualField, TypeDescriptor, TypeInfo, OperationAccess, Hooks, FieldHooks, FieldsWithTypeInfo, DatabaseConfig, SessionConfig, UIConfig, ThemeConfig, ThemePreset, ThemeColors, McpConfig, McpToolsConfig, McpAuthConfig, ListMcpConfig, McpCustomTool, FileMetadata, ImageMetadata, ImageTransformationResult, Plugin, PluginContext, GeneratedFiles, ResolveInputHookArgs, ValidateHookArgs, BeforeOperationHookArgs, AfterOperationHookArgs, FieldResolveInputHookArgs, FieldValidateHookArgs, FieldBeforeOperationHookArgs, FieldAfterOperationHookArgs, FieldResolveOutputHookArgs, } from './types.js';
|
|
65
|
+
export type { OpenSaasConfig, OutputConfig, ListConfig, ListConfigInput, ListAccessControl, FieldConfig, BaseFieldConfig, TextField, IntegerField, CheckboxField, TimestampField, PasswordField, SelectField, RelationshipField, PrismaRelationResult, MultiColumnPrismaResult, JsonField, VirtualField, TypeDescriptor, TypeInfo, OperationAccess, Hooks, FieldHooks, FieldsWithTypeInfo, DatabaseConfig, SessionConfig, UIConfig, ThemeConfig, ThemePreset, ThemeColors, McpConfig, McpToolsConfig, McpAuthConfig, ListMcpConfig, McpCustomTool, FileMetadata, ImageMetadata, ImageTransformationResult, Plugin, PluginContext, GeneratedFiles, ResolveInputHookArgs, ValidateHookArgs, BeforeOperationHookArgs, AfterOperationHookArgs, FieldResolveInputHookArgs, FieldValidateHookArgs, FieldBeforeOperationHookArgs, FieldAfterOperationHookArgs, FieldResolveOutputHookArgs, } from './types.js';
|
|
66
66
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,UAAU,EACV,eAAe,EAGhB,MAAM,YAAY,CAAA;AA8BnB;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,UAAU,EAAE,cAAc,GAAG,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAQ3F;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,wBAAgB,IAAI,CAAC,SAAS,SAAS,OAAO,YAAY,EAAE,QAAQ,EAClE,MAAM,EAAE,eAAe,CAAC,SAAS,CAAC,GACjC,UAAU,CAAC,SAAS,CAAC,CAUvB;AAGD,YAAY,EACV,cAAc,EACd,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,eAAe,EACf,SAAS,EACT,YAAY,EACZ,aAAa,EACb,cAAc,EACd,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACpB,SAAS,EACT,YAAY,EACZ,cAAc,EACd,QAAQ,EACR,eAAe,EACf,KAAK,EACL,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,QAAQ,EACR,WAAW,EACX,WAAW,EACX,WAAW,EACX,SAAS,EACT,cAAc,EACd,aAAa,EACb,aAAa,EACb,aAAa,EACb,YAAY,EACZ,aAAa,EACb,yBAAyB,EAEzB,MAAM,EACN,aAAa,EACb,cAAc,EAEd,oBAAoB,EACpB,gBAAgB,EAChB,uBAAuB,EACvB,sBAAsB,EAEtB,yBAAyB,EACzB,qBAAqB,EACrB,4BAA4B,EAC5B,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,YAAY,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,UAAU,EACV,eAAe,EAGhB,MAAM,YAAY,CAAA;AA8BnB;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,UAAU,EAAE,cAAc,GAAG,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAQ3F;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,wBAAgB,IAAI,CAAC,SAAS,SAAS,OAAO,YAAY,EAAE,QAAQ,EAClE,MAAM,EAAE,eAAe,CAAC,SAAS,CAAC,GACjC,UAAU,CAAC,SAAS,CAAC,CAUvB;AAGD,YAAY,EACV,cAAc,EACd,YAAY,EACZ,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,eAAe,EACf,SAAS,EACT,YAAY,EACZ,aAAa,EACb,cAAc,EACd,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACpB,uBAAuB,EACvB,SAAS,EACT,YAAY,EACZ,cAAc,EACd,QAAQ,EACR,eAAe,EACf,KAAK,EACL,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,QAAQ,EACR,WAAW,EACX,WAAW,EACX,WAAW,EACX,SAAS,EACT,cAAc,EACd,aAAa,EACb,aAAa,EACb,aAAa,EACb,YAAY,EACZ,aAAa,EACb,yBAAyB,EAEzB,MAAM,EACN,aAAa,EACb,cAAc,EAEd,oBAAoB,EACpB,gBAAgB,EAChB,uBAAuB,EACvB,sBAAsB,EAEtB,yBAAyB,EACzB,qBAAqB,EACrB,4BAA4B,EAC5B,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,YAAY,CAAA"}
|