@appxdigital/appx-core 0.1.121 → 0.1.123

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 (40) hide show
  1. package/CHANGELOG.md +48 -5
  2. package/README.md +24 -2
  3. package/dist/appx-core.module.d.ts +1 -0
  4. package/dist/appx-core.module.d.ts.map +1 -1
  5. package/dist/appx-core.module.js +1 -0
  6. package/dist/common/bigint-serializer.d.ts +1 -0
  7. package/dist/common/bigint-serializer.d.ts.map +1 -0
  8. package/dist/common/bigint-serializer.js +18 -0
  9. package/dist/common/decorators/scalar-field.decorator.d.ts +22 -0
  10. package/dist/common/decorators/scalar-field.decorator.d.ts.map +1 -0
  11. package/dist/common/decorators/scalar-field.decorator.js +103 -0
  12. package/dist/config/generate-all.js +21 -54
  13. package/dist/config/generate-controllers.d.ts +2 -1
  14. package/dist/config/generate-controllers.d.ts.map +1 -1
  15. package/dist/config/generate-controllers.js +7 -22
  16. package/dist/config/generate-dtos.d.ts +14 -1
  17. package/dist/config/generate-dtos.d.ts.map +1 -1
  18. package/dist/config/generate-dtos.js +85 -28
  19. package/dist/config/generate-models.d.ts +3 -0
  20. package/dist/config/generate-models.d.ts.map +1 -0
  21. package/dist/config/generate-models.js +107 -0
  22. package/dist/config/generate-modules.d.ts +30 -1
  23. package/dist/config/generate-modules.d.ts.map +1 -1
  24. package/dist/config/generate-modules.js +145 -86
  25. package/dist/config/generate-resolvers.d.ts +7 -1
  26. package/dist/config/generate-resolvers.d.ts.map +1 -1
  27. package/dist/config/generate-resolvers.js +10 -17
  28. package/dist/config/generate-services.d.ts +2 -1
  29. package/dist/config/generate-services.d.ts.map +1 -1
  30. package/dist/config/generate-services.js +7 -16
  31. package/dist/config/utils.d.ts +38 -1
  32. package/dist/config/utils.d.ts.map +1 -1
  33. package/dist/config/utils.js +76 -11
  34. package/dist/index.d.ts +1 -0
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +1 -0
  37. package/docs/README.md +1 -0
  38. package/docs/dtos.md +13 -1
  39. package/docs/generate.md +57 -0
  40. package/package.json +3 -1
package/CHANGELOG.md CHANGED
@@ -14,6 +14,51 @@ This project follows the `MAJOR.MINOR.PATCH` scheme in `package.json`.
14
14
 
15
15
  ---
16
16
 
