@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.
Files changed (68) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +268 -0
  3. package/CLAUDE.md +18 -15
  4. package/dist/access/field-visibility.d.ts.map +1 -1
  5. package/dist/access/field-visibility.js +29 -6
  6. package/dist/access/field-visibility.js.map +1 -1
  7. package/dist/access/multi-column-read-write.test.d.ts +2 -0
  8. package/dist/access/multi-column-read-write.test.d.ts.map +1 -0
  9. package/dist/access/multi-column-read-write.test.js +149 -0
  10. package/dist/access/multi-column-read-write.test.js.map +1 -0
  11. package/dist/config/index.d.ts +1 -1
  12. package/dist/config/index.d.ts.map +1 -1
  13. package/dist/config/types.d.ts +289 -1
  14. package/dist/config/types.d.ts.map +1 -1
  15. package/dist/context/index.d.ts.map +1 -1
  16. package/dist/context/index.js +31 -0
  17. package/dist/context/index.js.map +1 -1
  18. package/dist/extend.d.ts +1 -1
  19. package/dist/extend.d.ts.map +1 -1
  20. package/dist/fields/format-prisma-default.d.ts +35 -0
  21. package/dist/fields/format-prisma-default.d.ts.map +1 -0
  22. package/dist/fields/format-prisma-default.js +52 -0
  23. package/dist/fields/format-prisma-default.js.map +1 -0
  24. package/dist/fields/format-prisma-default.test.d.ts +2 -0
  25. package/dist/fields/format-prisma-default.test.d.ts.map +1 -0
  26. package/dist/fields/format-prisma-default.test.js +54 -0
  27. package/dist/fields/format-prisma-default.test.js.map +1 -0
  28. package/dist/fields/index.d.ts +1 -1
  29. package/dist/fields/index.d.ts.map +1 -1
  30. package/dist/fields/index.js +54 -16
  31. package/dist/fields/index.js.map +1 -1
  32. package/dist/fields/select.test.js +85 -0
  33. package/dist/fields/select.test.js.map +1 -1
  34. package/dist/fields/text-keystone-compat.test.d.ts +2 -0
  35. package/dist/fields/text-keystone-compat.test.d.ts.map +1 -0
  36. package/dist/fields/text-keystone-compat.test.js +93 -0
  37. package/dist/fields/text-keystone-compat.test.js.map +1 -0
  38. package/dist/hooks/index.d.ts.map +1 -1
  39. package/dist/hooks/index.js +60 -16
  40. package/dist/hooks/index.js.map +1 -1
  41. package/dist/index.d.ts +3 -1
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +7 -0
  44. package/dist/index.js.map +1 -1
  45. package/dist/index.test.d.ts +2 -0
  46. package/dist/index.test.d.ts.map +1 -0
  47. package/dist/index.test.js +33 -0
  48. package/dist/index.test.js.map +1 -0
  49. package/dist/mcp/handler.js +0 -1
  50. package/dist/mcp/handler.js.map +1 -1
  51. package/package.json +1 -1
  52. package/src/access/field-visibility.ts +28 -6
  53. package/src/access/multi-column-read-write.test.ts +255 -0
  54. package/src/config/index.ts +2 -0
  55. package/src/config/types.ts +291 -0
  56. package/src/context/index.ts +45 -0
  57. package/src/extend.ts +6 -1
  58. package/src/fields/format-prisma-default.test.ts +64 -0
  59. package/src/fields/format-prisma-default.ts +67 -0
  60. package/src/fields/index.ts +65 -18
  61. package/src/fields/select.test.ts +99 -0
  62. package/src/fields/text-keystone-compat.test.ts +126 -0
  63. package/src/hooks/index.ts +60 -17
  64. package/src/index.test.ts +50 -0
  65. package/src/index.ts +17 -1
  66. package/src/mcp/handler.ts +0 -2
  67. package/tests/context.test.ts +80 -1
  68. package/tsconfig.tsbuildinfo +1 -1
@@ -1,4 +1,4 @@
1
1
 
2
- > @opensaas/stack-core@0.21.0 build /home/runner/work/stack/stack/packages/core
2
+ > @opensaas/stack-core@0.23.0 build /home/runner/work/stack/stack/packages/core
3
3
  > tsc
4
4
 
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 `validateInput`
177
- 4. Field validation (isRequired, length, min/max)
178
- 5. Field-level access control (filter writable fields)
179
- 6. Field `beforeOperation`
180
- 7. List `beforeOperation`
181
- 8. **Database operation**
182
- 9. List `afterOperation`
183
- 10. Field `afterOperation`
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
- 4. Field `afterOperation`
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
- where: { id },
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
- - Only computed when explicitly selected/included in queries
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,CAkIrB"}
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(item)) {
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: item,
112
- hookItem: item,
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: item,
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":"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"}
1
+ {"version":3,"file":"field-visibility.js","sourceRoot":"","sources":["../../src/access/field-visibility.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=multi-column-read-write.test.d.ts.map
@@ -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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"}
@@ -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"}