@appxdigital/appx-core 0.1.120 → 0.1.121-beta.11

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 (49) hide show
  1. package/CHANGELOG.md +123 -0
  2. package/LICENSE +21 -21
  3. package/PRERELEASES.md +151 -0
  4. package/README.md +627 -533
  5. package/RELEASING.md +168 -0
  6. package/dist/common/config/permissionsConfigTypes.d.ts +8 -0
  7. package/dist/common/config/permissionsConfigTypes.d.ts.map +1 -1
  8. package/dist/common/config/permissionsConfigTypes.js +13 -1
  9. package/dist/common/decorators/permission.decorator.d.ts +14 -0
  10. package/dist/common/decorators/permission.decorator.d.ts.map +1 -1
  11. package/dist/common/decorators/permission.decorator.js +16 -1
  12. package/dist/common/guards/rbac.guard.d.ts.map +1 -1
  13. package/dist/common/guards/rbac.guard.js +8 -1
  14. package/dist/common/interceptors/prisma.interceptor.d.ts.map +1 -1
  15. package/dist/common/interceptors/prisma.interceptor.js +24 -8
  16. package/dist/common/setup/core-config.d.ts +66 -0
  17. package/dist/common/setup/core-config.d.ts.map +1 -0
  18. package/dist/common/setup/core-config.js +67 -0
  19. package/dist/common/setup/setup-core-security.d.ts +62 -0
  20. package/dist/common/setup/setup-core-security.d.ts.map +1 -0
  21. package/dist/common/setup/setup-core-security.js +40 -0
  22. package/dist/config/generate-all.js +4 -0
  23. package/dist/config/generate-controllers.js +19 -2
  24. package/dist/config/generate-dtos.d.ts +2 -0
  25. package/dist/config/generate-dtos.d.ts.map +1 -0
  26. package/dist/config/generate-dtos.js +285 -0
  27. package/dist/config/utils.d.ts +14 -0
  28. package/dist/config/utils.d.ts.map +1 -1
  29. package/dist/config/utils.js +35 -2
  30. package/dist/index.d.ts +2 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +2 -0
  33. package/dist/modules/auth/auth.controller.d.ts.map +1 -1
  34. package/dist/modules/auth/auth.controller.js +6 -2
  35. package/dist/modules/auth/auth.service.d.ts.map +1 -1
  36. package/dist/modules/core/core.controller.d.ts.map +1 -1
  37. package/dist/modules/core/core.controller.js +12 -5
  38. package/dist/modules/user/user.service.d.ts.map +1 -1
  39. package/dist/modules/user/user.service.js +7 -3
  40. package/dist/prisma/prisma.service.d.ts +94 -2
  41. package/dist/prisma/prisma.service.d.ts.map +1 -1
  42. package/dist/prisma/prisma.service.js +338 -8
  43. package/docs/README.md +21 -0
  44. package/docs/data-access.md +108 -0
  45. package/docs/dtos.md +163 -0
  46. package/docs/graphql.md +72 -0
  47. package/docs/limitations.md +59 -0
  48. package/docs/permissions.md +155 -0
  49. package/package.json +112 -86