17
+ ## [0.1.123] — 2026-07-22
18
+
19
+ Two changes: the `generate` command is split into a deploy-safe pass + a module wizard, and generated CRUD DTOs now type each scalar column to match its Prisma model type.
20
+
21
+ ### Generate command split
22
+
23
+ Regenerating types no longer mutates your code.
24
+
25
+ - **`appx-core generate`** — regenerates only the overwrite-safe artifacts under the gitignored `src/generated/**` (Prisma client, GraphQL types, DTO **base** classes). It **no longer** writes `src/modules/**` or edits `src/app.module.ts`. Idempotent and safe for CI / postinstall / predeploy.
26
+ - **`appx-core generate models`** — new interactive wizard that scaffolds CRUD (module/service/controller/resolver + DTO subclass) for the models you choose and registers them in `app.module.ts`, then runs the safe pass. Non-interactive: `appx-core generate models <Name…>` and `--all`. Only models **without** an existing module are offered; if there are none it prints `No modules available to generate`.
27
+ - Model discovery is driven by the Prisma **DMMF** (the schema), not by scanning the GraphQL plugin's output folders.
28
+ - The **`User`** model is framework-owned (auth already serves user endpoints; generic User CRUD is a mass-assignment surface) and is no longer scaffolded.
29
+ - Module-registration in `app.module.ts` finds the `@Module` `imports` array's real closing bracket (skipping strings/comments/nested arrays) and **appends** there, preserving order — it no longer breaks on a nested array literal like `ThrottlerModule.forRoot([...])`, never double-registers, and never adds a duplicate import.
30
+ - "Already has a module" is detected by the model a module serves (`CoreController<Model>` / `CoreService<Model>`), not by folder name — so a hand-written module under a non-canonical (e.g. pluralised) folder isn't duplicated.
31
+ - Full reference: [`docs/generate.md`](./docs/generate.md). GraphQL stays **read-only**; every scaffolded module still gets its resolver.
32
+
33
+ ### Generated-CRUD DTO scalar typing
34
+
35
+ Generated CRUD DTOs now type each scalar column to match its Prisma model type, so a controller override type-checks:
36
+
37
+ - **`DateTime` → `Date`** (was `string`), via `@Type(() => Date)`.
38
+ - **`Decimal` → `Prisma.Decimal`** (was `number`), via the new `@DecimalField()`.
39
+ - **`BigInt` → `bigint`** (was `number`), via the new `@BigIntField()`.
40
+
41
+ `@DecimalField()` / `@BigIntField()` accept a **string or number** on the wire and coerce/validate it (bad input → `400`), so there is no API change. `BigInt` columns serialize as a **string** in JSON responses (JSON has no bigint), pairing with the string-accepting input.
42
+
43
+ ### Migration
44
+
45
+ > **Behaviour change — a bare `appx-core generate` no longer creates modules or edits `app.module.ts`.** Intentional (it makes the command CI-safe), but new tables are not auto-exposed.
46
+
47
+ 1. Keep running `appx-core generate` after every schema change — it refreshes the Prisma client, GraphQL types and DTO bases, and is now guaranteed not to touch your committed code.
48
+ 2. **To expose a new table** as CRUD, run `appx-core generate models` and pick it (or `appx-core generate models <Name>`) — the only command that scaffolds `src/modules/**` and registers a module.
49
+ 3. **Existing modules are unaffected.** To reproduce the old "expose every table" behaviour in one shot, run `appx-core generate models --all`.
50
+ 4. If you relied on the generated **`/users`** CRUD, it is no longer scaffolded (auth owns `/auth/*`) — re-create it by hand only if you truly need generic user CRUD.
51
+ 5. **`Decimal` columns + GraphQL:** the GraphQL generator emits imports of `prisma-graphql-type-decimal` — a **peer dependency**, so `npm install` pulls it in (npm 7+ auto-installs peers); confirm with `npm ls prisma-graphql-type-decimal` if your package manager doesn't.
52
+ 6. If you hand-typed an `OmitType(...)` override to re-type a `DateTime` / `Decimal` / `BigInt` field (because the old type made your controller override fail with `TS2416`), delete it, let the generated base DTO stand, and regenerate. No wire change.
53
+
54
+ ---
55
+
56
+ ## [0.1.122] — 2026-07-21
57
+
58
+ **No migration necessary.**
59
+
60
+ ---
61
+
17
62
  ## [0.1.121] — 2026-07-14
18
63
 
19
64
  Hardening release: request validation, boot configuration, generated-CRUD DTOs, and data-access create enforcement.
@@ -38,8 +83,7 @@ Hardening release: request validation, boot configuration, generated-CRUD DTOs,
38
83
  - `src/generated/dto/<model>/<action>-<model>.generated.dto.ts` — base class, **always overwritten**, under the gitignored `src/generated/`. Never hand-edit.
39
84
  - `src/modules/<model>/dto/<action>-<model>.dto.ts` — a hand-owned subclass, generated **once**, for custom validation. The controller imports this.
40
85
  - **`/// @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`).
86
+ - **Create-permission enforcement in the data-access proxy.** `create` / `createMany` validate that the incoming data satisfies the permission's `conditions` before insert (the model's **own scalar fields** — relationship authorization is handled by the `connect` action; see Migration), 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.
43
87
 
44
88
  ### Writable-field policy (generated DTOs)
45
89
 
@@ -65,7 +109,7 @@ import { setupCoreSecurity } from '@appxdigital/appx-core';
65
109
  const corsOrigin = configService.get<string>('CORS_ORIGIN') ?? 'http://localhost:3000';
66
110
  setupCoreSecurity(app, { cors: { origin: corsOrigin, credentials: true } });
67
111
  ```
68
- - Install Helmet: `npm install helmet@^8.0.0` (applied automatically by `setupCoreSecurity`; tune with `{ helmet: {...} }`, disable with `{ helmet: false }`).
112
+ - Helmet is applied automatically by `setupCoreSecurity` (tune with `{ helmet: {...} }`, disable with `{ helmet: false }`). It's a peer dependency, so `npm install` pulls it in for you (npm 7+ auto-installs peers) — just confirm it's present with `npm ls helmet`. Only if your package manager doesn't auto-install peers (or reports it missing) add it explicitly.
69
113
  - If you register a `ValidationPipe` with custom options, pass them through: `setupCoreSecurity(app, { validationPipe: { /* overrides */ } })`.
70
114
  - 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.
71
115
  - 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 } }`.
@@ -112,7 +156,7 @@ async create(@Body() data: Create<Model>Dto) { return super.create(data as any);
112
156
  @Put(':id') @Permission('updateMany')
113
157
  async update(@Param('id') id: string, @Body() data: Update<Model>Dto) { return super.update(id, data as any); }
114
158
  ```
115
- (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).
159
+ (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` / `/// @Role(none)` (all roles), 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).
116
160
 
117
161
  > **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.**
118
162
  >
@@ -147,4 +191,3 @@ prefix. The auth building blocks (`AuthController`, `AuthService`, `RegisterDto`
147
191
  **5. Create-permission enforcement (behaviour change — review before upgrading).**
148
192
  - 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.
149
193
  - 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.
150
- - **`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/README.md CHANGED
@@ -202,13 +202,35 @@ npx prisma db pull
202
202
 
203
203
  ### 5) Generate Modules and CRUD
204
204
 
205
- After you edit `schema.prisma`, run:
205
+ There are two generation commands, split by what they touch:
206
206
 
207
207
  ```bash
208
+ # Deploy-safe: regenerate the Prisma client, GraphQL types and DTO base classes
209
+ # under src/generated/ (gitignored). Never writes src/modules/ or app.module.ts,
210
+ # so it is safe in CI / postinstall / predeploy. Run it every time the schema
211
+ # changes (no need to run `prisma generate` separately).
208
212
  appx-core generate
213
+
214
+ # Scaffold CRUD for the tables you want to EXPOSE. Interactive picker listing
215
+ # models that don't yet have a module; for each selected model it creates the
216
+ # module/service/controller/resolver + DTO subclass and registers the module in
217
+ # app.module.ts, then runs the safe pass above.
218
+ appx-core generate models
219
+ # appx-core generate models Habit Log # non-interactive: named models
220
+ # appx-core generate models --all # every generatable model
209
221
  ```
210
222
 
211
- Run this **every time** you change the Prisma schema. This is the only required generation step (no need to run `prisma generate` separately).
223
+ Not every table needs its own module — because CRUD supports permission-gated
224
+ nested writes (`create` / `connect`), you can expose a parent and write related
225
+ rows through it. Scaffold modules only for the resources you actually serve.
226
+
227
+ > **`generate` is deploy-safe; `generate models` mutates code.** Only `generate
228
+ > models` writes hand-owned files and edits `app.module.ts` — run it during
229
+ > development. `generate` alone is idempotent and touches only `src/generated/`.
230
+ > The `User` model is framework-owned (auth already serves user endpoints) and is
231
+ > never offered by the wizard.
232
+
233
+ Full reference: **[docs/generate.md](./docs/generate.md)**.
212
234
 
213
235
  ### 6) Run Migrations
214
236
 
@@ -1,3 +1,4 @@
1
+ import './common/bigint-serializer';
1
2
  import { DynamicModule } from '@nestjs/common';
2
3
  import { PermissionsConfigType } from './common/config/permissionsConfigTypes';
3
4
  import { FileUploadModuleOptions } from './common/interfaces/file-upload.interface';
@@ -1 +1 @@
1
- {"version":3,"file":"appx-core.module.d.ts","sourceRoot":"","sources":["../src/appx-core.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,aAAa,EAA2B,MAAM,gBAAgB,CAAC;AAEvE,OAAO,EAAC,qBAAqB,EAAC,MAAM,wCAAwC,CAAC;AAG7E,OAAO,EAAC,uBAAuB,EAAC,MAAM,2CAA2C,CAAC;AAElF,qBAEa,cAAc;IACvB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,qBAAqB,EAAE,gBAAgB,CAAC,EAAE,uBAAuB,GAAG,aAAa;CA6B5G"}
1
+ {"version":3,"file":"appx-core.module.d.ts","sourceRoot":"","sources":["../src/appx-core.module.ts"],"names":[],"mappings":"AAAA,OAAO,4BAA4B,CAAC;AACpC,OAAO,EAAC,aAAa,EAA2B,MAAM,gBAAgB,CAAC;AAEvE,OAAO,EAAC,qBAAqB,EAAC,MAAM,wCAAwC,CAAC;AAG7E,OAAO,EAAC,uBAAuB,EAAC,MAAM,2CAA2C,CAAC;AAElF,qBAEa,cAAc;IACvB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,qBAAqB,EAAE,gBAAgB,CAAC,EAAE,uBAAuB,GAAG,aAAa;CA6B5G"}
@@ -8,6 +8,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
8
8
  var AppxCoreModule_1;
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.AppxCoreModule = void 0;
11
+ require("./common/bigint-serializer"); // side effect: BigInt → string in JSON output
11
12
  const common_1 = require("@nestjs/common");
12
13
  const permissions_config_provider_1 = require("./common/config/permissions.config.provider");
13
14
  const permissions_service_1 = require("./common/config/permissions.service");
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=bigint-serializer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bigint-serializer.d.ts","sourceRoot":"","sources":["../../src/common/bigint-serializer.ts"],"names":[],"mappings":""}
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ /**
3
+ * `JSON.stringify` throws on a `bigint` ("Do not know how to serialize a
4
+ * BigInt"), so any model with a Prisma `BigInt` column would 500 when a CRUD
5
+ * endpoint serialises the row. Teach BigInt to serialise as its decimal string
6
+ * in JSON output (HTTP responses, logs). Applied once, on import — imported for
7
+ * its side effect by the core module, so it is active in every AppX Core app.
8
+ *
9
+ * String output pairs with `@BigIntField()` on the input side, which accepts a
10
+ * string (or number) — so a BigInt round-trips as a string over HTTP.
11
+ */
12
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
+ if (typeof BigInt.prototype.toJSON !== 'function') {
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, no-extend-native
15
+ BigInt.prototype.toJSON = function () {
16
+ return this.toString();
17
+ };
18
+ }
@@ -0,0 +1,22 @@
1
+ import { ValidatorConstraintInterface } from 'class-validator';
2
+ export declare class IsDecimalInputConstraint implements ValidatorConstraintInterface {
3
+ validate(value: unknown): boolean;
4
+ defaultMessage(): string;
5
+ }
6
+ export declare class IsBigIntInputConstraint implements ValidatorConstraintInterface {
7
+ validate(value: unknown): boolean;
8
+ defaultMessage(): string;
9
+ }
10
+ /**
11
+ * Marks a DTO field as a Prisma `Decimal`: accepts a string or number on the
12
+ * wire and validates it (Prisma coerces it to `Prisma.Decimal` on write). Pair
13
+ * with `@IsOptional` for optional columns.
14
+ */
15
+ export declare function DecimalField(): PropertyDecorator;
16
+ /**
17
+ * Marks a DTO field as a Prisma `BigInt`: accepts a string or number on the wire,
18
+ * coerces it to `bigint`, and validates it. Pair with `@IsOptional` for optional
19
+ * columns.
20
+ */
21
+ export declare function BigIntField(): PropertyDecorator;
22
+ //# sourceMappingURL=scalar-field.decorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scalar-field.decorator.d.ts","sourceRoot":"","sources":["../../../src/common/decorators/scalar-field.decorator.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgC,4BAA4B,EAAC,MAAM,iBAAiB,CAAC;AAiD5F,qBACa,wBAAyB,YAAW,4BAA4B;IACzE,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO;IAIjC,cAAc,IAAI,MAAM;CAG3B;AAED,qBACa,uBAAwB,YAAW,4BAA4B;IACxE,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO;IAIjC,cAAc,IAAI,MAAM;CAG3B;AAED;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,iBAAiB,CAEhD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,iBAAiB,CAK/C"}
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.BigIntField = exports.DecimalField = exports.IsBigIntInputConstraint = exports.IsDecimalInputConstraint = void 0;
10
+ const common_1 = require("@nestjs/common");
11
+ const class_transformer_1 = require("class-transformer");
12
+ const class_validator_1 = require("class-validator");
13
+ /**
14
+ * DTO field decorators for Prisma `Decimal` and `BigInt` columns.
15
+ *
16
+ * Generated CRUD DTOs must type these columns as `Prisma.Decimal` / `bigint`
17
+ * (the Prisma model types) so a controller override type-checks against
18
+ * `CoreController<T>`. But JSON can't carry them and class-transformer doesn't
19
+ * know how to build them. These decorators accept a **string or number** on the
20
+ * wire and validate it; a value that isn't numeric yields a clean `400`.
21
+ *
22
+ * - `@DecimalField()` validates the value and passes it through unchanged —
23
+ * Prisma's create/update input accepts `string | number | Decimal` and coerces
24
+ * it to a real `Prisma.Decimal` on write. (We deliberately do NOT construct a
25
+ * `Prisma.Decimal` here: the framework's own bundled client is a stub, so a
26
+ * `require('@prisma/client').Prisma.Decimal` isn't reliably available at the
27
+ * framework's runtime location.)
28
+ * - `@BigIntField()` coerces to a real `bigint` (Prisma's BigInt input doesn't
29
+ * accept a string); `BigInt()` is a global, so no client is involved.
30
+ */
31
+ const isNullish = (v) => v === null || v === undefined;
32
+ // A finite number, or a string that is a plain/scientific decimal literal.
33
+ const DECIMAL_STRING = /^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/;
34
+ function isDecimalWire(v) {
35
+ if (typeof v === 'number')
36
+ return Number.isFinite(v);
37
+ if (typeof v === 'string') {
38
+ const s = v.trim();
39
+ return s !== '' && DECIMAL_STRING.test(s);
40
+ }
41
+ return false;
42
+ }
43
+ function toBigInt(value) {
44
+ if (isNullish(value))
45
+ return value;
46
+ if (Array.isArray(value))
47
+ return value.map(toBigInt);
48
+ if (typeof value !== 'string' && typeof value !== 'number')
49
+ return value;
50
+ try {
51
+ return BigInt(value);
52
+ }
53
+ catch {
54
+ return value; // non-integer / garbage → validator produces a 400
55
+ }
56
+ }
57
+ // Constraints reject nullish so a missing REQUIRED column fails with a clean 400;
58
+ // optional columns carry `@IsOptional`, which short-circuits before the
59
+ // constraint runs, so null/undefined is still accepted there.
60
+ let IsDecimalInputConstraint = exports.IsDecimalInputConstraint = class IsDecimalInputConstraint {
61
+ validate(value) {
62
+ if (Array.isArray(value))
63
+ return value.every(isDecimalWire);
64
+ return isDecimalWire(value);
65
+ }
66
+ defaultMessage() {
67
+ return '$property must be a decimal (a numeric string or number)';
68
+ }
69
+ };
70
+ exports.IsDecimalInputConstraint = IsDecimalInputConstraint = __decorate([
71
+ (0, class_validator_1.ValidatorConstraint)({ name: 'isDecimalInput', async: false })
72
+ ], IsDecimalInputConstraint);
73
+ let IsBigIntInputConstraint = exports.IsBigIntInputConstraint = class IsBigIntInputConstraint {
74
+ validate(value) {
75
+ if (Array.isArray(value))
76
+ return value.every((v) => typeof v === 'bigint');
77
+ return typeof value === 'bigint';
78
+ }
79
+ defaultMessage() {
80
+ return '$property must be an integer (a numeric string or number)';
81
+ }
82
+ };
83
+ exports.IsBigIntInputConstraint = IsBigIntInputConstraint = __decorate([
84
+ (0, class_validator_1.ValidatorConstraint)({ name: 'isBigIntInput', async: false })
85
+ ], IsBigIntInputConstraint);
86
+ /**
87
+ * Marks a DTO field as a Prisma `Decimal`: accepts a string or number on the
88
+ * wire and validates it (Prisma coerces it to `Prisma.Decimal` on write). Pair
89
+ * with `@IsOptional` for optional columns.
90
+ */
91
+ function DecimalField() {
92
+ return (0, common_1.applyDecorators)((0, class_validator_1.Validate)(IsDecimalInputConstraint));
93
+ }
94
+ exports.DecimalField = DecimalField;
95
+ /**
96
+ * Marks a DTO field as a Prisma `BigInt`: accepts a string or number on the wire,
97
+ * coerces it to `bigint`, and validates it. Pair with `@IsOptional` for optional
98
+ * columns.
99
+ */
100
+ function BigIntField() {
101
+ return (0, common_1.applyDecorators)((0, class_transformer_1.Transform)(({ value }) => toBigInt(value)), (0, class_validator_1.Validate)(IsBigIntInputConstraint));
102
+ }
103
+ exports.BigIntField = BigIntField;
@@ -1,58 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
- if (k2 === undefined) k2 = k;
5
- var desc = Object.getOwnPropertyDescriptor(m, k);
6
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
- desc = { enumerable: true, get: function() { return m[k]; } };
8
- }
9
- Object.defineProperty(o, k2, desc);
10
- }) : (function(o, m, k, k2) {
11
- if (k2 === undefined) k2 = k;
12
- o[k2] = m[k];
13
- }));
14
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
- Object.defineProperty(o, "default", { enumerable: true, value: v });
16
- }) : function(o, v) {
17
- o["default"] = v;
18
- });
19
- var __importStar = (this && this.__importStar) || function (mod) {
20
- if (mod && mod.__esModule) return mod;
21
- var result = {};
22
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
23
- __setModuleDefault(result, mod);
24
- return result;
25
- };
26
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
+ /**
5
+ * `appx generate` — the DEPLOY-SAFE pass.
6
+ *
7
+ * Regenerates only the overwrite-safe artifacts under the gitignored
8
+ * `src/generated/**`:
9
+ * - the Prisma client and the prisma-nestjs-graphql outputs (`prisma generate`
10
+ * runs both generator blocks), and
11
+ * - the DTO base classes (`src/generated/dto/**`).
12
+ *
13
+ * It NEVER writes `src/modules/**` and NEVER edits `src/app.module.ts`, so it is
14
+ * safe to run in CI / postinstall / predeploy and re-run any number of times.
15
+ * Scaffolding CRUD modules (which mutates code) is the separate, interactive
16
+ * `appx generate models` wizard.
17
+ */
27
18
  const child_process_1 = require("child_process");
28
- const path = __importStar(require("path"));
29
- const scriptsDir = path.join(__dirname);
30
- console.log('Running Prisma Generate...');
31
- (0, child_process_1.execSync)(`prisma generate`, {
32
- stdio: 'inherit',
33
- });
34
- console.log('Generating Modules...');
35
- (0, child_process_1.execSync)(`node ${path.join(scriptsDir, 'generate-modules.js')}`, {
36
- stdio: 'inherit',
37
- });
38
- console.log('Generating Resolvers...');
39
- (0, child_process_1.execSync)(`node ${path.join(scriptsDir, 'generate-resolvers.js')}`, {
40
- stdio: 'inherit',
41
- });
42
- console.log('Generating Services...');
43
- (0, child_process_1.execSync)(`node ${path.join(scriptsDir, 'generate-services.js')}`, {
44
- stdio: 'inherit',
45
- });
46
- console.log('Generating DTOs...');
47
- (0, child_process_1.execSync)(`node ${path.join(scriptsDir, 'generate-dtos.js')}`, {
48
- stdio: 'inherit',
49
- });
50
- console.log('Generating Controllers...');
51
- (0, child_process_1.execSync)(`node ${path.join(scriptsDir, 'generate-controllers.js')}`, {
52
- stdio: 'inherit',
53
- });
54
- // console.log('Generating Session Schema...');
55
- // execSync(`node ${path.join(scriptsDir, 'generate-session-schema.js')}`, {
56
- // stdio: 'inherit',
57
- // });
58
- console.log('All generators ran successfully.');
19
+ const utils_1 = require("./utils");
20
+ const generate_dtos_1 = require("./generate-dtos");
21
+ console.log('Running Prisma Generate (client + GraphQL artifacts)...');
22
+ (0, child_process_1.execSync)('prisma generate', { stdio: 'inherit' });
23
+ console.log('Generating DTO base classes...');
24
+ (0, generate_dtos_1.generateDtoBases)((0, utils_1.loadAllModels)());
25
+ console.log('Deploy-safe generation complete — src/generated only, no code changes.');
@@ -1,2 +1,3 @@
1
- export {};
1
+ /** Scaffold the controller file for a single model (once; never overwritten). */
2
+ export declare function scaffoldController(modelName: string): void;
2
3
  //# sourceMappingURL=generate-controllers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"generate-controllers.d.ts","sourceRoot":"","sources":["../../src/config/generate-controllers.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"generate-controllers.d.ts","sourceRoot":"","sources":["../../src/config/generate-controllers.ts"],"names":[],"mappings":"AA6CA,iFAAiF;AACjF,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAI1D"}
@@ -23,10 +23,9 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- const fs = __importStar(require("fs"));
26
+ exports.scaffoldController = void 0;
27
27
  const path = __importStar(require("path"));
28
28
  const utils_1 = require("./utils");
29
- const modelsPath = path.join(process.cwd(), 'src/generated');
30
29
  const modulesOutputPath = path.join(process.cwd(), 'src/modules');
31
30
  /**
32
31
  * Generic controller template for a given model
@@ -67,24 +66,10 @@ export class ${model}Controller extends CoreController<${model}> {
67
66
  }
68
67
  }
69
68
  `;
70
- /**
71
- * Generate controllers for each model
72
- */
73
- fs.readdirSync(modelsPath).forEach((folder) => {
74
- if (utils_1.IGNORE_FOLDERS.includes(folder))
75
- return;
76
- const modelName = (0, utils_1.kebabToPascalCase)(folder); // Capitalize model name e.g., 'Comment', 'Post'
77
- const modelOutputPath = path.join(modulesOutputPath, folder);
78
- if (!fs.existsSync(modelOutputPath)) {
79
- fs.mkdirSync(modelOutputPath, { recursive: true });
80
- console.log(`Folder for model ${modelName} created.`);
81
- }
82
- else {
83
- console.log(`Folder for model ${modelName} already exists, skipping creation.`);
84
- }
85
- /**
86
- * Create the controller file
87
- */
88
- const controllerPath = path.join(modelOutputPath, `${folder}.controller.ts`);
69
+ /** Scaffold the controller file for a single model (once; never overwritten). */
70
+ function scaffoldController(modelName) {
71
+ const folder = (0, utils_1.modelFolder)(modelName);
72
+ const controllerPath = path.join(modulesOutputPath, folder, `${folder}.controller.ts`);
89
73
  (0, utils_1.createFileIfNotExists)(controllerPath, genericControllerTemplate(modelName, folder));
90
- });
74
+ }
75
+ exports.scaffoldController = scaffoldController;
@@ -1,2 +1,15 @@
1
- export {};
1
+ /**
2
+ * Deploy-safe pass: (re)generate the DTO **base** classes under the gitignored
3
+ * `src/generated/dto/`, one per non-framework model. Overwrite-only — never
4
+ * touches `src/modules/`. `allModels` is the full DMMF list (framework models
5
+ * included) so relation-nested-write members can resolve their target model;
6
+ * bases are only emitted for non-framework models.
7
+ */
8
+ export declare function generateDtoBases(allModels: any[]): void;
9
+ /**
10
+ * Wizard/scaffold pass: create the hand-owned DTO **subclass** files (create +
11
+ * update) for a single model under `src/modules/<model>/dto/`. Generated once,
12
+ * never overwritten — this is where the developer adds custom validation.
13
+ */
14
+ export declare function scaffoldDtoSubclass(modelName: string): void;
2
15
  //# sourceMappingURL=generate-dtos.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"generate-dtos.d.ts","sourceRoot":"","sources":["../../src/config/generate-dtos.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"generate-dtos.d.ts","sourceRoot":"","sources":["../../src/config/generate-dtos.ts"],"names":[],"mappings":"AA0UA;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,GAAG,EAAE,GAAG,IAAI,CAiBvD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAU3D"}