package/CHANGELOG.md ADDED
@@ -0,0 +1,123 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@appxdigital/appx-core` are documented here.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
+ This project follows the `MAJOR.MINOR.PATCH` scheme in `package.json`.
7
+
8
+ ## How to read this file
9
+
10
+ - **This file is the collapsed, production changelog** — one section per version published to the `latest` dist-tag. Granular per-build notes for `alpha`/`beta` prereleases live in [`PRERELEASES.md`](./PRERELEASES.md); the release process is documented in [`RELEASING.md`](./RELEASING.md).
11
+ - **Every released version has a section.** Newest first.
12
+ - **Changes that require action in an existing project are listed under `### Migration`** — copy-paste ready, small enough to hand to an AI coding agent. Items outside that block are drop-in.
13
+ - **Scaffold-only changes** (files under `cli/scaffold/`) affect *new* projects created with `appx-core create`. Existing projects own their copies of those files and apply the migration by hand.
14
+
15
+ ---
16
+
17
+ ## [0.1.121] — 2026-07-14
18
+
19
+ Hardening release: request validation, boot configuration, generated-CRUD DTOs, and data-access create enforcement.
20
+
21
+ > ## ⚠️ REQUIRED migration — upgrading the dependency is NOT enough
22
+ >
23
+ > **This is a security release, and its protections are INERT until you adapt your codebase.** Bumping `@appxdigital/appx-core` to `0.1.121` (including any `0.1.121-beta.x`) and doing nothing else leaves your app **vulnerable** — generated `POST`/`PUT` endpoints keep accepting arbitrary fields (mass-assignment), because the request-validation DTOs only take effect once you regenerate them **and** wire them into your controllers.
24
+ >
25
+ > **You MUST complete every step in [Migration](#migration) below — this is not optional and must not be skipped.** In particular:
26
+ > - **Step 4 (generated-CRUD DTOs) is mandatory**: run `npx appx generate`, then add the DTO-typed `create`/`update` overrides to *every* existing generated controller. Generation alone does nothing; the controllers must import and use the DTOs.
27
+ > - Steps 1–3 (`main.ts` / `app.module.ts`) are required for the boot-time hardening (validation pipe, session secret, CORS, `NODE_ENV`) to apply.
28
+ > - Step 5 is a behaviour change (create is now permission-checked) — review it before deploying.
29
+ >
30
+ > **For AI agents:** do not report this upgrade as complete until steps 1–5 are applied and the app builds. If you only changed the version number, the task is unfinished.
31
+
32
+ ### Added
33
+
34
+ - **`setupCoreSecurity(app, options?)`** — one call that applies baseline HTTP hardening: a global `ValidationPipe` (`{ transform: true, whitelist: true, forbidNonWhitelisted: true }`), CORS (`cors` option, default `{ origin: 'http://localhost:3000', credentials: true }`, throws if `origin: '*'` is combined with `credentials: true`), and **Helmet** security headers (on by default; `helmet` option to tune/disable). `helmet` is a new peer dependency.
35
+ - **`buildCoreSessionOptions({ secret, store, cookieName?, ttlSeconds?, secure?, sameSite? })`** — builds hardened `express-session` options. Throws if `secret` is missing or `< 32` chars (no fallback); sets `cookie: { httpOnly, secure (prod), sameSite: 'lax', maxAge }`.
36
+ - **`coreEnvFilePath()`** — returns `['.env.${NODE_ENV}', '.env']` (env-specific file wins, plain `.env` is a fallback) and throws if `NODE_ENV` is unset.
37
+ - **Per-model DTO generation** (`appx generate`). Each model+action gets a `class-validator` DTO so generated controllers type their request body concretely and the whitelist pipe can strip unknown fields. Two files:
38
+ - `src/generated/dto/<model>/<action>-<model>.generated.dto.ts` — base class, **always overwritten**, under the gitignored `src/generated/`. Never hand-edit.
39
+ - `src/modules/<model>/dto/<action>-<model>.dto.ts` — a hand-owned subclass, generated **once**, for custom validation. The controller imports this.
40
+ - **`/// @NoWrite` field annotation** — excludes a field from the generated create/update DTOs (all roles). Fields annotated `/// @Role(none)` are also treated as non-writable.
41
+ - **Create-permission enforcement in the data-access proxy.** `create` / `createMany` validate that the incoming data satisfies the permission's `conditions` before insert (own-scalar fields against the data; relation conditions by looking up the referenced parent), and default-deny when the model/role has no `create` permission. A development-only warning fires when a created row would not satisfy the model's `find` conditions.
42
+ - **`restrictedFields` on `create`** in `CoreController` (previously only `update`, which also read the wrong permission key so it never applied under `updateMany`).
43
+
44
+ ### Writable-field policy (generated DTOs)
45
+
46
+ A scalar field is included unless it is: the primary key (`@id`), a server-managed timestamp (`@default(now())` / `@updatedAt`), `/// @NoWrite`, or `/// @Role(none)`. Relation navigation fields are excluded; their scalar foreign keys are kept. Enums map to `@IsEnum`, scalars to the matching `@IsString`/`@IsInt`/`@IsBoolean`/`@IsNumber`/`@IsDateString`; `Json`/`Bytes` use `@Allow()`.
47
+
48
+ ### Changed (scaffold — new projects only)
49
+
50
+ - `main.ts` calls `setupCoreSecurity(app, { cors: { origin: corsOrigin, credentials: true } })`, builds session options via `buildCoreSessionOptions`, and reads `CORS_ORIGIN` from config.
51
+ - `app.module.ts` uses `coreEnvFilePath()` for `envFilePath`.
52
+ - Generated `package.json` adds `helmet`; the `start` script sets `NODE_ENV=development`.
53
+ - `AuthController.getAllSessions` / `closeSpecificSession` use `@UseGuards(AuthenticatedGuard)`.
54
+
55
+ ### Migration
56
+
57
+ > **Required for every existing project. All five steps must be applied** — the edits are to files you own (`main.ts` / `app.module.ts` / `package.json` / your generated controllers), so they are safe to hand to an AI coding agent, but they are **not optional**. The app is not secured until they are done and it builds.
58
+
59
+ **1. Request validation & HTTP hardening — `src/main.ts`.**
60
+ ```ts
61
+ import { setupCoreSecurity } from '@appxdigital/appx-core';
62
+ // remove: app.useGlobalPipes(new ValidationPipe({ transform: true }));
63
+ // remove: app.enableCors({ origin: 'http://localhost:3000', credentials: true });
64
+ const corsOrigin = configService.get<string>('CORS_ORIGIN') ?? 'http://localhost:3000';
65
+ setupCoreSecurity(app, { cors: { origin: corsOrigin, credentials: true } });
66
+ ```
67
+ - Install Helmet: `npm install helmet@^8.0.0` (applied automatically by `setupCoreSecurity`; tune with `{ helmet: {...} }`, disable with `{ helmet: false }`).
68
+ - If you register a `ValidationPipe` with custom options, pass them through: `setupCoreSecurity(app, { validationPipe: { /* overrides */ } })`.
69
+ - With `whitelist` + `forbidNonWhitelisted`, body properties not declared on a `class-validator` DTO are rejected (`400`). Confirm every DTO declares every field it legitimately accepts before deploying.
70
+ - Set `CORS_ORIGIN` to your real front-end origin in production. Do not combine `'*'` with credentials — the helper throws at boot. For a public credential-less API pass `{ cors: { origin: '*', credentials: false } }`.
71
+
72
+ **2. Session secret & cookie flags — `src/main.ts`.**
73
+ ```ts
74
+ import { buildCoreSessionOptions } from '@appxdigital/appx-core';
75
+ // replace your inline session({ secret: … }) (drop any hardcoded fallback secret) with:
76
+ app.use(session(buildCoreSessionOptions({
77
+ secret: configService.get<string>('SESSION_SECRET'),
78
+ cookieName: configService.get<string>('SESSION_COOKIE_NAME'),
79
+ ttlSeconds: configService.get<number>('SESSION_TTL') || 86400,
80
+ store: new CorePrismaSessionStore(prismaService, { ttl: sessionTTL }),
81
+ })));
82
+ ```
83
+ Ensure `SESSION_SECRET` is set (≥ 32 chars) in every environment — the app now refuses to boot without it.
84
+
85
+ **3. Environment file & `NODE_ENV` — `src/app.module.ts`.**
86
+ ```ts
87
+ import { coreEnvFilePath } from '@appxdigital/appx-core';
88
+ // ConfigModule.forRoot({ … envFilePath: coreEnvFilePath() })
89
+ ```
90
+ Set `NODE_ENV` explicitly (`production` / `development`) in every deploy and start script — boot fails if it is unset. If you use a bare `nest start`, change it to `cross-env NODE_ENV=development nest start`. (`coreEnvFilePath()` loads `.env.<NODE_ENV>` with `.env` as a fallback, so a plain `.env` still works.)
91
+
92
+ **4. Generated-CRUD DTOs — MANDATORY (this is the mass-assignment fix; do not skip).**
93
+
94
+ Without this step, your generated `POST`/`PUT` endpoints still accept arbitrary fields — the vulnerability this release closes stays open. Two parts, **both required**:
95
+
96
+ (a) Regenerate:
97
+ ```bash
98
+ npm install @appxdigital/appx-core@^0.1.121
99
+ npx appx generate
100
+ ```
101
+ (b) **Add the DTO-typed overrides to EVERY existing generated controller** (they are generated once and never overwritten, so `appx generate` will NOT add them for you — you must edit each controller by hand or delete and regenerate it):
102
+ ```ts
103
+ import { Body, Param, Post, Put } from '@nestjs/common';
104
+ import { Permission } from '@appxdigital/appx-core';
105
+ import { Create<Model>Dto } from './dto/create-<model-kebab>.dto';
106
+ import { Update<Model>Dto } from './dto/update-<model-kebab>.dto';
107
+
108
+ @Post() @Permission('create')
109
+ async create(@Body() data: Create<Model>Dto) { return super.create(data as any); }
110
+
111
+ @Put(':id') @Permission('updateMany')
112
+ async update(@Param('id') id: string, @Body() data: Update<Model>Dto) { return super.update(id, data as any); }
113
+ ```
114
+ (Or delete an un-customized controller and re-run `appx generate` for the new template.) Add custom validation in the hand-owned `dto/*.dto.ts` subclasses — never in the `.generated.dto.ts` base. Lock sensitive columns with `/// @NoWrite` (all roles) or `restrictedFields` on the permission (per role), then regenerate. To drop a field from one endpoint's DTO, extend `OmitType(<Model>GeneratedDto, ['field'] as const)`. Full details in [`docs/dtos.md`](../docs/dtos.md).
115
+
116
+ > **Nested relation writes are now an explicit, permission-gated allowlist — this is a behaviour restriction; a migrating agent MUST flag it to the developer.** Generated **create** DTOs accept only two nested operators, each authorized by ABAC: **`create`** (checked against the related model's `create` rule) and **`connect`** (checked against the related model's new dedicated **`connect`** permission — a `connect` rule is required; no rule ⇒ refused). Every other operator (`set`, `disconnect`, `update`, `upsert`, `delete`, …) is rejected (`400`). Nested writes are available on **`create` only** — `update` resolves to `updateMany`, whose payload is scalar-only. **Keep `forbidNonWhitelisted: true` on (fail loud); never `whitelist`-strip on writes.**
117
+ >
118
+ > **For AI agents doing this migration on an existing project:** this can break existing behaviour. Before deploying, tell the developer explicitly that (a) any nested operator other than `create`/`connect` used through CRUD will now `400`, (b) every model that is nested-`connect`ed now needs a `connect` permission or the request fails, and (c) nested writes no longer work on update endpoints. Do not proceed silently. See [`docs/dtos.md`](../docs/dtos.md#relations--nested-writes) and [`docs/permissions.md`](../docs/permissions.md).
119
+
120
+ **5. Create-permission enforcement (behaviour change — review before upgrading).**
121
+ - Ensure every model/role that legitimately creates records has an explicit `create` permission (or `'ALL'`). Without one, create now returns `403` (previously unchecked). Internal framework flows (registration, sessions, tokens) are unaffected.
122
+ - If you supplied arbitrary values for a field a `create` condition constrains (e.g. an owner id), those requests now return `403` unless the value matches — use `setUserIdField` to have the framework set it.
123
+ - **`restrictedFields` under `updateMany` now applies** (it was a no-op before). Fields listed there are stripped on `PUT`. Remove any field that should remain writable.
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 AppX
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AppX
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/PRERELEASES.md ADDED
@@ -0,0 +1,151 @@
1
+ # Prerelease log
2
+
3
+ > ## ⚠️ Every `0.1.121-beta.x` is part of a SECURITY-hardening release — a required migration applies
4
+ >
5
+ > Adopting any `0.1.121` beta from `0.1.120` (or earlier) requires the **mandatory migration in [`CHANGELOG.md`](./CHANGELOG.md) under `[0.1.121]`** — most importantly **regenerating the per-model DTOs and wiring them into your controllers** (`npx appx generate` + adding the DTO-typed `create`/`update` overrides to every generated controller). **Bumping the version without doing this leaves generated `POST`/`PUT` endpoints accepting arbitrary fields.**
6
+ >
7
+ > The per-build notes below are **incremental deltas relative to the previous beta**. When an entry says "no code change" or "regenerate not required", it means *nothing new to do versus the prior beta* — it does **not** waive the `[0.1.121]` migration you owe when coming from `0.1.120`.
8
+ >
9
+ > **AI agents:** do not report a `0.1.121` upgrade as done until the `CHANGELOG.md [0.1.121]` migration steps 1–5 are applied and the project builds.
10
+
11
+ Granular notes for **alpha / beta** builds of both packages —
12
+ `@appxdigital/appx-core` (library) and `@appxdigital/appx-core-cli` (CLI) —
13
+ newest first. Entries are labelled by package. These are the builds you install
14
+ to test a change in a real project before it ships:
15
+
16
+ ```bash
17
+ npm install @appxdigital/appx-core@beta # library, latest beta
18
+ npm install @appxdigital/appx-core@0.1.121-beta.0 # a specific library build
19
+ npx @appxdigital/appx-core-cli@beta create # CLI, latest beta
20
+ ```
21
+
22
+ Each prerelease gets its own entry here with fine-grained notes. When a version
23
+ is promoted to production it is **collapsed** into a single curated section in
24
+ [`CHANGELOG.md`](./CHANGELOG.md) with aggregated changes and consolidated
25
+ migration steps — that public changelog is the one an upgrading project reads.
26
+ Nothing here is a substitute for it.
27
+
28
+ Prereleases are excluded from normal semver range resolution, so a project on
29
+ `^0.1.x` never pulls one by accident — you always opt in via the `@beta` /
30
+ `@alpha` tag or an exact version.
31
+
32
+ ---
33
+
34
+ ## [0.1.121-beta.11] — unreleased
35
+
36
+ **Docs.** No code change. Documented that nested writes over HTTP CRUD are
37
+ **one level deep** (a nested `create` DTO exposes the related model's scalars +
38
+ `connect`, not its own relations, to avoid circular DTOs). The proxy's
39
+ authorization is recursive; the DTO surface is the cap — deeper nesting `400`s,
40
+ and multi-level nested writes belong in an explicit endpoint. See `docs/dtos.md`
41
+ and `docs/limitations.md`.
42
+
43
+ ## [0.1.121-beta.10] — unreleased
44
+
45
+ **Library.** **Nested relation writes on generated CRUD are now an explicit,
46
+ permission-gated allowlist.** A `create` body may nest **`create`** and
47
+ **`connect`** on its relations; both are authorized by ABAC (`create` → the
48
+ related model's `create` rule, recursively; `connect` → the related model's new
49
+ dedicated **`connect`** permission — a `connect` rule is required, because being
50
+ able to *read* a record does not authorize associating it). Every other nested
51
+ operator (`set` / `disconnect` / `update` / `upsert` / `delete` / …) is rejected
52
+ at both the DTO layer (not emitted → `400`) and the proxy (throws). Nested
53
+ writes are available on **`create` only** (`update` resolves to `updateMany`,
54
+ which is scalar-only).
55
+
56
+ - **Behaviour restriction — review on upgrade.** If a project used other nested
57
+ operators through CRUD, or connected models without a `connect` rule, those
58
+ requests now fail. Add `connect` rules where needed; move unsupported nested
59
+ writes to explicit endpoints. Keep `forbidNonWhitelisted: true` (fail loud).
60
+
61
+ ## [0.1.121-beta.9] — unreleased
62
+
63
+ **Docs / packaging.** No code change.
64
+
65
+ - Added a **`docs/` folder** (now shipped inside the npm package) — the
66
+ framework's source-of-truth guide: `permissions.md` (RBAC config +
67
+ `@Permission`/`@ExposeModels`), `data-access.md` (the Prisma proxy, ABAC
68
+ enforcement, single-record method contract, relations, transactions) and
69
+ `limitations.md` (not-ready features incl. `aggregate`/GraphQL, and caveats).
70
+ - **Clarified the `[0.1.121]` migration in `CHANGELOG.md`.** It is now explicit
71
+ that regenerating the per-model DTOs and wiring them into every controller is
72
+ **mandatory** (it is the mass-assignment fix), and that a version bump without
73
+ it leaves `POST`/`PUT` endpoints accepting arbitrary fields.
74
+
75
+ ## [0.1.121-beta.8] — unreleased
76
+
77
+ **Library.**
78
+
79
+ - **Single-record data-access methods now have an honest typed contract.**
80
+ `prisma.model.*` re-aliases the methods that can't carry ABAC conditions, in
81
+ both the types and the runtime dispatch: `findUnique`→`findFirst`,
82
+ `findUniqueOrThrow`→`findFirstOrThrow`, `update`→`updateMany`,
83
+ `delete`→`deleteMany`. A mismatched call now fails at compile time instead of
84
+ the old runtime throw. **Breaking (types only):** `update()`/`delete()` return
85
+ `{ count }` and `findUnique()` takes a non-unique `where` with a nullable
86
+ return — adjust the call sites the compiler flags. A `*Many` permission action
87
+ now inherits its singular rule (`updateMany`→`update`, `deleteMany`→`delete`,
88
+ `createMany`→`create`), so a config can declare just `update`/`delete`/`create`.
89
+ - **`@ExposeModels(...models)`** — expose models on a route WITHOUT a permission
90
+ action, for public / GUEST endpoints that must read an otherwise-restricted
91
+ model without adding a `GUEST` rule for it. `@Permission(action, models)` keeps
92
+ working unchanged. Exposed-model matching is case-insensitive.
93
+
94
+ ## [0.1.121-beta.7] — unreleased
95
+
96
+ **Library.** `createMany` now inherits the `create` permission when not declared
97
+ explicitly — in both the data-access proxy and the `RbacGuard`. A
98
+ `permissions.config.ts` can declare a single `create` rule and have it cover
99
+ `createMany` too (mirrors the existing `findMany → count/findFirst` fallback).
100
+ One-directional: declaring only `createMany` does **not** enable `create`.
101
+
102
+ - If you previously duplicated `create` and `createMany`, the `createMany` entry
103
+ is now redundant (harmless to keep). No action required.
104
+
105
+ ## [0.1.121-beta.6] — unreleased
106
+
107
+ **Packaging.** The npm tarball now ships `CHANGELOG.md`, `PRERELEASES.md`, and
108
+ `RELEASING.md` (previously only `dist/` + `README.md`). Migration steps are now
109
+ readable from inside an installed `node_modules/@appxdigital/appx-core`, so an
110
+ upgrading project (or an AI agent) can apply them without cloning the repo. No
111
+ code change.
112
+
113
+ ## [0.1.121-beta.5] — unreleased
114
+
115
+ **Library.** Field-level access control (`/// @Role(...)`) now applies to
116
+ **nested** relation selections, not just top-level fields. Previously a query
117
+ that selected a restricted field *through* a relation
118
+ (`select: { relation: { select: { restrictedField: true } } }`) received that
119
+ field; the data-access proxy now omits it at every depth, for both `select` and
120
+ `include`, while preserving any `where` / `orderBy` you set on the relation.
121
+
122
+ - Exercise reads that pull fields through relations (nested `select` / `include`,
123
+ including multi-level) and confirm the values you expect still come back — a
124
+ field annotated `@Role(<roles>)` should be absent for roles not listed.
125
+ - No API change; regenerate is not required.
126
+
127
+ ## [0.1.121-beta.0] — unreleased
128
+
129
+ First prerelease of the `0.1.121` hardening set. Testing target before this is
130
+ promoted to `latest`. Full aggregated notes and migration live under
131
+ `[0.1.121]` in `CHANGELOG.md`; the highlights to exercise in a project:
132
+
133
+ - **`setupCoreSecurity(app, options?)`** — one call for the global
134
+ `ValidationPipe` (whitelist + `forbidNonWhitelisted`), CORS, and Helmet.
135
+ Verify existing DTOs still accept every field they legitimately take (the
136
+ whitelist rejects undeclared body properties with `400`).
137
+ - **`buildCoreSessionOptions(...)`** — requires a session secret ≥ 32 chars and
138
+ sets hardened cookie flags. Confirm your env provides the secret.
139
+ - **`coreEnvFilePath()`** — `['.env.${NODE_ENV}', '.env']`; throws if
140
+ `NODE_ENV` is unset. Confirm boot scripts set `NODE_ENV`.
141
+ - **Per-model generated DTOs** — regenerate (`appx generate`) and check the
142
+ generated controllers compile and accept your writable fields.
143
+ - **Create-permission enforcement in the data-access proxy** — `create` /
144
+ `createMany` now validate incoming data against the permission conditions and
145
+ default-deny when a model/role has no `create` permission. Exercise every
146
+ create path in the consuming app and confirm intended creates still succeed;
147
+ set explicit `create` permissions (or `ALL`) where needed.
148
+
149
+ > How to try it: bump this package to `0.1.121-beta.0` in the project under
150
+ > test (`npm install @appxdigital/appx-core@beta`), run the app's own suite,
151
+ > and smoke-test create/update flows and boot. Report back before promotion.